Você está na página 1de 990

PIENSA EN JAVA

PIENSA EN JAVA
Cuarta Edicin
BRUCEECKEL
President, MindView, ne.
Traduccin
Vuelapluma
PEARSON
, Prentice .
, Hall
Madrid e Mxico e Santa Fe de Bogot e Buenos Aires e Caracas e Lima
Montevideo e San Juan e San Jos e Santiago e Sao Paulo e White Plains e
/ Datos de catalogacin bibliogrfica
PIENSA EN JAVA
Bruce EckeJ
PEARSON EDUCACIN S.A. , Madrid, 2007
ISBN: 978-84-8966-034-2
Materia: Informtica, 004
Fonnato: 215 x 270 mm.
Todos los derechos reservados.
Pginas: 1004
Queda prohibida, salvo excepcin prevista en la Ley, cualquier forma de reproduccin, distribucin, comunica-
cin pblica y transfonnacin de esta obra sin contar con autorizacin de los ti tulares de propiedad intelectual.
La infraccin de los derechos mencionados puede ser constitutiva de delito contra la propiedad intelectual
(arls. 270 y sgls. Cdigo Penal).
DERECHOS RESERVADOS
2007 por PEARSON EDUCACIN S.A.
Ribera del Loira, 28
28042 Madrid
PIENSA EN JAVA
Bruee Eekel
ISBN: 978-84-8966-034-2
Deposito Legal: M-4.753-2007
PRENTICE HALL es un sello editorial autori zado de PEARSON EDUCACIN S.A.
Authorized translation from the English language edition, entitled THINlUNG IN JAVA, 4" Edition by
ECKEL BRUCE, published by Pearsoo Education loe, publishing as Prentiee Hall , Copyright 2006
EQillPO EDITORIAL
Editor: Miguel Martn-Romo
Tcnico editorial: Marta Caicoya
EQUIPO DE PRODUCCIN:
Director: Jos A. CIares
Tcnico: Mara Alvear
Diseo de Cubierta: Equipo de diseo de Pearson Educacin S.A.
Impreso por: Grficas Rgar, S. A.
IMPRESO EN ESPAA - PRINTED IN SPAIN
Este li bro ha sido impreso con papel y tintas ecolgicos
Dedicatoria
A Dawn
Resumen
del contenido
Prefacio ...................... . ..... . ..... . .... . ........ . . . .... xix
Introduccin .... . .............. . ... . ...... . ..... . . ....... . . ... . . . xxv
1 Introduccin a los objetos . .. . . .. . ....... . ......... . . .... . . .. .. . ..... 1
2 Todo es un objeto ..... . ... . ..... . ...... . ................... . ...... 23
3 Operadores ................ . ...... . .......... . ............ . ...... 43
4 Control de ejecucin ......... . . ... . ........ . ... . .. . . . .... . ......... 71
5 Inicializacin y limpieza ..... . .......... .. ...................... . ... 85
6 Control de acceso .... . ....... . ............... . ................ . .. 121
7 Reutilizacin de clases ......... . ............ . ......... . . . ...... . . . 139
8 Polimorfismo .. .... . ... . . .. . .. .. .... . . .. ....... ... .......... . .. 165
9 Interfaces ... . ......................................... . . ....... 189
10 Clases internas .............. . ......... . . . ...... . ......... . . . .... 211
11 Almacenamiento de objetos .... . ..... .. ........ .. . . .. ............. 241
12 Tratamiento de errores mediante excepciones .. . ............ . .. . ..... 277
13 Cadenas de caracteres ........ . ...... . .. . . . . . ..... . ............... 317
14 Informacin de tipos . ........ . . . .. . ..... .. ......... . ............. . 351
15 Genricos . . .... . .................. . . . ..................... . ..... 393
16 Matrices . ................... .. . . . . .... .. ...... . . . . . . .. .. . ....... 483
17 Anlisis detallado de los contenedores .... .. ........................ 513
18 E/S ..... .. . . ............. . ........ . .. . ............. . . .... .. .. . .. 587
19 Tipos enumerados .... . .... .. . . . . . .. . . . .. ......... . ...... .. ...... 659
20 Anotaciones . ....... . ....... . .... . .. .. .... ... .. .. ......... .. . . ... 693
21 Concurrencia ......... . ..... . . . . .. . .... . ............... .. . .. ..... 727
22 Interfaces grficas de usuario ... . ...... . .. . . . ........ . . ...... . .. . .. 857
A Suplementos . ........... . . . .................... . ......... . ...... 955
B Recursos ......... . . . .. . .. . ......... . . . ...... . ........ . .. .. ..... 959
ndice .. .. . . ........ . ............ . .... . .......... . ....... .. ..... 963
Contenido
Prefacio ... .. ..... . .. . ........ .. ... .. . . xix
Java SES Y SE6 ........ . . .. .. . .... ,. xx
Java SE6. . . . .... .. ..... . xx
La cuarta edicin .. .. .. . . .. . .. ..... ... .... xx
Cambios ... . xxi
Sobre el diseo de la cubierta .......... . .... xxii
Agradecimientos. . . . . . . . . . . .......... XXll
Introduccin . ..... .. .. .... . ........... . xxv
Prerrequisitos ... .... xxv
Aprendiendo Java ..... .. . . , . . ... XXVI
Objetivos ................ . . ..... XXVI
Ensear con este libro ........... . ........ XXVll
Documentacin del JDK en HTML . . . XXVI1
Ej ercicios ............................. XXVll
Fundamentos para Java . .. ........... .... . XXVIl
Cdigo fuente ........ . ..... . . . .. . ..... XXVIII
Estndares de codificacin. . .. xxix
Errores .. ............................... xxx
1 Introduccin a los objetos . ... ...... . ... . . . 1
El progreso de la abstraccin ............ . ... 1
Todo objeto tiene una interfaz ..... . .. ........ 3
Un objeto proporciona servicios ...... . ........ 4
La implementacin oculta . .......... ......... 5
Reutili zacin de la implementacin ..... ... .... 6
Herencia. . . . . . . . . . . . . . . . . . . . . . .. . ...... 6
Relaciones es-un y es-como-un ....... ... . . .... . 8
Objetos intercambiables con polimorfismo ...... 9
La jerarqua de raz nica. . . .... ...... 11
Contenedores ... .......................... 12
Tipos paramctrizados (genricos) . . ... 13
Creacin y vida de los objetos ... . ... ........ 13
Tratamiento de excepciones: manejo de errores .. 15
Programacin concurrente .................. 15
Java e Internet.
Qu es la Web?
16
. 16
Programacin del lado del clicntc ............. 18
Programacin del lado del servidor . . . . . . . . .. .. 21
Resumen........................ . ... 22
2 Todo es un objeto ....... . ...... . ........ 23
Los objetos se manipulan mediante referencias .. 23
Es necesario crear todos los obj etos. . . 24
Los lugares de almacenamiento ... ..... ... . . . 24
Caso especial: tipos primitivos. . . . . . . . . . . . . ..25
Matrices en Java. . . . . . . . ... . .. .. . .. 26
Nunca es necesario destruir un objeto ......... 27
mbito. .. .. . . . . . . . . . .. . .. . .... . .. 27
mbito de los objetos .. ............. . .. 27
Creacin de nuevos tipos de datos: class ..... ' .. 28
Campos y mtodos . . . .... . ... . ... ... 28
Mtodos, argumentos y valores de retomo ...... 29
La lista de argumentos ....................... 30
Construccin de un programa Java .... 31
Visibilidad de los nombres. . .... 31
Utilizacin de otros componentes. . ... . .. . 31
La palabra clave static . . . . . . . . . . . .. 32
Nuestro primer programa Java .. 33
Compilacin y ejecucin. . . . 35
Comentarios y documentacin embebida ....... 35
Documentacin mcdiantc comcntarios .......... 36
Sintaxi s.
HTML embebido . ... . ... . .. .
Algunos marcadores de ejemplo ... . ... . . .
. . 36
.... 37
.37
. . 39 Ejemplo de documentacin.
Estilo de codificacin. .. . . . . ... 39
Resumen .............. . ..... ... .. ... .. .. 40
Ejercicios ...... .. .. . . . ...... .. .......... 40
x Piensa en Java
3 Operadores ... ... .... ... . .. .... . . . .... . 43
Instrucciones simples de impresin ....... ... 43
Utilizacin de los operadores Java . 44
Precedencia ..... .......... . ...... . .. . .... 44
Asignacin ..................... ... .. ... . 44
Creacin dc alias en las llamadas a mtodos ...... 46
Operadores matemticos. . . . . .......... 46
Operadores unarios ms y menos .............. 48
Autoincremento y autodecremento . . . 48
Operadores relacionales . . . . . . . . . . . . . . . 49
Comprobacin de la equivalencia de obj etos ..... 49
Operadores lgicos ........................ 51
Cortocircuitos . . . . . . . . . . . . . . . . . . . . . . . ... . 52
Literales. . . . . . . . . ........ . . . .......... 53
Notacin exponencial .. . . . ........ . . ........ 54
Operadores bit a bit. ............. . .... ... 55
Operadores de desplazamiento. . 55
. 58 Operador temario if-clsc
Operadores + y += para String ............... 59
Errores comunes a la hora de utilizar operadores. 60
Operadores de proyeccin
Truncamiento y redondeo
... 60
..... 61
Promocin ..... . . .... . . . . . . . . . . . ... . . .. . . . 62
Java no tiene operador "sizeof' . . . . . ... 62
Compedio de operadores . ....... .. .... . .... 62
Resumen .... ......... .... . . . . . ... . ... .. 70
4 Control de ejecucin .. . .................. 71
troe y false ........... . ..... . . ..... .. ... 71
if-else. . .. ..... . . . . . ... . . . . .. ..... 71
Iteracin
do-while ... .
for ...
.. .... .. . .. .. ........... 72
... ...... .. . . . . . . . . . ... . . ... . 73
.. .. .. ...... ...... .. 73
El operador coma . . . . . . . . . . ... 74
Sintaxis[oreach ...... . . ..... ...... 74
returo ...... ...... . .. ... .... .. . . . . . .... 76
break y continue . .. .............. . .... ... 77
La despreciada instruccin "goto" .... .. ..... 78
switch ... ..... ....... .............. 81
Resumen ... ....... ........ . . ... . ....... 83
5 Inicializacin y limpieza . . .. . ... .. ... . . . . . 85
Inicializacin garantizada con el constructor .... 85
Sobrecarga de mtodos ..................... 87
Cmo se distingue entre mtodos sobrecargados .. 88
Sobrecarga con primitivas. ..... .. .. ... . . 89
Sobrecarga de los valores de retomo.
Constructores predetenninados .
.. .. 92
. .. 92
La palabra clave tbis ....... ........ .... ... 93
Invocacin de constructores desde otros
constructores .... . . ... ... . .. .. . ... .. ... . 95
El significado de sta tic . . ... ............... . . 96
Limpieza: finalizacin y depuracin de
memoria .............. .
Para qu se utiliza finalizeO?
...... 97
.. . 97
Es necesario efectuar las tareas de limpieza ... 98
La condicin de terminacin . . .... . ... . ... .. .. 98
Cmo funciona un depurador de memoria
Inicializacin de miembros ..
Especificacin de la ini cial izacin
Tnicializacin mediante constmctores ....
Orden de inicializacin .....
Inicializacin de datos estticos .
... 100
.. 102
... 103
104
105
.. . 106
Ini cial izacin static explcita ................ 108
Iniciali zacin de instancias no estticas
Inicializacin de matrices ..... .
..... 109
.... 110
Listas variables de argumentos . 113
Tipos enwnerados ..... . ..... ..... . . . . .. .. 11 7
Resnmen ............ .... . . . . ... .. ..... 119
6 Control de acceso . .. . . . .. . . .... . . .. . .. . 121
package: la unidad de biblioteca ....... . ... . 122
Organizacin del cdigo . ............. . . 123
Creacin de nombres de paquete unvocos ...... 124
Una biblioteca personalizada de herramientas .. 126
Uti lizacin de importaciones para modificar
el comportamiento . . ... . ... .. .. . .... . . 128
Un consejo sobre los nombres de paquete .. ... . . 128
Especificadores de acceso Java .... 128
Acceso de paquete .. . . 129
public: acceso de interfaz .. .. .. .. . .......... 129
private: ino lo toque! . . . . . . . . 130
protected: acceso de herencia .. 131
Interfaz e impl ementacin ................. 133
Acceso de clase .......... .. ........... 134
Resumen . ..... 136
7 Reutilizacin de clases .............. .. .. 139
Sintaxis de la composicin ...... ... .... .... 139
Sintaxis de la herencia ............. .. ... 142
Inicializacin de la clase base .. ......... . . .. . 143
Delegacin ....... . ....... . .... ... . . 145
Combinacin de la composicin
y de la herencia .... . ............ 147
Cmo garantizar una limpieza apropiada .. 148
Ocultacin de nombres . . . . . . . . . . . . .. . 151
Cmo elegir entre la composicin y la herencia 152
protected ....... . .............. . .... 153
Upcasting (generalizacin) ..... . . ..... ..... 154
Por qu "upcasling'? . . .. . . 155
Nueva comparacin entre la composicin
y la herencia. .. . . .. . . . . . . 155
La palabra clave final . . . . . . . . . . . . . 156
Datos final .. ....... .... . . . . .. ... 156
Mtodos final . . .. ... 159
Clases final
Una advertencia sobre final
Inicializacin y carga de clases ....
.. 161
. 161
... 162
Inicializacin con herencia . . . . . . . . . . 162
Resumen. . . . . . . . . . 163
8 Polimorfismo . ... . . . . . . .. .. . . .... . .. .. 165
Nuevas consideraciones sobre la generalizacin 165
Por qu olvidar el tipo de un objeto . ..... 166
El secreto. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
Acoplamiento de las llamadas a mtodos ....... 168
Especi ficacin del comportamiento correcto 168
Amp1iabi1idad ....................... .
Error: "sustitucin" de mtodos privare
ElTor: campos y mtodos static
Constructores y polimorfi smo
Orden de las llamadas a los constructores.
. 171
. . 173
174
... 175
176
Herencia y limpieza ...... 177
Comportamiento de los mtodos polimrficos
dentro de los constructores . . .. ... . 181
Tipos de retorno covariantes .. . . . ..... 183
Di seo de sistemas con herencia .... ... ... . 183
Sustitucin y extensin .... . .. .. ....... . .... 184
Especializacin e infonnacin de tipos
en tiempo de ejecucin .. .186
......... 187 Resumen ......... .
9 Interfaces ... . ........ ... ..... .. .. . ... 189
Clases abstractas y mtodos abstractos ....... 189
Interfaces. . . . . . . . . . .. . . . . . . .. . . . . . .. 192
Desacoplamiento completo .... . . . .. . . . .. . . 195
"Herencia mltiple" en Java ......... . ...... 199
Contenido xi
Ampliacin de la interfaz mediante herencia .. 201
Colisiones de nombres al combinar interfaces ... 202
Adaptaci n a una interfaz ..
Campos en las interfaces
....... 203
.205
Inicializacin de campos en las interfaces. .205
Anidamiento de interfaces. . . . . . . 206
Interfaces y factoras ............. . . . ...... 208
Resumen ........ ........ .. . . ....... .... 210
10 Clases internas .. .. . . .. ......... . . .. ... 211
Creacin de clases internas .... ... . ..... 211
El enlace con la clase externa ..
Utilizacin de .this y .new .
.. .... ..... 213
.... . ..... 214
Clases interna y generalizacin
Clases internas en los mtodos y mbi tos
Un nuevo anlisi s del mtodo factora
Clase anidadas .............. .
Clases dentro de interfaces .. .
Acceso al exterior desde una clase
mltiplemente anidada .. .
Para qu se usan las clases internas?
Cierres y retro llamada ..
..... 222
.224
.225
.... 226
.227
.229
Clases internas y marcos de control . . . ... 230
Cmo heredar de cIases internas ............ 236
Pueden sustituirse las clases internas? ....... 236
Clases internas locales .................... 238
Identificadores de una clase interna .. 239
Resumen ......................... . ..... 239
11 Almacenamiento de objetos .. ... ... . ..... 241
Genricos y contenedores seguros
respecto al tipo ................. .... .. 242
Conceptos bsicos ....... .... . .. .... ...... 244
Adicin de grupos de elementos .245
Impresin de contenedores ................ 247
List ...................... . ............ 248
Iterator. . . . . . . . . . . . . . . . . . . 252
Listl te.-ator . . .. . 254
LinkedList ...... .. .. ... . . .. . . ... .. .. .. . 255
Stack. . . . . . . . . . . . . . . . . . . ... 256
Set. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .258
Map ...... . .... . . . . . . . . ... . . .... .... ... 260
Queue.... ....... ........ . ... 263
PriorityQucue. . . . . . . . . . . . . . 264
Comparacin entre CoHection e Iterator ..... 266
xii Piensa en Java
La estructura foreach y los iteradores ... 268
El mtodo basado en adaptadores. . . . . . .. 270
Resumen. . . . . . . . . . . . . ...... . 273
12 Tratamiento de errores
mediante excepciones . ... ........... .. 277
Conceptos ....................... . . .... 277
Excepciones bsicas ...... . . ........ 278
Argumentos de las excepciones. . . 279
Deteccin de una excepcin ................ 279
El bloque try . . . . .. 280
Rutinas de tratamiento de excepciones ......... 280
Creacin de nuestras propias excepciones .. 281
Excepciones y registro. . . . . . . . . . . . . . . . . .. 283
La especificacin de la excepcin .... . .. .... 286
Cmo capturar una excepcin .. . . . ..... 286
La traza de la pila .. 288
Regeneracin de una excepcin ....... . .. .... 289
Encadenamiento de excepciones ... .... . ..... 291
Excepciones estndar de Java .............. 294
Caso especial: RuntimeException .. . . . . . ..... 294
Realizacin de tareas de limpieza con finally .. 295
Para qu sirve finally? . . . . . . . . . . . . . .. 296
Utilizacin de finaU)' durante la ej ecucin
de la instruccin return. .. 299
Un error: la excepcin perdida .... 300
Restricciones de las excepciones ............ 301
Constructores ................... .. . ..303
Localizacin de excepciones
Enfoques alternativos.
............. 307
. ............. 308
Historia ..
Perspectivas ... . ......... .
Paso de las excepciones a la consola
.309
.310
... 312
Conversin de las excepciones comprobadas
en no comprobadas ...... 3 13
Directrices relativas a las excepciones . 315
Resumen ............................... 315
13 Cadenas de caracteres ... ............ ... 317
Cadenas de caracteres inmutables . . . . . . . . . 317
Comparacin entre la sobrecarga de '+' y
StringBuilder . . . . . . . . . . . . . . . . . 318
Recursin no intencionada .... ........ ..... 32 1
Operaciones con cadenas de caracteres ....... 322
Formateo de la salida ....... . . ... 324
printfO . . . . . ... .. . ..... 324
System.out.format( ) .... .. . . .... ... . . ... . . 324
La clase Formatter .....
Especificadores de formato ..
. . . . . .... ..325
. ........ 326
Conversiones posibles con Formattcr . . 327
String.format(). . . . . . . . . . . 329
Expresiones regulares ........ ..... 331
Fundamentos bsicos. . . . . . . . . . . . . 33 1
Creacin de expresiones regulares ... .... ...... 333
Cuantificadores
Pattcrn y Matcher
split().
Operaciones de sustitucin
reset( ) .
. 335
. ... . 336
. 342
. 343
. .. 344
Expresiones regulares y E/S en Java .... 345
Anlisis de la entrada . . ............. 346
Delimitadores de Scanner .348
Anlisis con expresiones regulares. . ..... 348
StringTokenizer. ............. . .... 349
Resumen. . ..... .. . . . ....... . . .. .... 350
14 Informacin de tipos . ........... ... . ... 351
La necesidad de RTTI. ........ . .
El objeto Class ....
. ... 35 1
.353
Literales de clase ........ . .357
Referencias de clase genricas .. 359
Nueva sintaxis de proyeccin ................ 361
Comprobacin antes de una proyeccin. .361
Utilizacin de literales de clase
Instanceof dinmico ..
. 367
..... 368
Recuento recursivo. . . . . . . . . . . . . . . . . . . 369
Factoras registradas . . . . . ..... ........ 371
instanceof y equivalencia de clases .......... 373
Reflexin: infonnacin de clases en tiempo de
ejecucin ............................ 374
Un extractor de mtodos de clases . 375
Proxies dinmicos. . . . . . . . 378
Objetos nulos. . . . . . . . . . . . . . . . . . . . . . . . .. 381
Objetos maqueta y stubs ..
Interfaces e informacin de tipos
Resumen.
.387
.. 387
. .. .... 392
15 Genricos ..... .. ..................... 393
Comparacin con c++ .................... 394
Genricos simples ...... .... . . . . ....
Una biblioteca de tuplas
.394
.396
Una clase que implementa una piJa ..... 398
.. 399
.......... 399
RandornList ...
lnterfaces genricas
Mtodos genricos .. ... ........... 403
Aprovechamiento de la inferencia del argumento
de tipo ...... 404
Varargs y mtodos genricos. . .... 405
Un mtodo genrico para utilizar con
generadores . ............. 406
Un generador de propsito general .
Simplificacin del uso de las tuplas
. .... 407
..... 408
Una utilidad Set .... ... ............. 409
Clases internas annimas . . . . ......... 412
Construccin de modelos complejos ......... 413
El mi sterio del borrado ............. 415
La tcnica usada en C++ . . .. ............ 417
Compatibi lidad de la migracin. .419
.419 El problema del borrado de tipos . .... . . . . .
El efecto de los lmites
Compensacin del borrado de tipos
Creacin dc instancias de tipos.
.... 42 1
.... 424
..425
Matrices de genricos . . . . . . . . . . . . . . . . .. 427
... . ......................
Comodines ........... .. ..... ..... 434
Hasta qu punto es inteligente el compilador? .. 436
Contravarianza ....
Comodines no limitados
Conversin de captura.
Problemas .....
............... 438
.. 440
. .... 444
. ... 445
No pueden usarse primitivas corno parmetros
de tipo. . . . .. 445
Implementacin de interfaces parametrizadas . ... 447
Proyecciones de tipos y advertencias ..... 447
Sobrecarga. . .. 449
Secuestro de una interfaz por parte de la clase
base
Tipos autolimitados ........ .
. 449
.. ......... 450
Gcnricos curiosamente recurrentes .. ..... 450
.... 451 Autolimitacin
Covarianza de argumentos. . .. 453
Seguridad dinmica de los tipos ... . . ....... 456
Excepciones
Mixins ........... .
Mixins en C++.
.. 457
.. 458
............. 459
Mezclado de clases utilizando interfaces.. . .... 460
Contenido xiii
Utilizacin del patrn Decorador ..
Mixins con proxies dinmicos.
. .... 461
. .462
Tipos latentes . .......................... 464
Compensacin de la carencia de tipos latentes .. 467
Reflexin . . . . . . . . . . . . . 467
Aplicacin de un mtodo a una secuencia .. . .. 468
Qu pasa cuando no disponemos de la intcrfaz
correcta? . . . . . . . . . . . . 471
Simulacin de tipos latentes mediante adaptadores472
Utilizando los objetos de funcin como
estrategias .................. 474
Resumen: realmente es tan malo el
mecanismo de proyeccin? ..... . ..479
Lecturas adicionales .. . . . . . . . . . . . . 481
16 Matrices ...... .. .. .................. . . 483
Por qu las matrices son especiales . . 483
Las matrices son objetos de primera clase .... 484
Devolucin de una matriz ... . .... 487
Matrices multidimensionales .. 488
Matrices y genricos .......... .... .... 491
Creacin de datos de prueba .......... 493
Array,.fillO . . . . . . . . . . . . . . . ... 493
Generadores de datos ............. 494
Creacin de matrices a parti r de generadores . ... 498
Utilidades para matrices . . .. 502
Copia en una matriz
Comparacin de matrices . ....... .
Comparaciones de elementos de matriz
Ordenacin de una matriz
Bsquedas en una matriz ordenada
Resumen .................... .
. ... 502
. . .. ... 503
... 504
.. 507
.. 508
....... 509
17 Anlisis detallado de los contenedores . ... 513
Taxonoma completa de los contenedores ..... 513
Rclleno de contenedores. . . 514
Una solucin basada en generador .... ... ..... 515
Generadores de mapas .
Utilizacin de clases abstractas
. . 516
... 519
Funcionalidad de las colecciones .. .. . . ..... 525
Operaciones opcionales ............. .. .... 528
Operaciones no soportadas . ...... .. ..... 529
Funcionalidad de List .... . ......... 530
Conjuntos y orden de . . . .. . 533
SortedSet. . . . . . . . . . . 536
Colas ... ............ . ..... . . . . .... 537
xiv Piensa en Java
Colas con prioridad. . . . . .. . . . . .... 538
Colas dobles. ....... . .. .. ........ . 539
Mapas ...... .. ..... . ..... . . . ........... 540
Rendimiento
SortedMap . .
. 541
.544
LinkedHashMap. . .... 545
Almacenamiento y cdigos hash ..... . . .. . . 545
Funcionamiento de hashCode( ) .. 548
Mejora de la velocidad con el almacenamiento
hash . . . . . . . . . . . . . . . . . .. 550
Sustitucin de hashCodeQ ......... .
Seleccin de una implementacin
.... . 553
.... 558
Un marco de trabajo para pruebas de rendimiento 558
Seleccin entre listas ....................... 561
Peligros asociados a las micropruebas de
rendimiento .... ...... . . .
Seleccin de un tipo de conjunto
Seleccin de un tipo de mapa
.566
... 567
.. ... 569
Utilidades ....................... 572
Ordenaciones y bsquedas en las listas
Creacin de colecciones o mapas no
modificables
.. 575
.... 576
Sincronizacin de una coleccin o un mapa .... 577
Almacenamiento de referencias. . . . . . . 578
WeakHashMap. . . . .. . . . . .. . . . . 580
Contenedores Java 1.0/ 1.1 ...... .. . ........ 581
Vector y Enumeration . . . . . . . . . . . . . . . .... 58 1
Hashtable . . . . .. ..... . ............. . ... 582
Stack . . .. ... . .. . . . ... 582
BitSet. . . . . . . . .. . . . . . . . . . . . . ..584
Resumen. . . . . ........ . . . .. . ..... 585
18 Entrada/salida . ....... . ................ 587
La clase File ...... .. ...... . ... 587
Una utilidad para listados de directorio. . ... 587
Utilidades de directorio .......... . . ... 590
Bsqueda y creacin de directorios ..... 594
Entrada y salida ...................... 596
Tipos de InputStream ..... .. ... . . . . . . , .... . 597
Tipos de OutputStream ..................... 598
Adicin de atributos e interfaces til es . 598
Lectura de un flujo InputStream con
FiltcrInputStream .
Escritura de un flujo OutputStream con
. . 599
FilterOutputStream ............. . ...... 599
Lectores y escritores ....... . .. , . . ........ 600
Orgenes y destinos de los datos . . . 601
Modificacin del comportamiento de los flujos
dc datos. . . . . . . . . . . . . ... 601
Clases no modificadas .... 602
RandomAccessFile . . . .. . ............... 602
Utilizacin tpica de los flujos de E/ S . ... 603
Archivo de entrada con buffer . . .. .. .... 603
Entrada desde memoria. . .......... .. .... 604
Entrada de memoria formateada .... .. .. . ..... 604
Salida bsica a archivo ..... . .... . .... . .... 605
Almacenamiento y recuperacin de datos ...... 607
Lectura y escritura de archivos de acceso
aleatorio .. .. 608
Flujos de datos canalizados. . ....... .. .... 609
Utilidades de lectura y escritura de archivos . . . 609
Lectura de archivos binarios . . . . . . . . . ..... 612
E/S estndar ..................... . ...... 6 12
Lectura de la entrada estndar ............... 613
Cambio de System.out a un objeto PrintWriter . .. 613
Redireccionamiento de la E/S estndar ... 613
Control de procesos . . . . . . . . . . . . . . 614
Los paquetes new ...... ...... .... ...... 616
Conversin de datos . 618
Extraccin de primitivas ..
Buffers utilizados como vistas
..... . . ........ 62 1
... 622
Manipulacin de datos con buffers .... . . . .. 625
... 625 Detalles acerca de los buffers
Archivos mapeados en memoria. . .. 629
Bloqueo de archivos. . . 632
Compresin .................... . . . . . ... 634
Compresin simple con GZIP .. . . 635
Almacenamiento de mltiples archivos con Zip . . 635
Archivos Java (lAR) . . . ................ 637
Serializacin de objetos .. . . .... ... 639
Localizacin de la clase
Control en la serializacin ..
..... 642
.642
Utilizacin de la persistencia ...... ..... . . . . .. 649
XML .............................. .. .. 654
Preferencias .... . . . .......... . . .. . ....... 656
Resumen . . ......... .. .............. . 658
19 Tipos enumerados .......... . .... . ..... 659
Caractersticas bsicas de las enumeraciones .. 659
Utilizacin de importaciones estticas con las
enumeraciones ..... .. . ... ... 660
Adicin de mtodos a lma enumeracin ...... 661
Sustitucin de los mtodos de una enumeracin . 662
Enumeraciones en las instrucciones switch ... 662
El misterio de values( ) . . . . . . . . .. 663
Implementa, no hereda ..... o o o o o o o 665
Seleccin aleatoria ... . ......... . , . .. 666
Utilizacin de interfaces con propsitos de
organizacin .. ... . . o 667
Utilizacin de EnurnSet en lugar de
indicadores ........ o o o o o o 671
Uti lizacin de EnurnMap ........ 672
yltodos especficos de constante ............ 673
Cadena de responsabilidad en las enumeraciones 676
Mquinas de estado con enumeraciones. . . 680
Despacho ml tiple .. . ................... 684
Cmo despachar con enumeraciones ........... 686
Utilizacin de mwdos especficos de constante. 688
Cmo despachar con mapas EnurnMap.
Utilizacin de una matriz 2-D
Reswnen.
..690
.690
.... 691
20 Anotaciones o o o o o o o o o o o o o o o o o o o o o o o o o o 693
Sintaxis bsici:t ... 694
Definicin de anotaciones. . . .. 694
Meta-anotaciones ........ , ... 695
Escritura de procesadores de anotaciones , .... 696
Elementos de anotacin. . . 697
. . . 697
... ...... 697
Restricciones de valor predeterminado.
Generacin de archivos externos
Soluciones alternativas
Las anotaciones no soportan la herencia
Implementacin del procesador
.. 700
.... . . 700
Uti lizacin de apt para procesar anotaciones
. 700
.. 703
Uti lizacin del patrn de diseo Visitante
con apt ..... , , , , , , , , .. ........ . ... . . 706
Pruebas unitarias basadas en anotaciones . , . 709
Utilizacin de @Unit con genricos .. 716
No hace falta ningn "agrupamiento" .. 717
Implementacin de@Unit ,., ...... . . . . . .... 717
El iminacin del cdigo de prueba . . 723
Resumen. . . . . . . . . . .. , o 724
21 Concurrencia o o o o o o o o o o o o o o o o o o o o o o o o o o 727
Las mltiples caras de la concurrencia .. , , . , , . 728
Ejecucin ms rpida . . . ....... . 728
Contenido xv
Mejora del diseo del cdigo .. ..... ,.. . . 730
Conceptos bsicos sobre hebras .. , , . o o 731
Definicin de las tarcas . . ....... . .. .... 731
La clase Thread .732
Utilizacin de Executor .... . 734
Produccin de valores de retomo de las tareas . . 736
Cmo donnir una tarea . , ..... .... . . . ... . 737
Prioridad.
Cesin del control .
Hebras demonio.
Variaciones de cdigo
Terminologa .
Absorcin de una hebra
Creacin de interfaces de usuario de
. 738
. .. 740
. , .. 740
.. 744
. .. , 748
,748
rcspuesta rpida
Grupos dc hebras.
. . . .. ......... 750
Captura dc excepciones ... . ... .. . .
Comparticin de recursos
, 751
. 751
.753
Acceso inapropiado a los recursos .... 754
Resolucin de la contienda por los recursos
compartidos.. ................... . . 756
Atomicidad y volatilidad. . . .. . 760
Clases atmicas . ..
Secciones crticas
Sincronizacin sobre otros objetos.
Almacenamiento local de las hebras
Terminacin de tareas
El jardn ornamental
Tcnuinacin durantc el bloqueo
. . ... 764
, , 765
. .. 770
... 77 1
0 772
.. 772
.. 775
Interrupcin. . ......... , ... .. . 776
Comprobacin de la existencia de una
interrupcin .
Cooperacin entre tareas
waitO y notifyAIIO ... , ... . o o ,
notifyO y notifyAIIO,
Productores y consumidores.
Productores-consumidores y colas
Utilizacin de canalizaciones para la E/S
... 782
.784
.. , 784
.. ,788
. .... 791
.796
entre tareas . . . . . . . . . . . . . . . . . . . . . . . . . , 800
Interbloqueo . . . . . . , ..... o 801
Nuevos componentes de bibli oteca
CountDownLatch ..
CyclicBarrier
DelayQueue.
,805
.. 805
.... .. . 807
.... 809
PriorityBlockingQueue . . ,.,., . 0 ,., ... . . 811
xvi Pi ensa en Java
El controlador de invernadero implementado
con ScbeduledExecutor . ........... ..... 814
Semaphore .......... . .. .. .. ..... 817
Exchanger ...... .. . . . . . . .. ..... . . .. ...... 820
Simulacin . . . . . . . . . . .. .. . . . . ... .. 82 1
Simulacin de un cajero
Simulacin de un restaurante.
Di stribucin de trabajo . ...
Optimizacin del rendimiento
.... 82 1
...826
. .... ... . . ..... 829
.............. 834
Comparacin de las tecnologas mutex .. 834
Contenedores libres de bloqueos .. . .... 841
Bloqueo optimista. . . . . . . . .. .847
ReadWriteLocks . . . . . ................ 848
Objetos activos ................. . ...... 850
Resumen ..................... . . . ...... 853
Lecturas adicionales . ...... . . . .. . ..... 855
22 Interfaces grficasde usuario ... .. . . . . ... 857
Applets . . ........ .................. ..... 858
Fundamentos de Swing ..... ... . .......... 859
Un entorno de visuali zacin . .. .. ....... 86 1
Definicin de un botn ............ ..... 862
Captura de un suceso ............... . ..... 862
reas de texto ....... .... ...... . ...... 864
Control de la disposicin ................. 865
BorderLayout . ............. .. ............ 866
"lowLayout. .. .. . .. .. . .. . . . .. .. .. .. .. . . 867
GridLayout . .
Grid.BagLayout. ..
Posicionamiento absoluto
BoxLayout. ........ .
.867
.. . .... .. .... ..... 868
........ .. .... ... 868
. ... .. . . . . . ..... 868
Cul es la mejor solucin? . ....... ... ..... 868
........... 868 El modelo de sucesos de Swing
Tipos de sucesos y de escuchas
Control de mltiples sucesos .....
Una seleccin de componentes Swing
Botones.
... 869
.874
....... 876
. .... 876
Iconos . ........ . . . . . . . . . .. . . .. . . . . . .878
.880
.880
Sugerencias
Campos de texto
Bordes
Un mini-editor.
Casill as de verificacin
.881
.... 882
... 883
Botones de opcin. . . . . ...... 884
Cuadros combi nados (listas desplegables) ...... 885
Cuadros de lista. . ..... 886
Tableros con fichas . . . . . . . . . . . . . . . . . .. . 887
Recuadros de mensaje . ... . ............ ..... 888
Mens. . . .... .. . . . ........ . . . 890
Mens emergentes.
Dibujo ..... .
Cuadros de dilogo ...
Cuadros de dil ogo de archivos
HTML en los componentes Swing
Desli zadores y barras de progreso
894
..895
. .. .... .... 898
.... 901
.... 902
..... 903
Seleccin del aspecto y del estilo . 904
rboles, tablas y portapapeles . . . . . ... . . .. 906
JNLP Y Java Web Start .................... 906
Concurrencia y Swi ng . ....... . . . . . . .. . .... 910
Tareas de larga duracin .... . ....... .. ...... 910
Hebras visuales. . . . ............... 916
Programacin visual y componentes JavaBean . 918
Qu es un componenle JavaBean? . .... . .. .. . 919
Extraccin de la informacin Beanlnfo con
lntrospector ...
Una Bean ms sofisti cada . ...
Sincronizacin en JavaBeans
Empaquetado de una Bean . ......... .
...920
.. ... .. 924
. . . . 927
. ... 930
Soporte avanzado de componentes Bean . .... . . 931
Ms informacin sobre componentes Bean. . .932
Alternativas a Swing . ................. .... 932
Construccin de clientes web Flash con Flex .. 932
Helio, Flex ............................... 932
Compilacin de MXML. . . . . ..... 933
MXML y ActionScript . .................... . 934
Contenedores y controles. . . . . ..... . 935
Efectos y estil os
Sucesos . .
Conexin con Java.
.936
. . ...... ... . 937
........... 937
Modelos de datos y acoplamiento de datos .. . . . . 939
Construccin e implantacin de aplicaciones . ... 940
Creacin de aplicaciones SWT ........ .941
Instalacin de SWT . . . . . . . . . . . . . . . . . . . . . 941
Helio, SWT .. .. .... . .. .. 94 1
Eliminacin del cdigo redundante. . . . . 944
Mens . ................. ....... ....... ... 945
Paneles con fichas, botones y sucesos events . ... 946
Grficos . ............ . . ........ .. 949
Concurrencia en SWT ... .. 950
SWT o Swing? . . . . . . .. . . .. . . . . ......... 952
Resumen ......... . . ... . . . . . . . . . ... . .... 952
Recursos .... . .. ... . . . . .. ... .. . . ... . . ... . 953
A Suplementos ..................... . .... 955
Suplementos descargables ......... .
Thinking in C: fundamentos para Java ..
.... 955
. . . . 955
Seminario ThinJ..ing in Java . . . . . . . . . . . . . . 955
Seminario CD Hallds-On Java .... 956
Seminario Thinking in Objects ........ .... 956
Thinking in Entelprise Java . ....... . . .. ... . 956
Thinking in Patlerns (con Java) ............. 957
Seminario Thinking in Patterns
Consultora y revisin de di seo
... 957
.... 957
Contenido xvii
B Recursos ...... .. .......... . .... .... .. 959
Software ............. .... ... ... .
Editores y entornos !DE ..... . ... ... .
Libros ........... .
Anlisis y diseo . ..
Python . ........ .
... 959
... 959
... 959
.. ... 960
.. .. 962
Mi propia lista de libros . . . . . . . . . ... 962
Indice ............ .. .. ... . . . .... . .... . 963
Prefacio
Originalmente, me enfrent a Java como si fuera "simplemente otro lenguaje ms de programacin", lo cual es cierto en
muchos sentidos.
Pero, a medida que fue pasando el tiempo y lo fui estudiando con mayor detalle, comenc a ver que el objetivo fundamen-
tal de este lenguaje era distinto de los dems lenguajes que haba visto hasta el momento.
La tarea de programacin se basa en gestionar la complejidad: la complejidad del problema que se quiere resolver, sumada
a la complej idad de la mquina en la cual hay que resolverlo. Debido a esta complejidad, la mayora de los proyectos de
programacin terminan fallando. A pesar de lo cual, de todos los lenguajes de programacin que conozco, casi ninguno de
ellos haba adoptado como principal objetivo de di seo resolver la complej idad inherente al desarrollo y el mantenimiento
de los programas.
1
Por supuesto, muchas decisiones del diseo de lenguajes se realizan teniendo presente esa complejidad,
pero siempre termina por considerarse esencial introducir otros problemas dentro del conj unto de los objetivos.
Inevitablemente, son estos otros problemas los que hacen que los programadores terminen no pudiendo cumplir el objetivo
principalmente con esos lenguajes. Por ejemplo, C++ tena que ser compatible en sentido descendente con C (para permitir
una fcil migracin a los programadores de C), adems de ser un lenguaje eficiente. Ambos objetivos resultan muy tiles y
explican parte del xito de C++, pero tambin aaden un grado adicional de complejidad que impide a algunos proyectos
finalizar (por supuesto, podemos echar la culpa a los programadores y a los gestores, pero si un lenguaje puede servir de
ayuda detectando los errores que cometemos, por qu no utilizar esa posibilidad?). Otro ejemplo, Visual BASIC (VB) esta-
ba ligado a BASIC, que no haba sido diseado para ser un lenguaje extensible, por lo que todas las extensiones aadidas a
VB han dado como resultado una sintaxis verdaderamente inmanejable. Ped es compatible en sentido descendente con awk,
sed, grep y otras herramientas Unix a las que pretenda sustituir, y como resultado, se le acusa a menudo de generar "cdi-
go de slo escritura" (es decir, despus de pasado un tiempo se vuelve completamente ilegible). Por otro lado, C++, VB,
Ped y otros lenguajes como Smalltalk han centrado algo de esfuerzo de diseo en la cuestin de la complejidad, y como
resultado, ha tenido un gran xito a la hora de resolver ciertos tipos de problemas.
Lo que ms me ha impresionado cuando he llegado a entender el lenguaje Java es que dentro del conjunto de objetivos de
diseo establecido por Sun, parece que se hubiera decidido tratar de reducir la complejidad para el programador. Como si
quienes marcaron esos obj etivos hubieran dicho: "Tratemos de reducir el tiempo y la dificultad necesarios para generar cdi-
go robusto" . Al principio, este objetivo daba como resultado un cdigo que no se ejecutaba especialmente rpido (aunque
esto ha mejorado a lo largo del tiempo), pero desde luego ha permitido reducir considerablemente el tiempo de desarrollo,
que es inferior en un 50 por ciento o incluso ms al tiempo necesario para crear un programa en c ++ equivalente.
Simplemente por esto, ya podemos ahorrar cantidades enormes de tiempo y de dinero, pero Java no se detiene ah, sino que
trata de hacer transparentes muchas de las complejas tareas que han llegado a ser importantes en el mundo de la programa-
cin, como la utilizacin de mltiples hebras o la programacin de red, empleando para conseguir esa transparencia una
serie de caractersticas del lenguaje y de bibliotecas preprogramadas que pueden hacer que muchas tareas lleguen a resultar
sencillas. Finalmente, Java aborda algunos problemas realmente complejos: programas interplataforma, cambios de cdigo
dinmicos e incluso cuestiones de seguridad, todos los cuales representan problemas de una complejidad tal que pueden
hacer fracasar proyectos completos de programacin. Por tanto, a pesar de los problemas de prestaciones, las oportunida-
des que Java nos proporciona son inmensas, ya que puede incrementar significativamente nuestra productividad como pro-
gramadores.
Java incrementa el ancho de banda de comunicacin entre las personas en todos los sentidos: a la hora de crear los pro-
gramas, a la hora de trabajar en grupo, a la hora de construir interfaces para comunicarse con los usuarios, a la hora de
I Sin embargo, creo que el lenguaj e Python es el que ms se acerca a ese objetivo. Consulte www. Python. OI g.
xx Piensa en Java
ejecutar los programas en diferentes tipos de mquinas y a la hora de escribir con sencillez aplicaciones que se comuniquen
a travs de Internet.
En mi opinin, los resultados de la revolucin de las comunicaciones no se percibirn a partir de los efectos de desplazar
grandes cantidades de bits de un sitio a otro, sino que seremos conscientes de la verdadera revolucin a medida que veamos
cmo podemos comunicamos con los dems de manera ms sencilla, tanto en comunicaciones de persona a persona, como
en grupos repartidos por todo el mundo. Algunos sugieren que la siguiente revolucin ser la formacin de una especie de
mente global derivada de la interconexin de un nmero suficiente de personas. No s si Java llegar a ser la herramienta
que fomente dicha revolucin, pero esa posibilidad me ha hecho sentir, al menos, que estoy haciendo algo importante al tra-
tar de ensear este lenguaje.
Java SES Y SE6
Esta edicin del libro aprovecha en buena medida las mejoras realizadas al lenguaje Java en 10 que Sun originalmente deno-
min JDK 1.5 Y cambi posteriormente a JDK5 o J2SE5. Posteriormente, la empresa elimin el obsoleto "2" y cambi el
nombre a Java SE5. Muchos de los cambios en el lenguaj e Java SE5 fueron decididos para mejorar la experiencia de uso
del programador. Como veremos, los diseadores del lenguaje Java no obtuvieron un completo xito en esta tarea, pero en
general dieron pasos muy significativos en la direccin correcta.
Uno de los objetivos ms importantes de esta edicin es absorber de manera completa las mejoras introducidas por Java
SE5/6, presentarlas y emplearlas a lo largo de todo el texto. Esto quiere decir que en esta edicin se ha tomado la dura deci -
sin de hacer el texto nicamente compatible con Java SE5/6, por lo que buena parte del cdigo del libro no puede compi-
larse con las versiones anteriores de Java; el sistema de generacin de cdigo dar errores y se detendr si se intenta efectuar
esa compilacin. A pesar de todo, creo que los beneficios de este enfoque compensan el riesgo asociado a dicha decisin.
Si el lector prefiere por algn motivo las versiones anteriores de Java, se puede descargar el texto de las versiones anterio-
res de este libro (en ingls) en la direccin www.MindView.net. Por diversas razones, la edicin actual del libro no est en
formato electrnico gratuito, sino que slo pueden descargarse las ediciones anteriores.
Java SE6
La redaccin de este libro ha sido, en s misma, un proyecto de proporciones colosales y al que ha habido que dedicar much-
simo tiempo. Y antes de que el libro fuera publicado, la versin Java SE6 (cuyo nombre en clave es mustang) apareci en
versin beta. Aunque hay unos cuantos cambios de menor importancia en Java SE6 que mejoran algunos de los ejemplos
incluidos en el libro, el tratamiento de Java SE6 no ha afectado en gran medida al contenido del texto; las principales mejo-
ras de la nueva versin se centran en el anmento de la velocidad y en determinadas funcionalidades de biblioteca que caan
fuera del alcance del texto.
El cdigo incluido en el libro ha sido comprobado con una de las primeras versiones comerciales de Java SE6, por lo que
no creo que vayan a producirse cambios que afecten al contenido del texto. Si hubiera algn cambio importante a la hora de
lanzar oficialmente JavaSE6, ese cambio se ver reflejado en el cdigo fuente del libro, que puede descargarse desde
www.MindView.net.
En la portada del libro se indica que este texto es para "Java SE5/6", lo que significa "escrito para Java SE5 teniendo en
cuenta los significativos cambios que dicha versin ha introducido en el lenguaje, pero siendo el texto igualmente aplicable
a Java SE6".
La cuarta edicin
La principal satisfaccin a la hora de realizar una nueva edicin de un libro es la de poder "corregir" el texto, aplicando todo
aquello que he podido aprender desde que la ltima edicin viera la luz. A menudo, estas lecciones son derivadas de esa
frase que dice: "Aprender es aquello que conseguimos cuando no conseguimos lo que queremos", y escribir una nueva edi-
cin del libro constituye siempre una oportunidad de corregir errores o hacer ms amena la lectura. Asimismo, a la hora de
abordar una nueva edicin vienen a la mente nuevas ideas fascinantes y la posibilidad de cometer nuevos errores se ve ms
que compensada por el placer de descubrir nuevas cosas y la capacidad de expresar las ideas de una forma ms adecuada.
Prefacio xxi
Asimismo, siempre se tiene presente, en el fondo de la mente, ese desafio de escribir un libro que los poseedores de las edi-
ciones anteriores estn dispuestos a comprar. Ese desafio me anima siempre a mejorar, reescribir y reorganizar todo lo que
puedo, con el fin de que el libro constituya una experiencia nueva y valiosa para los lectores ms fieles.
Cambios
El CD-ROM que se haba incluido tradicionalmente como parte del libro no ha sido incluido en esta edicin. La parte esen-
cial de dicho CD, el seminario multimedia Thinking in e (creado para MindView por Chuck Allison), est ahora disponible
como presentacin Flash descargable. El objetivo de dicho seminario consiste en preparar a aquellos que no estn lo sufi-
cientemente familiarizados con la sintaxis de C, de manera que puedan comprender mejor el material presentado en este
libro. Aunque en dos de los captulos del libro se cubre en buena medida la sintaxis a un nivel introductorio, puede que no
sean suficientes para aquellas personas que carezcan de los conocimientos previos adecuados, y la presentacin Thinking in
e trata de ayudar a dichas personas a alcanzar el nivel necesario.
El captulo dedicado a la concurrencia, que antes llevaba por ttulo "Programacin multihebra", ha sido completamente rees-
crito con el fin de adaptarlo a los cambios principales en las bibliotecas de concurrencia de Java SE5, pero sigue proporcio-
nando informacin bsica sobre las ideas fundamentales en las que la concurrencia se apoya. Sin esas ideas fundamentales,
resulta dificil comprender otras cuestiones ms complejas relacionadas con la programacin multihebra. He invertido
muchos meses en esta tarea, inmerso en ese mundo denominado "concurrencia" y el resultado final es que el captulo no
slo proporciona los fundamentos del tema sino que tambin se aventura en otros territorios ms novedosos.
Existe un nuevo captulo dedicado a cada una de las principales caractersticas nuevas del lenguaje Java SE5, y el resto de
las nuevas caractersticas han sido reflejadas en las modificaciones realizadas sobre el material existente. Debido al estudio
continuado que realizo de los patrones de diseo, tambin se han introducido en todo el libro nuevos patrones.
El libro ha sufrido una considerable reorganizacin. Buena parte de los cambios se deben a razones pedaggicas, junto con
la perfeccin de que quiz mi concepto de "captulo" necesitaba ser revisado. Adicionalmente, siempre he tendido a creer
que un tema tena que tener "la suficiente envergadura" para justificar el dedicarle un captulo. Pero luego he visto, espe-
cialmente a la hora de ensear los patrones de diseo, que las personas que asistan a los seminarios obtenan mejores resul-
tados si se presentaba un nico patrn y a continuacin se haca, inmediatamente, un ejercicio, incluso si eso significaba que
yo slo hablara durante un breve perodo de tiempo (asimismo, descubr que esta nueva estructura era ms agradable para
el profesor). Por tanto, en esta versin del libro he tratado de descomponer los captulos segn los temas, sin preocuparme
de la longitud final de cada captulo. Creo que el resultado representa una autntica mejora.
Tambin he llegado a comprender la enorme importancia que tiene el tema de las pruebas de cdigo. Sin un marco de prue-
bas predefinido, con una serie de pruebas que se ejecuten cada vez que se construya el sistema, no hay forma de saber si el
cdigo es fiable o no. Para conseguir este objetivo en el libro, he creado un marco de pruebas que permite mostrar y vali-
dar la salida de cada programa (dicho marco est escrito en Python, y puede descargarse en www.MindVew.net. El tema de
las pruebas, en general, se trata en el suplemento disponible en http://www.MindVew.net/Books/BetterJava. que presenta lo
que creo que son capacidades fundamentales que todos los programadores deberan tener como parte de sus conocimientos
bsicos.
Adems, he repasado cada uno de los ejemplos del libro preguntndome a m mismo: "Por qu lo hice de esta manera?".
En la mayora de los casos, he realizado algunas modificaciones y mejoras, tanto para hacer los ejemplos ms coherentes
entre s, como para demostrar lo que considero que son las reglas prcticas de programacin en Java, (al menos dentro de
los lmites de un texto introductorio). Para muchos de los ejemplos existentes, se ha realizado un rediseo y una nueva
implementacin con cambios significativos con respecto a las versiones anteriores. Aquellos ejemplos que me pareca que
ya no tenan sentido han sido eliminados y se han aadido, asimismo, nuevos ejemplos.
Los lectores de las ediciones anteriores han hecho numerossimos comentarios muy pertinentes, lo que me llena de satisfac-
cin. Sin embargo, de vez en cuando tambin me llegan algunas quejas y, por alguna razn, tilla de las ms frecuentes es
que "este libro es demasiado voluminoso". En mi opinin, si la nica queja es que este libro tiene "demasiadas pginas",
creo que el resultado global es satisfactorio (se me viene a la mente el comentario de aquel emperador de Austria que se
quejaba de la obra de Mozart diciendo que tena "demasiadas notas"; por supuesto, no trato en absoluto de compararme con
Mozart). Adems, debo suponer que ese tipo de quejas proceden de personas que todava no han llegado a familiarizarse
con la enorme variedad de caractersticas del propio lenguaje Java y que no han tenido ocasin de consultar el resto de libros
dedicados a este tema. De todos modos, una de las cosas que he tratado de hacer en esta edicin es recortar aquellas partes
xxii Piensa en Java
que han ll egado a ser obsoletas, o al menos, no esenciales. En general, se ha repasado todo el texto eliminando lo que ya
haba dejado de ser necesario, incluyendo los cambios pertinentes y mejorando el contenido de la mejor manera posible. No
me importa demasiado eliminar algunas partes, porque el material ori ginal cOlTespondiente contina estando en el sitio web
(www.MindVew.net).graciasa laversindescargabledelastresprimerasedicionesdel libro.Asimismo. el lector tiene a su
disposicin material adicional en supl ementos descargables de esta edicin.
En cualqui er caso, para aquellos lectores que sigan considerando excesivo el tamao del libro les pido disculpas. Lo crean
o no, he hecho cuanto estaba en mi mano para que ese tamao fuera el menor posible.
Sobre el diseo de la cubierta
La cubierta del libro est inspirada por el movimiento American Arts & Crafls Movement que comenz poco antes del cam-
bio de siglo y alcanz su cenit entre 1900 y 1920. Comenz en Inglaterra como reaccin a la produccin en masa de la revo-
lucin industrial y al estil o altamente ornamental de la poca victoriana. Arts & Crafls enfatizaba el di seo con formas
naturales, como en el movimiento art nOllveau, como el trabajo manual y la importancia del artesano, sin por ello renunciar
al uso de herramientas modernas. Existen muchos ecos con la situacin que vivimos hoy en da: el cambio de siglo, la evo-
lucin desde los rudimentarios comienzos de la revolucin informtica hacia algo ms refinado y significativo y el nfasis
en la artesana del software en lugar de en su simple manufactura.
La concepcin de Java tiene mucho que ver con este punto de vista. Es un intento de elevar al programador por encima del
sistema operativo, para transformarlo en un "artesano del software".
Tanto el autor de este libro como el diseador de la cubierta (que son amigos desde la infancia) han encontrado inspiracin
en este movimiento, ambos poseemos muebles, lmparas y otros objetos originales de este perodo o inspirados en el mismo.
El otro tema de la cubierta sugiere una vitrina coleccionista que un naturalista podra emplear para mostrar los especrnenes
de insectos que ha preservado. Estos insectos son objetos situados dentro de los objetos compartimento. Los objetos com-
partimento estn a su vez, colocados dentro del "objeto cubierta", lo que ilustra el concepto de agregacin dentro de la pro-
gramacin orientada a objetos. Por supuesto, cualquier programador de habla ingl esa efectuar enseguida entre los insectos
"bugs" y los errores de programacin (tambin bugs). Aqu , esos insectos/errores han sido capturados y presumiblemente
muertos en un tarro y confinados fmalmente dentro de una vitrina, con lo que tratamos de sugerir la habi lidad que Java tiene
para encontrar, mostrar y corregir los errores (habilidad que constituye uno de sus ms potentes atributos).
En esta edicin, yo me he encargado de la acuarela que puede verse como fondo de la cubierta.
Agradecimientos
En primer lugar, gracias a todos los colegas que han trabajo conmigo a la hora de impartir seminarios, realizar labores de
consultora y desarrollar proyectos pedaggicos: Dave Bartlett, Bill Venners, Chuck AIli son, Jeremy Meyer y Jamie King.
Agradezco la paciencia que mostris mientras contino tratando de desarrollar el mejor modelo para que una serie de per-
sonas independientes como nosotros puedan continuar trabajando juntos.
Recientemente, y gracias sin duda a Internet, he tenido la oportunidad de relacionarme con un nmero sorprendentemente
grande de personas que me ayudan en mi trabajo, usualmente trabajando desde sus propias oficinas. En el pasado, yo ten-
dra que haber adquirido o alquilado una gran oficina para que todas estas personas pudieran trabajar, pero gracias a Internet,
a los servicios de mensajeros y al telfono, ahora puedo contar con su ayuda si n esos costes adicionales. Dentro de mis inten-
tos por aprender a "trabajar eficazmente con los dems", todos vosotros me habis ayudado enormemente y espero poder
continuar aprendiendo a mejorar mi trabajo gracias a los esfuerzos de otros. La ayuda de Paula Steuer ha sido valiossima a
la hora de tomar mis poco inteligentes prcticas empresariales y transformarlas en algo razonable (gracias por ayudarme
cuando no quiero encargarme de algo concreto, Paula). Jonathan Wilcox, Esq. , se encarg de revisar la estructura de mi
empresa y de eliminar cualquier piedra que pudiera tener un posible escorpin, hacindonos marchar disciplinadamente a
travs del proceso de poner todo en orden desde el punto de vista legal, gracias por tu minuciosi dad y tu persistencia.
Sharlynn Cobaugh ha llegado a convertirse en una autntica experta en edicin de sonido y ha sido una de las personas esen-
ciales a la hora de crear los cursos de formacin multimedia, adems de ayudar en la resolucin de muchos otros proble-
mas. Gracias por la perseverancia que has demostrado a la hora de enfrentarte con problemas informticos complejos. La
gente de Amaio en Praga tambin ha sido de gran ayuda en numerosos proyectos. Daniel Will-Harris fue mi primera fuen-
Prefacio xxiii
te de inspiracin en lo que respecta al proyecto de trabajo a travs de Internet y tambin ha sido imprescindible, por supues-
to, en todas las soluciones de diseo grfico que he desarrollado.
A lo largo de los aos, a travs de sus conferencias y seminarios, Gerald Weinberg se ha convertido en mi entrenador y men-
tor extraoficial, por lo que le estoy enormemente agradecido.
Ervin Varga ha proporcionado numerosas correcciones tcnicas para la cuarta edicin, aunque tambin hay otras personas
que han ayudado en esta tarea, con diversos captulos y ej emplos. Ervin ha sido el revisor tcnico principal del libro y tam-
bin se encarg de escribir la gua de soluciones para la cuarta edicin. Los errores detectados por Ervin y las mejoras que
l ha introducido en el libro han permitido redondear el texto. Su minuciosidad y su atencin al detalle resultan sorprenden-
tes y es, con mucho, el mejor revisor tcnico que he tenido. Muchas gracias, Ervin.
Mi weblog en la pgina www.Artima.com de Bill Venners tambin ha resultado de gran ayuda a la hora de verificar deter-
minadas ideas. Gracias a los lectores que me han ayudado a aclarar los conceptos enviando sus comentarios; entre esos lec-
tores debo citar a James Watson, Howard Lovatt, Michael Barker, y a muchos otros que no menciono por falta de espacio,
en particular a aquellos que me han ayudado en el tema de los genricos.
Gracias a Mark Welsh por su ayuda continuada.
Evan Cofsky contina siendo de una gran ayuda, al conocer de memoria todos los arcanos detalles relativos a la configura-
cin y mantenimiento del servidor web basados en Linux, as como a la hora de mantener optimizado y protegido el servi-
dor MindView.
Gracias especiales a mi nuevo amigo el caf, que ha permitido aumentar enormemente el entusiasmo por el proyecto. Camp4
Coffee en Crested Butte, Colorado, ha llegado a ser el lugar de reunin normal cada vez que alguien vena a los seminarios
de MindView y proporciona el mejor catering que he visto para los descansos en el seminario. Gracias a mi colega Al Smith
por crear ese caf y por convertirlo en un lugar tan extraordinario, que ayuda a hacer de Crested Butte un lugar mucho ms
interesante y placentero. Gracias tambin a todos los camareros de Camp4, que tan servicialmente atienden a sus clientes.
Gracias a la gente de Prentice Hall por seguir atendiendo a todas mis peticiones, y por facilitarme las cosas en todo momen-
to.
Hay varias herramientas que han resultado de extraordinaria utilidad durante el proceso de desarrollo y me siento en deuda
con sus creadores cada vez que las uso. Cygwin (www.cygwin.com) me ha permitido resolver innumerables problemas que
Windows no puede resolver y cada da que pasa ms me engancho a esta herrami enta (me hubiera encantado disponer de
ella hace 15 aos, cuando tena mi mente orientada a Gnu Emacs). Eclipse de IBM (www.eclipse.org) representa una mara-
villosa contribucin a la comunidad de desarrolladores y cabe esperar que se puedan obtener grandes cosas con esta herra-
mienta a medida que vaya evolucionando. JetBrains IntelliJ Idea contina abriendo nuevos y creativos caminos dentro del
campo de las herramientas de desarrollo.
Comenc a utilizar Enterprise Architect de Sparxsystems con este libro y se ha convertido rpidamente en mi herramienta
UML favorita. El formateador de cdigo Jalopy de Marco Hunsicker (www.triemax.com) tambin ha resultado muy til en
numerosas ocasiones y Marco me ha ayudado extraordinariamente a la hora de configurarlo para mis necesidades concre-
tas. En mi opinin, la herramienta JEdit de Slava Pestov y sus correspondientes plug-ins tambin resultan tiles en diversos
momentos (wwwjedit.org); esta herramienta es un editor muy adecuado para todos aquellos que se estn iniciando en el
desarrollo de seminarios.
y por supuesto, por si acaso no lo he dejado claro an, utilizo constantemente Python (www.Python.org) para resolver pro-
blemas, esta herramienta es la criatura de mi colega Guido Van Rossum y de la panda de enloquecidos genios con los que
disfrut enormemente haciendo deporte durante unos cuantos das (a Tim Peters me gustara decirle que he enmarcado ese
ratn que tom prestado, al que le he dado el nombre oficial de "TimBotMouse"). Permitidme tan slo recomendaros que
busquis otros lugares ms sanos para comer. Asimismo, mi agradecimiento a toda la comunidad Python, formada por un
conjunto de gente extraordinaria.
Son muchas las personas que me han hecho llegar sus correcciones y estoy en deuda con todas ellas, pero quiero dar las gra-
cias en particular a (por la primera edicin): Kevin Raulerson (encontr numerossimos errores imperdonables), Bob
Resendes (simplemente increble), John Pinto, Joe Dante, loe Sharp (fabulosos vuestros comentarios), David Combs (mune-
rosas correcciones de clarificacin y de gramtica), Dr. Robert Stephenson, lohn Cook, Franklin Chen, Zev Griner, David
Karr, Leander A. Strosehein, Steve Clark, Charles A. Lee, Austin Maher, Dennis P. Roth, Roque Oliveira, Douglas Dunn,
Dejan Ristic, N eil Galarneau, David B. Malkovsky, Steve Wilkinson, y muchos otros. El Profesor Mare Meurrens dedic
xxiv Piensa en Java
una gran cantidad de esfuerzo a pub licitar y difundir la versin electrnica de la primera edicin de este libro en Europa.
Gracias a todos aquellos que me han ayudado a reescribir los ejemplos para utilizar la biblioteca Swing (para la segunda
edicin), as como a los que han proporcionado otros tipos de comentarios: Jan Shvarts, Thomas Kirsch, Rahim Adatia,
Rajesh Jain, Ravi Manthena, Banu Rajamani, Jens Brandt, Nitin Shivaram, Malcolm Davis y a todos los dems que me han
manifestado su apoyo.
En la cuarta edicin, Chris Grindstaff result de gran ayuda durante el desarrollo de la seccin SWT y Sean Neville escri-
bi para m el primer borrador de la seccin dedicada a Flex.
Kraig Brockschmidt y Gen Kiyooka son algunas de esas personas inteligentes que he podido conocer en alglm momento de
vida y que han llegado a ser autnticos amigos, habiendo tenido una enorme influencia sobre m. Son personas poco usua-
les en el sentido de que practican yoga y otras formas de engrandecimiento espiritual, que me resultan particularmente ins-
piradoras e instructivas.
Me resulta sorprendente que el saber de Delphi me ayudara a comprender Java, ya que ambos lenguajes tienen en comn
muchos conceptos y decisiones relativas al diseo del lenguaj e. Mis amigos de Delphi me ayudaron enormemente a la hora
de entender mejor ese maravilloso entorno de programacin. Se trata de Marco Cantu (otro italiano, quiz el ser educado en
latn mejora las aptitudes de uno para los lenguajes de programacin), Neil Rubenking (que sola dedicarse al yoga, la comi-
da vegetariana y el Zen hasta que descubri las computadoras) y por supuesto Zack Urlocker (el jefe de producto original
de Delphi), un antiguo amigo con el que he recorrido el mundo. Todos nosotros estamos en deuda con el magnfico Anders
Hejlsberg, que contina asombrndonos con C# (lenguaje que, como veremos en el libro, fue una de las principales inspi-
raciones para Java SES).
Los consejos y el apoyo de mi amigo Richard Hale Shaw (al igual que los de Kim) me han sido de gran ayuda. Richard y
yo hemos pasado muchos meses juntos impartiendo seminarios y tratando de mejorar los aspectos pedaggicos con el fin
de que los asistentes disfrutaran de una experiencia perfecta.
El diseo del libro, el diseo de la cubierta y la fotografia de la cubierta han sido realizados por mi amigo Daniel Will-Harris,
renombrado autor y diseador (www.Will-Harris.com). que ya sola crear sus propios diseos en el colegio, mientras espe-
raba a que se inventaran las computadoras y las herramientas de autoedicin, y que ya entonces se quejaba de mi forma de
resolver los problemas de lgebra. Sin embargo, yo me he encargado de preparar para imprenta las pginas, por lo que los
errores de fotocomposicin son mos. He utilizado Microsoft Word XP para Windows a la hora de escribir el libro y de
preparar las pginas para imprenta mediante Adobe Acrobat; este libro fue impreso directamente a partir de los archivos
Acrobat PDF. Como tributo a la era electrnica yo me encontraba fuera del pas en el momento de producir la primera y la
segunda ediciones finales del libro; la primera edicin fue enviada desde Ciudad del Cabo (Sudfrica), mientras que la
segunda edicin fue enviada desde Praga. La tercera y cuarta ediciones fueron realizadas desde Crested Butte, Colorado. En
la versin en ingls del libro se utiliz el tipo de letra Ceorgia para el texto y los ttulos estn en Verdana. La letra de la
cubierta original es Te Rennie Mackintosh.
Gracias en especial a todos mis profesores y estudiantes (que tambin son mis profesores).
Mi gato Molly sola sentarse en mi regazo mientras trabajaba en esta edicin, ofrecindome as mi propio tipo de apoyo
peludo y clido.
Entre los amigos que tambin me han dado su apoyo, y a los que debo citar (aunque hay muchos otros a los que no cito por
falta de espacio), me gustara destacar a: Patty Gast (extraordinaria masaj ista), Andrew Binstock, Steve Sinofsky, JD
Hildebrandt, Tom Keffer, Brian McElhinney, BrinkJey Barr, Bill Gates en Midnight Engineering Magazine, Larry
Constantine y Lucy Lockwood, Gene Wang, Dave Mayer, David Intersimone, Chris y Laura Sttand, Jos Almquists, Brad
Jerbic, Marilyn Cvitanic, Mark Mabry, la familia Robbins, la familia Moelter (y los McMillans), Michael Wilk, Dave Stoner,
los Cranstons, Larry Fogg, Mike Sequeira, Gary Entsminger, Kevin y Sonda Donovan, Joe Lordi, Dave y Brenda Bartlett,
Patti Gast, Blake, Annette & Jade, los Rentschlers, los Sudeks, Dick, Patty, y Lee Eckel, Lynn y Todd, y sus familias. Y por
supuesto, a mam y pap.
I ntrod uccin
"El dio al hombre la capacidad de hablar, y de esa capacidad surgi el pensamiento. Que es la
medida del Universo" Prometeo desencadenado, Shelley
Los seres humanos ... eslamos, en buena medida, a merced del lenguaje concreto que nuestra
sociedad haya elegido como medio de expresin. Resulta completamente ilusorio creer que nos
ajustamos a la realidad esencialmente sin utilizar el lenguaje y que el lenguaje es meramente
un medio incidental de resolver problemas especficos de comunicacin y reflexin. Lo cierto
es que e/ "mundo real" est en gran parle construido, de manera inconsciente, sobre los hbi-
tos lingsticos del grupo.
El estado de la Lingstica como ciencia, 1929, Edward Sapir
Como cualquier lenguaje humano, Java proporciona una forma de expresar conceptos. Si tiene xito, esta forma de expre-
sin ser significativamente ms fcil y flexible que las alternati vas a medida que los problemas crecen en tamao y en com-
plejidad.
No podemos ver Java slo como una coleccin de caractersticas, ya que algunas de ellas no tienen sentido aisladas. Slo
se puede emplear la suma de las partes si se est pensando en el diseo y no simplemente en la codificacin. Y para enten-
der Java as, hay que comprender los problemas del lenguaje y de la programacin en general. Este libro se ocupa de los
problemas de la programacin, porque son problemas, y del mtodo que emplea Java para resolverlos. En consecuencia, el
conjunto de caractersticas que el autor explica en cada captulo se basa en la forma en que l ve cmo puede resolverse un
tipo de problema en particular con este lenguaje. De este modo, el autor pretende conducir, poco a poco, al lector hasta el
punto en que Java se convierta en su lengua materna.
La actitud del autor a lo largo del libro es la de conseguir que el lector construya un modelo mental que le permita desarro-
llar un conocimiento profundo del lenguaje; si se enfrenta a un puzzle, podr fijarse en el modelo para tratar de deducir la
respuesta.
Prerrequisitos
Este libro supone que el lector est familiari zado con la programacin: sabe que un programa es una coleccin de instruc-
ciones, qu es una subrutina, una funcin o una macro, conoce las instrucciones de control como "if' y las estructuras de
bucle como "while", etc. Sin embargo, es posible que el lector haya aprendido estos conceptos en muchos sitios, tales como
la programacin con un lenguaje de macros o trabajando con una herramienta como Perl. Cuando programe sintindose
cmodo con las ideas bsicas de la programacin, podr abordar este libro. Por supuesto, el libro ser ms fcil para los pro-
gramadores de C y ms todava para los de C++, pero tampoco debe autoexcluirse si no tiene experiencia con estos lengua-
jes (aunque tendr que trabajar duro). Puede descargarse en www.MindView.net el seminario muJtimedia Thinking in e, el
cual le ayudar a aprender ms rpidamente los fundamentos necesarios para estudiar Java. No obstante, en el libro se abor-
dan los conceptos de programacin orientada a objetos (POO) y los mecanismos de control bsicos de Java.
Aunque a menudo se hacen referencias a las caractersticas de los lenguajes C y C++ no es necesario profundizar en ellos,
aunque s ayudarn a todos los programadores a poner a Java en perspectiva con respecto a dichos lenguajes, de los que al
fin y al cabo desciende. Se ha intentado que estas referencias sean simples y sirvan para explicar cualquier cosa con la que
una persona que nunca haya programado en C/C++ no est familiarizado.
xxvi Piensa en Java
Aprendiendo Java
Casi al mismo tiempo que se public mi primer libro, Using C++ (Osbome/McGraw-I-lill, 1989), comenc a ensear dicho
lenguaje. Ensear lenguajes de programacin se convirti en mi profesin; desde 1987 he visto en auditorios de todo el
mundo ver dudar a los asistentes, he visto asimismo caras sorprendidas y expresiones de incredulidad. Cuando empec a
impartir cursos de formacin a grupos pequeos, descubr algo mientras se hacan ejercicios. Incluso aquellos que sonrean
se quedaban con dudas sobre muchos aspectos. Comprend al dirigir durante una serie de aos la sesin de C++ en la
Software Development Conference (y ms tarde la sesin sobre Java), que tanto yo como otros oradores tocbamos dema-
siados temas muy rpidamente. Por ello, tanto debido a la variedad en el nivel de la audiencia como a la forma de presen-
tar el material , se termina perdiendo audiencia. Quiz es pedir demasiado pero dado que soy uno de esos que se resisten a
las conferencias tradicionales (yen la mayora de los casos, creo que esa resistencia proviene del aburrimiento), quera inten-
tar algo que permitiera tener a todo el mundo enganchado.
Durante algn tiempo, cre varias presentaciones diferentes en poco tiempo, por lo que termin aprendiendo segn el mto-
do de la experimentacin e iteracin (una tcnica que tambin funciona en el diseo de programas). Desarroll un curso uti-
lizando todo lo que haba aprendido de mi experiencia en la enseanza. Mi empresa, MindView, Inc. , ahora imparte el
seminario Thinking in Java (piensa en Java); que es nuestro principal seminario de introduccin que proporciona los funda-
mentos para nuestros restantes seminarios ms avanzados. Puede encontrar informacin detallada en www.MindView.net. El
seminario de introduccin tambin est disponible en el CD-ROM Hands-On Java. La informacin se encuentra disponible
en el mismo sitio web.
La respuesta que voy obteniendo en cada seminario me ayuda a cambiar y reenfocar el material hasta que creo que funcio-
na bien como mtodo de enseanza. Pero este libro no son slo las notas del seminario; he intentado recopilar el mximo
de informacin posible en estas pginas y estructurarla de manera que cada tema lleve al siguiente. Ms que cualquier otra
cosa, el libro est diseado para servir al lector solitario que se est enfrentando a un nuevo lenguaje de programacin.
Objetivos
Como mi anterior libro, Thinking in C++, este libro se ha diseado con una idea en mente: la forma en que las personas
aprenden un lenguaje. Cuando pienso en un captulo del libro, pienso en trminos de qu hizo que fuera una leccin duran-
te un seminario. La infonnacin que me proporcionan las personas que asisten a un seminario me ayuda a comprender cu-
les son las partes complicadas que precisan una mayor explicacin. En las reas en las que fui ambicioso e inclu demasiadas
caractersticas a un mismo tiempo, pude comprobar que si inclua muchas caractersticas nuevas, tena que explicarlas yeso
contribua fcilmente a la confusin del estudiante.
En cada captulo he intentado ensear una sola caracterstica o un pequeo grupo de caractersticas asociadas, sin que sean
necesarios conceptos que todava no se hayan presentado. De esta manera, el lector puede asimilar cada pieza en el contex-
to de sus actuales conocimientos.
Mis objetivos en este libro son los siguientes:
1. Presentar el material paso a paso de modo que cada idea pueda entenderse fcilmente antes de pasar a la siguien-
te. Secuenciar cuidadosamente la presentacin de las caractersticas, de modo que se haya explicado antes de que
se vea en un ejemplo. Por supuesto, esto no siempre es posible, por lo que en dichas situaciones, se proporciona
una breve descripcin introductoria.
2. Utilizar ejemplos que sean tan simples y cortos como sea posible. Esto evita en ocasiones acometer problemas
del "mundo real", pero he descubierto que los principiantes suelen estar ms contentos cuando pueden compren-
der todos los detalles de un ejemplo que cuando se ven impresionados por el mbito del problema que resuelve.
Tambin, existe una seria limitacin en cuanto a la cantidad de cdigo que se puede absorber en el aula. Por esta
razn, no dudar en recibir crticas acerca del uso de "ejemplos de juguete", sino que estoy deseando recibirlas
en aras de lograr algo pedaggicamente til.
3. Dar lo que yo creo que es importante para que se comprenda el lenguaje, en lugar de contar todo lo que yo s_
Pienso que hay una jerarqua de importancia de la informacin y que existen hechos que el 95% de los progra-
madores nunca conocern, detalles que slo sirven para confundir a las personas y que incrementan su percep-
cin de la complejidad del lenguaje. Tomemos un ejemplo de C, si se memori za la tabla de precedencia de los
Introduccin xxvii
operadores (yo nunca lo he hecho), se puede escribir cdigo inteligente. Pero si se piensa en ello, tambin con-
fundir la lectura y el mantenimiento de dicho cdigo, por tanto, hay que olvidarse de la precedencia y emplear
parntesis cuando las cosas no estn claras.
4. Mantener cada seccin enfocada de manera que el tiempo de lectura y el tiempo entre ejerci cios, sea pequeo.
Esto no slo mantiene las mentes de los alumnos ms activas cuando se est en un seminario, sino que tambin
proporciona al lector una mayor sensacin de estar avanzando.
5. Proporcionar al alumno una base slida de modo que pueda comprender los temas los suficientemente bien como
para que desee acudir a cursos o libros ms avanzados.
Ensear con este libro
La edicin original de este libro ha evolucionado a partir de un seminario de una semana que era, cuando Java se encontra-
ba en su infancia, suficiente ti empo para cubrir el lenguaje. A medida que Java fue creciendo y aadiendo ms y ms fun-
cionalidades y bibliotecas, yo tenazmente trataba de ensearlo todo en una semana. En una ocasin, un cliente me sugiri
que enseara "slo los fundamentos" y al hacerlo descubr que tratar de memori zar todo en una nica semana era angustio-
so tanto para m como para las personas que asistan al seminario. Java ya no era un lenguaje "simple" que se poda apren-
der en una semana.
Dicha experiencia me llev a reorganizar este libro, el cual ahora est di seado como material de apoyo para un seminario
de dos semanas o un curso escolar de dos trimestres. La parte de introduccin termina con el Captulo 12, Tratamiento de
errores mediante excepciones, aunque tambin puede complementarla con una introduccin a IDBC, Servlets y JSP. Esto
proporciona las bases y es el ncl eo del CD-ROM Hands-On Java. El resto del libro se corresponde con un curso de nivel
intennedio y es el material cubi erto en el CD-ROM Intermediale Thinking in Java. Ambos di scos CD ROM pueden adqui-
rirse a travs de wl:vw.MindView.net.
Contacte con Prentice-Hall en www.prenhallprofessional. com para obtener ms informacin acerca del material para el pro-
fesor relacionado con este libro.
Documentacin del JDK en HTML
El lenguaje Java y las bibliotecas de Sun Microsystems (descarga gratuita en hllp://java. sun. com) se sumini stran con docu-
mentacin en formato electrnico, que se puede leer con un explorador web. Muchos de los libros publicados sobre Java
proporcionan esta documentacin. Por tanto, o ya se tiene o puede descargase y, a menos que sea necesario, en este libro no
se incluye dicha documentacin, porque normalmente es mucho ms rpido encontrar las descripciones de las clases en el
explorador web que buscarlas en un libro (y probablemente la documentacin en lnea estar ms actualizada). Basta con
que utilice la referencia "JDK documentation". En este libro se proporcionan descripciones adicionales de las clases slo
cuando es necesari o complementar dicha documentacin, con el fin de que se pueda comprender un determinado ejemplo.
Ejercicios
He descubierto que durante las clases los ejercicios senci llos son excepcionalmente tiles para que el alumno termine de
comprender el tema, por lo que he incluido al final de cada captulo una serie de ejercicios.
La mayor parte de los ej ercicios son bastante sencillos y estn di seados para que se puedan reali zar durante un tiempo razo-
nable de la clase, mientras el profesor observa los progresos, asegurndose de que los estudiantes aprenden el tema. Algunos
son algo ms complejos, pero ninguno presenta un reto inal canzable.
Las soluciones a los ejercicios seleccionados se pueden encontrar en el documento electrnico The Thinking in Java
Annotated So/ution Guide, que se puede adquirir en www.MindVew.net.
Fundamentos para Java
Otra ventaja que presenta esta edicin es el seminario multimedia gratuito que puede descargarse en la direccin
www.MindVew.net. Se trata del seminario Thinking in e, el cual proporciona una introduccin a los operadores, funciones
xxviii Piensa en Java
y la sintaxis de e en la que se basa la sintaxis de Java. En las ediciones anteriores del libro se encontraba en el eD
Foundations for Java que se proporcionaba junto con el libro, pero ahora este seminario puede descargarse gratuitamente.
Originalmente, encargu a ehuck Allison que creara Thinking in C como un producto autnomo, pero decid incluirlo en la
segunda edici n de Thinking in C++ y en la segunda y tercera ediciones de Thinking in Java, por la experiencia de haber
estado con personas que llegan a los seminarios sin tener una adecuada formacin en la sintaxis bsica de e. El razonamien-
to suele ser: "Soy un programador inteligente y no quiero aprender e, sino e ++ o Java, por tanto, me salto el e y paso direc-
tamente a ver el e++/Java". Despus de asistir al seminario, lentamente todo el mundo se da cuenta de que el prerrequisito
de conocer la sintaxis de e tiene sus buenas razones de ser.
Las tecnologas han cambiado y han pennitido rehacer Thinking in C como una presentacin Flash descargable en lugar de
tener que proporcionarlo en CD. Al proporcionar este seminario en linea, puedo garantizar que todo el mundo pueda comen-
zar con una adecuada preparacin.
El seminario Thinking in C tambin permite atraer hacia el libro a una audiencia importante. Incluso aunque los captulos
dedicados a operadores y al control de la ejecucin cubren las partes fundamentales de Java que proceden de C, el semina-
rio en lnea es una buena introduccin y precisa del estudiante menos conocimientos previos sobre programacin que este
libro.
Cdigo fuente
Todo el cdigo fuente de este libro est disponible gratuitamente y sometido a copyright, distribuido como un paquete nico,
visitando el sitio web www.MindView.net. Para asegurarse de que obtiene la versin ms actual, ste es el sitio oficial de dis-
tribucin del cdigo. Puede di stribuir el cdigo en las clases y en cualquier otra situacin educativa.
El objetivo principal del copyright es asegurar que el cdigo fuente se cite apropiadamente y evitar as que otros lo publi-
quen sin permiso. No obstante, mientras se cite la fuente, no constituye ningn problema en la mayora de los medios que
se empleen los ejemplos del libro.
En cada archivo de cdigo fuente se encontrar una referencia a la siguiente nota de copyright:
// ,! Copyright.txt
Thi s eomputer source eode is Copyright ~ 2 6 MindView, lnc.
All Rights Reserved .
Permission to use, eopy, mOdify, and distribute this
computer souree code (Source Code) and its documentation
without fee and without a wri tten agreement for the
purposes set forth below is hereby granted, provided that
the aboye copyright notice, this paragraph and the
following five numbered paragraphs appear in a l l copies.
l. Permiss i on is granted to compile the Souree Code and to
include the compiled code, in executable format only, in
personal and eommereial software programs .
2. Permission is granted to use the Souree Code wi thout
modification in classroom situations, including in
presentation materials, provided that the book "Thinking in
Java 11 is cited as the origino
3. Permi ssion to incorporate the Souree Code into printed
media may be obtained by contact i ng :
MindView, lne . 5343 Vall e Vist a La Mesa, California 91941
Wayne@MindView . net
4. The Bouree Code and documentation are copyrighted by
MindView, lnc. The Souree eode is provided wi thout express
or implied warranty of any kind, including any implied
warranty of merchantability, fitness ter a particular
purpose or non- infringement . MindView, lnc. does not
Introduccin xxix
warrant that the operation of any program that includes the Sauree Cede will be uninte-
rrupted or error- free . MindView,
lnc . makes no representation about the suitability of the
Bouree Cede or of any software that includes the Sauree
Cede tor any purpose. The entire risk as to the quality
and performance of any program that includes the Sauree
Cade is with the user of the Sauree Codeo The user
understands that the Sauree Cede was developed for research and instructional purposes
and is advised not to rely
exclusively for any reason on the Source Cede er any
pr ogram that includes the Source Codeo Should the Source
Cede er any resulting software prove defective, the user
as sumes the cost of all necessary servicing, repair, or
correction.
5. IN NO EVENT SHALL MINDVIEW, INC . , OR ITS PUBLI SHER BE
LI ABLE TO ANY PARTY UNDER ANY LEGAL THEORY FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
INCLUDING LOST PROFITS, BUSINESS INTERRUPTION, LOSS OF
BUSINESS INFORMATION, OR ANY OTHER PECUNIARY LOSS, OR FOR
PERSONAL I NJURIES, ARISING OUT OF THE USE OF THIS SOURCE
CODE AND ITS DOCUMENTATION, OR ARISING OUT OF THE INABI LI TY TO USE ANY RESULTING PROGRAM,
EVEN IF MINDVIEW, INC., OR
I TS PUBLISHER HAS BE EN ADVI SED OF THE POSSIBILITY OF SUCH
DAMAGE . MI NDVIEW, INC . SPECIFICALLY DISCLAIMS ANY
WARRANTIES, INCLUDI NG, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY ANO FITNESS FOR A PARTICULAR
PURPOSE. THE SOURCE CODE ANO DOCUMENTATION PROVIDED
HEREUNDER IS ON AN "AS I S" BASI S, WI THOUT ANY ACCOMPANYING
SERVICES FROM MINDVI EW, I NC . , ANO MINDVIEW, INC. HAS NO
OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
ENHANCEMENTS, OR MODI FICATIONS.
Please note that MindVi e w, lnc. maintains a Web site whi ch
is the sole distribution point f or electronic copies of the Sour ce Code,
http://www . Mi ndView.net (and official mirror
sites), where it i s freely available under the terms stated above .
lf you think you 1ve found an error in the Source Code ,
please submit a correction using the feedback system that you will find at
http : //www.MindView . net .
/// : -
Puede utilizar el cdi go en sus proyectos y en la clase (incluyendo su material de presentaciones) siempre y cuando se man-
tenga la nota de copyright en cada uno de los archi vos fuente.
Estndares de codificacin
En el texto del libro, los identifi cadores (nombres de mtodos, vari ables y clases) se escriben en negrita. La mayora de las
palabras clave se escriben en negrita, excepto aquellas palabras clave que se usan con mucha frecuencia y ponerl as en negri-
ta podra volverse tedioso, como en el caso de la palabra "e1ass".
En este libro, he utilizado un estilo de codificacin particular para los ejemplos. Este estilo sigue el que emplea Sun en prc-
ticamente todo el cdigo que encontrar en su sitio (vase http://java.sun. com/docs/codeconv/index. htmf), y que parece que
soporta la mayora de los entornos de desarrollo Java. Si ha ledo mis otros libros, observar tambin que el estilo de codi-
fi cacin de Sun coincide con el mo, lo que me complace, ya que yo no tengo nada que ver con la creacin del estil o de
xxx Piensa en Java
Sun. El tema del estilo de fonnato es bueno para conseguir horas de intenso debate, por lo que no vaya intentar dictar un
estilo correcto a travs de mis ejemplos; tengo mi s propias motivaciones para usar el estilo que uso. Dado que Java es un
lenguaj e de programacin de fonoato libre, se puede empl ear el estilo con el que uno se encuentre a gusto. Una solucin
para el tema del esti lo de codifi cacin consiste en utili zar una herramienta como Jalopy (www.triemax.com). la cual me ha
ayudado en el desarrollo de este libro a cambi ar el fonoato al que se adaptaba a m.
Los archi vos de cdigo impresos en el libro se han probado con un sistema automati zado, por lo que deberan ejecutarse sin
crrores de compi lacin.
Este libro est basado y se ha comprobado con Java SE5/6. Si necesita obtener infonnacin sobre versiones anteriores del
lenguaje que no se cubren en esta edicin, la ediciones primera y tercera del mismo pueden descargarse gratuitamente en
www.MindView.net.
Errores
No importa cuantas herrami entas uti li ce un escritor para detectar los errores, algunos quedan ah y a menudo son lo que pri -
mero ve el lector. Si descubre cualquier cosa que piensa que es un error, por favor utili ce el vnculo que encontrar para este
libro en www.MindView.net y enveme el error junto con la correccin que usted crea. Cualqui er ayuda siempre es bienve-
nida.
Introduccin
a los objetos
"Analizamos la Naturaleza, la organizamos en conceptos y vamos asignando significados a
medida que lo hacemos, fundamentalmente porque participamos en un acuerdo tcito suscrito
por toda nuestra comunidad de hablantes y que est codificado en los propios patrones de nues-
tro idioma .. . nos resulta imposible hablar si no uti li zamos la organizacin y clasificacin de los
datos decretadas por ese acuerdo".
Benjamin Lee Whorf (1 897-1941)
La gnesis de la revolucin de las computadoras se hallaba en una mquina. La gnesis de nuestros lenguajes de programa-
cin ti ende entonces a parecerse a dicha mquina.
Pero las computadoras, ms que mquinas, pueden considerarse como herramientas que permiten ampliar la mente ("bici-
cletas para la mente", como se enorgull ece en decir Steve Jobs), adems de un medio de expresin diferente. Como resul-
tado, las herrami entas empiezan a parecerse menos a mqui nas y ms a partes de nuestras mentes, al igual que ocurre con
otras formas de expresin como la escritura, la pintura, la escultura, la animacin y la realizacin de pelculas. La progra-
macin orientada a objetos (POO) es parte de este movimiento dirigido al uso de las computadoras como un medio de expre-
sin.
Este captulo presenta los conceptos bsicos de la programacin orientada a objetos, incluyendo una introduccin a los mto-
dos de desarrollo. Este captulo, y este libro, supone que el lector tiene cierta experiencia en programacin, aunque no nece-
sariamente en C. Si cree que necesita una mayor preparacin antes de abordar este libro, debera trabajar con el seminario
multimedia sobre C, Thinking in C. que puede descargarse en www.MindView.net.
Este captulo conti ene material de carcter general y suplementario. Muchas personas pueden no sentirse cmodas si se
enfrentan a la programacin orientada a objetos sin obtener primero una visin general. Por tanto, aqu se presentan muchos
conceptos que proporcionarn una slida introduccin a la PDO. Sin embargo, otras personas pueden no necesitar tener una
visin general hasta haber visto algunos de los mecani smos primero, estas personas suelen perderse si no se les ofrece algo
de cdigo que puedan manipular. Si usted forma parte de este ltimo glUpO, estar ansioso por ver las especifidades del len-
guaje, por lo que puede saltarse este captulo, esto no le impedir aprender a escribir programas ni conocer el lenguaje. Sin
embargo, podr volver aqu cuando lo necesite para completar sus conocimientos, con el fin de comprender por qu son
importantes los obj etos y cmo puede disearse con ellos.
El progreso de la abstraccin
Todos los lenguaj es de programacin proporcionan abstracciones. Puede argumentarse que la complejidad de los problemas
que sea capaz de resolver est directamente relacionada con el tipo (clase) y la calidad de las abstracciones, entendiendo por
"clase", "qu es lo que se va a abstraer?", El lenguaj e ensamblador es una pequea abstraccin de la mquina subyacente.
Muchos de los lenguajes denominados "imperativos" que le siguieron (como FORTRAN, BASIC y C) fueron abstraccio-
nes del lenguaje ensamblador. Estos lenguajes constituyen grandes mejoras sobre el lenguaje ensamblador, pero su princi-
pal abstraccin requiere que se piense en tnninos de la estructura de la computadora en lugar de en la estructura del
problema que se est intentado resolver. El programador debe establ ecer la asociacin entre el modelo de la mquina (en el
"espacio de la solucin", que es donde se va a implementar dicha solucin, como puede ser una computadora) y el modelo
2 Piensa en Java
del problema que es lo que realmente se quiere resolver (en el "espacio del problema", que es el lugar donde existe el pro-
blema, como por ejemplo en un negocio). El esfuerzo que se requiere para establecer esta correspondencia y el hecho de
que sea extrnseco al lenguaje de programacin, da lugar a programas que son dificil es de escribir y caros de mantener, ade-
ms del efecto colateral de toda una industria de "mtodos de programacin",
La alternativa a modelar la maquina es modelar el problema que se est intentado solucionar. Los primeros lenguajes como
LISP y APL eligen vistas parciales del mundo ("todos los problemas pueden reducirse a listas" o "todos los problemas son
algortmicos", respectivamente). Prolog convierte todos los problemas en cadenas de decisin. Los lenguajes se han creado
para programar basndose en restricciones y para programar de forma exclusiva manipulando smbolos grficos (aunque se
demostr que este caso era demasiado restrictivo). Cada uno de estos mtodos puede ser una buena solucin para resolver
la clase de problema concreto para el que estn diseados, pero cuando se aplican en otro dominio resultan inadecuados.
El enfoque orientado a objetos trata de ir un paso ms all proporcionando herramientas al programador para representar los
elementos en el espacio del problema. Esta representacin es tan general que el programador no est restringido a ningn
tipo de problema en particular. Se hace referencia a los elementos en el espacio del problema denominando "objetos" a sus
representaciones en el espacio de la solucin (tambin se necesitarn otros objetos que no tendrn anlogos en el espacio
del problema). La idea es que el programa pueda adaptarse por s slo a la jerga del problema aadiendo nuevos tipos de
objetos, de modo que cuando se lea el cdigo que describe la solucin, se estn leyendo palabras que tambin expresen el
problema. sta es una abstraccin del lenguaje ms flexible y potente que cualquiera de las que se hayan hecho anterior-
mente
l
. Por tanto, la programacin orientada a objetos permite describir el problema en tnninos del problema en lugar de
en trminos de la computadora en la que se ejecutar la solucin. Pero an existe una conexin con la computadora, ya que
cada objeto es similar a una pequea computadora (tiene un estado y dispone de operaciones que el programador puede
pedirle que realice). Sin embargo, esto no quiere decir que nos encontremos ante una mala analoga de los objetos del mundo
real, que tienen caractersticas y comportamientos.
Alan Kay resumi las cinco caractersticas bsicas del Smalltalk, el primer lenguaje orientado a objetos que tuvo xito y uno
de los lenguajes en los que se basa Java. Estas caractersticas representan un enfoque puro de la programacin orientada a
objetos.
1. Todo es un objeto. Piense en un objeto como en una variable: almacena datos, permite que se le "planteen soli-
citudes", piclindole que realice operaciones sobre s mismo. En teora, puede tomarse cualquier componente con-
ceptual del problema que se est intentado resolver (perros, edificios, servicios, etc.) y representarse como un
objeto del programa.
2. Un programa es un montn de objetos que se dicen entre s lo que tienen que hacer envindose mensajes.
Para hacer una solicitud a un objeto, hay que enviar un mensaje a dicho objeto. Ms concretamente, puede pen-
sar en que un mensaje es una solicitud para llamar a un mtodo que pertenece a un determinado objeto.
3. Cada objeto tiene su propia memoria formada por otros objetos. Dicho de otra manera, puede crear una nueva
clase de objeto definiendo un paquete que contenga objetos existentes. Por tanto, se puede incrementar la comple-
jidad de un programa acuitndola tras la simplicidad de los objetos.
4. Todo objeto tiene un tipo asociado. Como se dice popularmente, cada objeto es una instancia de una clase, sien-
do "clase" sinnimo de "tipo". La caracterstica distintiva ms importante de una clase es "el conjunto de men-
sajes que se le pueden enviar".
5. Todos los objetos de un tipo particular pueden recibir los mismos mensajes. Como veremos ms adelante,
esta afirmacin es realmente importante. Puesto que un objeto de tipo "crculo" tambin es un objeto de tipo
"forma", puede garantizarse que un crculo aceptar los mensajes de forma. Esto quiere decir que se puede escri-
bir cdigo para comunicarse con objetos de tipo forma y controlar automticamente cual quier cosa que se ajuste
a la descripcin de una forma. Esta capacidad de suplantacin es uno de los conceptos ms importantes de la pro-
gramacin orientada a objetos.
Booch ofrece una descripcin an ms sucinta de objeto:
Algunos diseadores de lenguajes han decidido que la programacin ori entada a objetos por s misma no es adecuada para resolver fcilmente todos
los problemas de la programacin, y recomiendan combinar varios mtodos en lenguajes de programacin mll/liparadigma. Consulte MII/I/paradim
Programming in Leda de Timothy Budd (Addison-Wesley, 1995).
1 Introduccin a los objetos 3
Un objeto tiene estado, comportamiento e identidad.
Esto significa que un objeto puede tener datos internos (lo que le proporciona el estado), mtodos (para proporcionar un
comportamiento) y que cada objeto puede ser diferenciado de fanna unvoca de cualquier otro objeto; es decir, cada objeto
tiene una direccin de memoria exclusiva.
2
Todo objeto tiene una interfaz
Aristteles fue probablemente el primero en estudiar cuidadosamente el concepto de tipo; hablaba de "la clase de peces y
de la clase de pjaros". La idea de que todos los objetos, an siendo nicos, son tambin parte de una clase de objetos que
tienen caractersticas y comportamientos comunes ya se emple en el primer lenguaj e orientado a objetos, el Simula-67, que
ya usaba su palabra clave fundamental class, que permite introducir un nuevo tipo en un programa.
Simula, como su nombre implica, se cre para desarrollar simulaciones como la clsica del "problema del cajero de un
banco". En esta simulacin, se tienen muchos cajeros, clientes, cuentas, transacciones y unidades monetarias, muchsimos
"'objetos". Los objetos, que son idnticos excepto por su estado durante la ejecucin de un programa, se agrupan en "clases
de objetos", que es de donde procede la palabra clave class. La creacin de tipos de datos abstractos (clases) es un concep-
to fundamental en la programacin orientada a objetos. Los tipos de datos abstractos funcionan casi exactamente como tipos
predefmidos: pueden crearse variables de un tipo (llamadas objetos u instancias en la jerga de la POOl y manipular dichas
variables (mediante el envo de mensajes o solicitudes, se enva un mensaje y el objeto sabe lo que tiene que hacer con l).
Los miembros (elementos) de cada clase comparten algunos rasgos comunes. Cada cuenta tiene asociado un saldo, cada caje-
ro puede aceptar un depsito, etc. Adems, cada miembro tiene su propio estado. Cada cuenta tiene un saldo diferente y cada
cajero tiene un nombre. Por tanto, los cajeros, clientes, cuentas, transacciones, etc., pueden representarse mediante una enti-
dad unvoca en el programa informtico. Esta entidad es el objeto y cada objeto pertenece a una detenninada clase que defi-
ne sus caractersticas y comportamientos.
Por tanto, aunque en la programacin orientada a objetos lo que realmente se hace es crear nuevos tipos de datos, en la prc-
tica, todos los lenguajes de programacin orientada a objetos utilizan la palabra clave "class". Cuando vea la palabra "type"
(tipo) piense en "elass" (elase), y viceversa
3
Dado que una clase describe un conjunto de objetos que tienen caractersticas (elementos de datos) y comportamientos (fun-
cionalidad) idnticos, una clase realmente es un tipo de datos porque, por ej emplo, un nmero en coma flotante tambin
tiene un conjunto de caractersticas y comportamientos. La diferencia est en que el programador define un clase para adap-
tar un problema en lugar de forzar el uso de un tipo de datos existente que fue diseado para representar una unidad de alma-
cenamiento en una mquina. Se puede ampliar el lenguaje de programacin aadiendo nuevos tipos de datos especficos que
se adapten a sus necesidades. El sistema de programacin admite las nuevas clases y proporciona a todas ellas las compro-
baciones de tipo que proporciona a los tipos predefinidos.
El enfoque orientado a objetos no est limitado a la creacin de simulaciones. Se est o no de acuerdo en que cualquier pro-
grama es una simulacin del sistema que se est diseando, el uso de las tcnicas de la POO puede reducir fcil mente un
gran conjunto de problemas a una sencilla solucin.
Una vez que se ha definido una clase, se pueden crear tantos objetos de dicha clase como se desee y dichos objetos pueden
manipularse como si fueran los elementos del problema que se est intentado resolver. Realmente, uno de los retos de la
programacin orientada a objetos es crear una correspondencia uno-a-uno entre los elementos del espacio del problema y
los objetos del espacio de la solucin.
Pero, cmo se consigue que un objeto haga un trabajo til para el programador? Debe haber una [onua de hacer una soli-
citud al objeto para que haga algo, como por ejemplo, completar una transaccin, dibujar algo en pantalla o encender un
interruptor. Adems, cada objeto slo puede satisfacer ciertas solicitudes. Las solicitudes que se pueden hacer a un objeto
se definen mediante su infeljaz y es el tipo lo que determina la interfaz. Veamos un ejemplo con la representacin de una
bombilla:
2 Realmente, esto es poco restrictivo, ya que pueden existir objetos en diferentes mquinas y espacios de direcciones, y tambin sc pueden almacenar en
disco. En estos casos, debe determinarse la identidad del objeto mediante alguna otra cosa que la direccin de memoria.
3 Algunas personas hacen una distincin, estableciendo que el tipo determina la interfaz micntras que la clase es una implemcntacin concreta de dicha
interfaz.
4 Piensa en Java
Luz 1z = new Luz {) ;
1z.encender () ;
Tipo
Interfaz
Luz
encenderO
apagarO
brillarO
atenuarO
La interfaz determina las solicitudes que se pueden hacer a un determinado objeto, por lo que debe existir un cdigo en algu-
na parte que satisfaga dicha solicitud. Esto, junto con los datos ocultos, definen lo que denomina la implementacin. Desde
el punto de vista de la programacin procedimental, esto no es complicado. Un tipo tiene un mtodo asociado con cada posi-
ble solicitud; cuando se hace una determinada solicitud a un objeto, se llama a dicho mtodo. Este proceso se resume dicien-
do que el programador "enva un mensaje" (hace una solicitud) a un objeto y el objeto sabe lo que tiene que hacer con ese
mensaje (ejecuta el cdigo).
En este ejemplo, el nombre del tipo/clase es Luz, el nombre de este objeto concreto Luz es lz y las solicitudes que se pue-
den hacer a un objeto Luz son encender, apagar, brillar o atenuar. Se ha creado un objeto Luz definiendo una "referencia"
(lz) para dicho objeto e invocando new para hacer una solicitud a un nuevo objeto de dicho tipo. Para enviar un mensaje al
objeto, se define el nombre del objeto y se relaciona con la solicitud del mensaje mediante un punto. Desde el punto de vista
del usuario de una clase predefinida, esto es el no va ms de la programacin con objetos.
El diagrama anterior sigue el formato del lenguaje UML (Unified Modeling Langr/age, lenguaje de modelado unificado).
Cada clase se representa mediante un recuadro escribiendo el nombre del tipo en la parte superior, los miembros de datos
en la zona intermedia y los mtodos (las funciones de dicho objeto que reciben cualquier mensaje que el programador enVe
a dicho objeto) en la parte inferior. A menudo, en estos diagramas slo se muestran el nombre de la clase y los mtodos
pblicos, 00 incluyndose la zona iotennedia, como en este caso. Si slo se est interesado en el nombre de la clase, tam-
poco es necesario incluir la parte inferior.
Un objeto proporciona servicios
Cuando se est intentando desarrollar o comprender el diseo de un programa, una de las mejores formas de pensar en los
objetos es como si fueran "proveedores de servicios", El programa proporciona servicios al usuario y esto se conseguir uti-
lizando los servicios que ofrecen otros objetos. El objetivo es producir (o incluso mejor, localizar en las bibliotecas de cdi-
go existentes) un conjunto de objetos que facilite los servicios idneos para resolver el problema.
Una manera de empezar a hacer esto es preguntndose: "Si pudiera sacarlos de un sombrero mgico, qu objetos resolve-
ran el problema de la forma ms simple?". Por ejemplo, suponga que quiere escribir un programa de contabilidad. Puede
pensar en algunos objetos que contengan pantallas predefinidas para la introduccin de los datos contables, otro conjunto
de objetos que realicen los clculos necesarios y un objeto que controle la impresin de los cheques y las facturas en toda
clase de impresoras. Es posible que algunos de estos objetos ya existan, pero cmo deben ser los que no existen? Qu ser-
vicios deberan proporcionar esos objetos y qu objetos necesitaran para cumplir con sus obligaciones? Si se hace este plan-
teamiento, llegar a un punto donde puede decir: "Este objeto es lo suficientemente sencillo como para escribirlo yo mismo"
o "Estoy seguro de que este objeto ya tiene que existir". sta es una fonna razonable de descomponer un problema en un
conjunto de objetos.
Pensar en un objeto como en un proveedor de servicios tiene una ventaja adicional: ayuda a mejorar la cohesin del objeto.
Una alta cohesin es una cualidad fundamental del diseo software, lo que significa que los diferentes aspectos de un com-
ponente de software (tal como un objeto, aunque tambin podra aplicarse a un mtodo o a una biblioteca de objetos) deben
"ajustar bien entre s". Un problema que suelen tener los programadores cuando disean objetos es el de asignar demasia-
da funcionalidad al objeto. Por ejemplo, en el mdulo para imprimir cheques, puede decidir que es necesario un objeto que
sepa todo sobre cmo dar formato e imprimir. Probablemente, descubrir que esto es demasiado para un solo objeto y que
hay que emplear tres o ms objetos. Un objeto puede ser un catlogo de todos los posibles diseos de cheque, al cual se le
Introduccin a los objetos 5
puede consultar para obtener infonnacin sobre cmo imprimir un cheque. Otro objeto o conjunto de objetos puede ser una
interfaz de impresin genrica que sepa todo sobre las diferentes clases de impresoras (pero nada sobre contabilidad; por
ello, probablemente es un candidato para ser comprado en lugar de escribirlo uno mismo). Y un tercer objeto podra utili-
zar los servicios de los otros dos para llevar a cabo su tarea. Por tanto, cada objeto tiene un conjunto cohesivo de servicios
que ofrecer. En un buen diseo orientado a objetos, cada objeto hace una cosa bien sin intentar hacer demasiadas cosas. Esto
adems de pernlitir descubrir objetos que pueden adquirirse (el objeto interfaz de impresora), tambin genera nuevos obje-
tos que se reutilizarn en otros diseos.
Tratar los objetos como proveedores de servicios es una herramienta que simplifica mucho. No slo es til durante el pro-
ceso de diseo, sino tambin cuando alguien intenta comprender su propio cdigo o reutilizar un objeto. Si se es capaz de
ver el valor del objeto basndose en el servicio que proporciona, ser mucho ms fcil adaptarlo al diseo.
La implementacin oculta
Resulta til descomponer el campo de juego en creadores de clases (aquellos que crean nuevos tipos de datos) y en progra-
madores de clientes
4
(los consumidores de clases que emplean los tipos de datos en sus aplicaciones). El objetivo del pro-
gramador cliente es recopilar una caja de herramientas completa de clases que usar para el desarrollo rpido de aplicaciones.
El objetivo del creador de clases es construir una clase que exponga al programador cliente slo lo que es necesario y man-
tenga todo lo dems oculto. Por qu? Porque si est oculto, el programador cliente no puede acceder a ello, lo que signifi-
ca que el creador de clases puede cambiar la parte oculta a voluntad sin preocuparse del impacto que la modificacin pueda
implicar. Nonnalmente, la parte oculta representa las vulnerabilidades internas de un objeto que un programador cliente
poco cuidadoso o poco formado podra corromper fcilmente, por lo que ocultar la implementacin reduce los errores en
los programas.
En cualquier relacin es importante tener lmites que todas las partes implicadas tengan que respetar. Cuando se crea una
biblioteca, se establece una relacin con el programador de clientes, que tambin es un programador, pero que debe cons-
truir su aplicacin utilizando su biblioteca, posiblemente con el fin de obtener una biblioteca ms grande. Si todos los
miembros de una clase estn disponibles para cualquiera, entonces el programador de clientes puede hacer cualquier
cosa con dicha clase y no hay forma de imponer reglas. Incluso cuando prefiera que el programador de clientes no mani-
pule directamente algunos de los miembros de su clase, sin control de acceso no hay manera de impedirlo. Todo est a
la vista del mundo.
Por tanto, la primera razn que justifica el control de acceso es mantener las manos de los programadores cliente apartadas
de las partes que son necesarias para la operacin interna de los tipos de datos, pero no de la parte correspondiente a la inter-
faz que los usuarios necesitan para resolver sus problemas concretos. Realmente, es un servicio para los programadores de
clientes porque pueden ver fcilmente lo que es importante para ellos y lo que pueden ignorar.
La segunda razn del control de acceso es permitir al diseador de bibliotecas cambiar el funcionamiento interno de la
clase sin preocuparse de cmo afectar al programador de clientes. Por ejemplo, desea implementar una clase particular
de una forma sencilla para facilitar el desarrollo y ms tarde descubre que tiene que volver a escribirlo para que se eje-
cute ms rpidamente. Si la interfaz y la implementacin estn claramente separadas y protegidas, podr hacer esto fcil -
mente.
Java emplea tres palabras clave explcitamente para definir los lmites en una clase: public, private y protected. Estos
modificadores de acceso detenninan quin puede usar las definiciones del modo siguiente: public indica que el elemento
que le sigue est disponible para todo el mundo. Por otro lado, la palabra clave private, quiere decir que nadie puede acce-
der a dicho elemento excepto usted, el creador del tipo, dentro de los mtodos de dicho tipo. private es un muro de ladri-
llos entre usted y el programador de clientes. Si alguien intenta acceder a un miembro private obtendr un error en tiempo
de compilacin. La palabra clave protected acta como private, con la excepcin de que una clase heredada tiene acceso
a los miembros protegidos (protected), pero no a los privados (private). Veremos los temas sobre herencia enseguida.
Java tambin tiene un acceso "predeterminado", que se emplea cuando no se aplica uno de los modificadores anteriores.
Normalmente, esto se denomina acceso de paquete, ya que las clases pueden acceder a los miembros de otras clases que
pertenecen al mismo paquete (componente de biblioteca), aunque fuera del paquete dichos miembros aparecen como priva-
dos (private).
4 Tnnino acuado por mi amigo Scott Meyers.
6 Piensa en Java
Reutilizacin de la implementacin
Una vez que se ha creado y probado una clase, idealmente debera representar una unidad de cdigo til. Pero esta reutili-
zacin no siempre es tan fcil de conseguir como era de esperar; se necesita experiencia y perspi cacia para generar un dise-
o de un obj eto reutili zable. Pero, una vez que se dispone de tal diseo, parece implorar ser reuti lizado. La reutilizacin de
cdi go es una de las grandes ventajas que proporcionan los lenguajes de programacin orientada a objetos.
La fonna ms sencill a de reutilizar una clase consiste simplemente en emplear directamente un objeto de dicha clase, aun-
que tambin se puede colocar un objeto de dicha clase dentro de una clase nueva. Esto es 10 que se denomina "crear un obje-
to miembro". La nueva clase puede estar fonnada por cualqui er nmero y tipo de otros objetos en cualquier combinacin
necesaria para conseguir la funcionalidad deseada en dicha nueva clase. Definir una nueva clase a partir de clases existen-
tes se denomina composicin (si la composicin se realiza de forma dinmica, se llama agregacin). A menudo se hace refe-
rencia a la composicin como una relacin "tiene un", como en "un coche tiene un motor".
~ __ co_c_h_e __ I ~ ~ ___ m_o_to_r __
Este diagrama UML indica la composicin mediante un rombo relleno, que establece que hay un coche. Nonnalmente, yo
utilizo una forma ms sencilla: slo una lnea, si n el rombo, para indicar una asociacin.
5
La composicin conll eva una gran fl exibilidad. Los objetos miembro de la nueva clase normalmente son privados, lo que
les hace inaccesibles a los programadores de clientes que estn usando la clase. Esto le permi te cambiar dichos miembros
sin disturbar al cdigo cliente existente. Los obj etos miembro tambin se pueden modifi car en tiempo de ejecucin, con el
fin de cambiar dinmicamente el comportamiento del programa. La herencia, que se describe a continuacin, no proporcio-
na esta fl exibilidad, ya que el compilador ti ene que aplicar las restricciones en tiempo de compilacin a las clases creadas
por herencia.
Dado que la herencia es tan importante en la programacin orientada a objetos, casi siempre se enfatiza mucho su uso, de
manera que los programadores novatos pueden ll egar a pensar que hay que emplearla en todas partes. Esto puede dar lugar
a que se hagan diseos demasiado complejos y complicados. En lugar de esto, en primer lugar, cuando se van a crear nue-
vas clases debe considerarse la composicin, ya que es ms simple y flexible. Si aplica este mtodo, sus diseos sern ms
inteligentes. Una vez que haya adquirido algo de experiencia, ser razonablemente obvio cundo se necesita emplear la
herencia.
Herencia
Por s mi sma, la idea de objeto es una buena herramienta. Permite unir datos y funcionalidad por concepto, lo que permite
representar la idea del problema-espacio apropiada en lugar de forzar el uso de los idiomas de la mquina subyacente. Estos
conceptos se expresan como unidades fundamentales en el lenguaje de programacin uti lizando la palabra clave c1ass.
Sin embargo, es una pena abordar todo el problema para crear una clase y luego verse forzado a crear una clase nueva que
podra tener una funcionalidad similar. Es mejor, si se puede, tomar la clase existente, clonarla y luego aadir o modificar
lo que sea necesari o al clon. Esto es lo que se logra con la herencia, con la excepcin de que la clase original (llamada clase
base. supere/ase o e/ase padre) se modifi ca, el clon "modificado" (denominado e/ase derivada, clase heredada, subclase o
clase hija) tambin refleja los cambios.
cy
I derivada I
5 Normalmente, es suficiente grado de detalle para la mayora de los diagramas y no es necesario especificar si se est usando una agregacin o una com-
posicin.
1 Introduccin a los objetos 7
La flecha de este diagrama UML apunta de la clase derivada a la clase base. Como veremos, puede haber ms de una clase
derivada.
Un tipo hace ms que describir las restricciones definidas sobre un conjunto de objetos; tambin tiene una relacin con otros
tipos. Dos tipos pueden tener caractersticas y comportamientos en comn, pero un tipo puede contener ms caractersticas
que el otro y tambin es posible que pueda manejar ms mensajes (o manejarlos de forma diferente). La herencia expresa
esta similitud entre tipos utilizando el concepto de tipos base y tipos derivados. Un tipo base contiene todas las caracters-
ticas y comportamientos que los tipos derivados de l comparten. Es recomendable crear un tipo base para representar el
ncleo de las ideas acerca de algunos de los objetos del sistema. A partir de ese tipo base, pueden deducirse otros tipos para
expresar las diferentes fonnas de implementar ese ncleo.
Por ejemplo, una mquina para el reciclado de basura clasifica los desperdicios. El tipo base es "basura" y cada desperdi-
cio tiene un peso, un valor, etc., y puede fragmentarse, mezclarse o descomponerse. A partir de esto, se derivan ms tipos
especficos de basura que pueden tener caractersticas adicionales (una botella tendr un color) o comportamientos (el alu-
minio puede modelarse, el acero puede tener propiedades magnticas). Adems, algunos comportamientos pueden ser dife-
rentes (el valor del papel depende de su tipo y condicin). Utilizando la herencia, puede construir una jerarqua de tipos que
exprese el problema que est intentando resolver en trminos de sus tipos.
Un segundo ejemplo es el clsico ejemplo de la forma, quiz usado en los sistemas de diseo asistido por computadora o en
la simulacin de juegos. El tipo base es ""forma" y cada forma tiene un tamao, un color, una posicin, etc. Cada forma puede
dibujarse, borrarse, desplazarse, colorearse, etc. A partir de esto, se derivan (heredan) los tipos especficos de formas (cr-
culo, cuadrado, tringulo, etc.), cada una con sus propias caractersticas adicionales y comportamientos. Por ejemplo, cier-
tas fonnas podrn voltearse. AIgtmos comportamientos pueden ser diferentes, como por ejemplo cuando se quiere calcular
su rea. La jerarqua de tipos engloba tanto las similitudes con las diferencias entre las formas.
Forma
dibujar()
borrar()
mover()
obtenerColor()
definirColor()
I I
Crculo Cuadrado Tringulo
Representar la solucin en los mismos tnninos que el problema es muy til , porque no se necesitan muchos modelos inter-
medios para pasar de una descripcin del problema a una descripcin de la solucin. Con objetos, la jerarqua de tipos es el
modelo principal, porque se puede pasar directamente de la descripcin del sistema en el mundo real a la descripcin del
sistema mediante cdigo. A pesar de esto, una de las dificultades que suelen tener los programadores con el diseo orienta-
do a objetos es que es demasiado sencillo ir del principio hasta el final. Una mente formada para ver soluciones complejas
puede, inicialmente, verse desconcertada por esta simplicidad.
Cuando se hereda de un tipo existente, se crea un tipo nuevo. Este tipo nuevo no slo contiene todos los miembros del tipo
existente (aunque los privados estn ocultos y son inaccesibles), sino lo que es ms importante, duplica la interfaz de la clase
base; es decir, todos los mensajes que se pueden enviar a los objetos de la clase base tambin se pueden enviar a los obje-
tos de la clase derivada. Dado que conocemos el tipo de una clase por los mensajes que se le pueden enviar, esto quiere decir
que la clase derivada es del mismo tipo que la clase base. En el ejemplo anterior, "un crculo es una forma". Esta equiva-
lencia de tipos a travs de la herencia es uno de los caminos fundamentales para comprender el significado de la programa-
cin orientada a objetos.
Puesto que la clase base y la clase derivada tienen la misma interfaz, debe existir alguna implementacin que vaya junto con
dicha interfaz. Es decir, debe disponerse de algn cdigo que se ejecute cuando un objeto recibe un mensaje concreto. Si
8 Piensa en Java
simplemente hereda una clase y no hace nada ms, los mtodos de la interfaz de la clase base pasan tal cual a la clase deri-
vada, lo que significa que los objetos de la clase derivada no slo tienen el mismo tipo sino que tambin tienen el mismo
comportamiento, lo que no es especialmente interesante.
Hay dos formas de diferenciar la nueva clase deri vada de la clase base original. La primera es bastante directa: simplemen-
te, se aaden mtodos nuevos a la clase derivada. Estos mtodos nuevos no forman parte de la interfaz de la clase base, lo
que significa que sta simplemente no haca todo lo que se necesitaba y se le han aadido ms mtodos. Este sencillo y pri-
mitivo uso de la herencia es, en ocasiones, la solucin perfecta del problema que se tiene entre manos. Sin embargo, debe
considerarse siempre la posibilidad de que la clase base pueda tambin necesitar esos mtodos adicionales. Este proceso de
descubrimiento e iteracin en un diseo tiene lugar habitualmente en la programacin orientada a objetos.
Forma
dibujarO
borrarO
moverO
obtenerColorO
definirColorO
I I
I
Crculo Cuadrado
Tringulo
VoltearVerticalO
VoltearHorizontal O
Aunque en ocasiones la herencia puede implicar (especialmente en Java, donde la palabra clave para herencia es extends)
que se van a aadir mtodos nuevos a la interfaz, no tiene que ser as necesariamente. La segunda y ms importante fonna
de diferenciar la nueva clase es cambiando el comportamiento de un mtodo existente de la clase base. Esto es lo que se
denomina sustitucin del mtodo.
Forma
dibujarO
borrarO
moverO
obtenerColorO
definirColorO
I I
Crculo Cuadrado Tringulo
dibujarO dibujarO dibujarO
borrarO borrarO borrarO
Para sustituir un mtodo, basta con crear una nueva defmicin para el mi smo en la clase derivada. Es decir, se usa el mismo
mtodo de interfaz, pero se quiere que haga algo diferente en el tipo nuevo.
Relaciones es-un y es-corno-un
Es habitual que la herencia suscite un pequeo debate: debe la herencia sustituir slo los mtodos de la clase base (y no
aadir mtodos nuevos que no existen en la clase base)? Esto significara que la clase derivada es exactamente del mismo
1 Introduccin a los objetos 9
tipo que la clase base, ya que tiene exactamente la misma interfaz. Como resultado, es posible sustituir de forma exacta un
objeto de la clase derivada por uno de la clase base. Se podra pensar que esto es una sustitucin pura y a menudo se deno-
mina principio de sustitucin. En cierto sentido, sta es la fonna ideal de tratar la herencia. A menudo, en este caso, la rela-
cin entre la clase base y las clases derivadas se dice que es una relacin es-un, porque podemos decir, "un crculo es una
forma". Una manera de probar la herencia es determinando si se puede aplicar la relacin es-un entre las clases y tiene sen-
tido.
A veces es necesario a.adir nuevos elementos de interfaz a un tipo derivado, ampliando la interfaz. El tipo nuevo puede
todava ser sustituido por el tipo base, pero la sustitucin no es perfecta porque el tipo base no puede acceder a los mtodos
nuevos. Esto se describe como una relacin es-corno-un. El tipo nuevo tiene la interfaz del tipo antiguo pero tambin con-
tiene otros mtodos, por lo que realmente no se puede decir que sean exactos. Por ejemplo, considere un sistema de aire
acondicionado. Suponga que su domicilio est equipado con todo el cableado para controlar el equipo, es decir, dispone de
una interfaz que le permite controlar el aire fro. Imagine que el aparato de aire acondicionado se estropea y lo reemplaza
por una bomba de calor, que puede generar tanto aire caliente como fro. La bomba de calor es-como-un aparato de aire
acondicionado, pero tiene ms funciones. Debido a que el sistema de control de su casa est diseado slo para controlar el
aire fro, est restringido a la comunicacin slo con el sistema de fro del nuevo objeto. La interfaz del nuevo objeto se ha
ampliado y el sistema existente slo conoce la interfaz original.
Termostato
Controles
Sistema de fro
bajarTemperaturaO enfriarO
t
I I
Acondicionador
Bomba de calor
de aire
enfriarO
enfriarO
calentarO
Por supuesto, una vez que uno ve este diseo, est claro que la clase base "sistema de aire acondicionado" no es general y
debera renombrarse como "sistema de control de temperatura" con el fin de poder incluir tambin el control del aire calien-
te, en esta situacin, est claro que el principio de sustitucin funcionar. Sin embargo, este diagrama es un ejemplo de lo
que puede ocurrir en el diseo en el mundo real.
Cuando se ve claro que el principio de sustitucin (la sustitucin pura) es la nica fonma de poder hacer las cosas, debe apli-
carse sin dudar. Sin embargo, habr veces que no estar tan claro y ser mejor aadir mtodos nuevos a la interfaz de una
clase derivada. La experiencia le proporcionar los conocimientos necesarios para saber qu mtodo emplear en cada caso.
Objetos intercambiables con polimorfismo
Cuando se trabaja con jerarquas de tipos, a menudo se desea tratar un objeto no como el tipo especfico que es, sino como
su tipo base. Esto penmite escribir cdigo que no dependa de tipos especficos. En el ejemplo de las fonmas, los mtodos
manipulan las fonnas genricas, independientemente de que se trate de crculos, cuadrados, tringulos o cualquier otra
fonna que todava no baya sido definida. Todas las fonmas pueden dibujarse, borrarse y moverse, por lo que estos mtodos
simplemente envan un mensaje a un objeto forma, sin preocuparse de cmo se enfrenta el objeto al mensaje.
Tal cdigo no se ve afectado por la adicin de tipos nuevos y esta adicin de tipos nuevos es la fonna ms comn de ampliar
un programa orientado a objetos para manejar situaciones nuevas. Por ejemplo, puede derivar un subtipo nuevo de forma
llamado pentgono sin modificar los mtodos asociados slo con las fonmas genricas. Esta capacidad de ampliar fcilmen-
te un diseo derivando nuevos subtipos es una de las principales fonnas de encapsular cambios. Esto mejora enormemente
los diseos adems de reducir el coste del mantenimiento del software.
Sin embargo, existe un problema cuando se intenta tratar los objetos de tipos derivado como sus tipos base genricos (cr-
culos como formas, bicicletas como automviles, cormoranes como aves, etc.). Si un mtodo dice a una forma que se dibu-
je, O a un automvil genrico que se ponga en marcha o a un ave que se mueva, el compilador no puede saber en tiempo de
10 Piensa en Java
compilacin de forma precisa qu parte del cdigo tiene que ejecutar. ste es el punto clave, cuando se enva el mensaje, el
programador no desea saber qu parte del cdigo se va a ejecutar; el mtodo para dibujar se puede aplicar igualmente a un
crculo, a un cuadrado o a un tringulo y los objetos ejecutarn el cdigo apropiado dependiendo de su tipo especfico.
Si no se sabe qu fragmento de cdigo se ej ecutar, entonces se aadir un subtipo nuevo y el cdigo que se ej ecute puede
ser diferente sin que sea necesario realizar cambios en el mtodo que lo llama. Por tanto, el compilador no puede saber de
forma precisa qu fragmento de cdigo hay que ejecutar y qu hace entonces? Por ejemplo, en el siguiente diagrama, el
objeto controlador Aves slo funciona con los objetos genricos Ave y no sabe exactamente de qu tipo son. Desde la pers-
pectiva del objeto controladorAves esto es adecuado ya que no ti ene que escribir cdigo especial para determinar el tipo
exacto de Ave con el que est trabajando ni el comportamiento de dicha Ave. Entonces, cmo es que cuando se invoca al
mtodo moverO ignorando el tipo especfico de Ave, se ejecuta el comportamiento correcto (un Ganso camina, vuela o nada
y un Pingino camina o nada)?
controladorAves Ave
reubicarO Qu ocurre cuando se moverO
llama a moverO?
t
I
I
Ganso Pingino
moverO moverO
La respuesta es una de las principales novedades de la programacin orientada a objetos: el compi lador no puede hacer una
llamada a funcin en el sentido tradicional. La llamada a funcin generada por un compilador no-POO hace lo que se deno-
mina un acoplamiento temprano, trmino que es posible que no haya escuchado antes. Significa que el compilador genera
una llamada a un nombre de funcin especfico y el sistema de tiempo de ejecucin resuelve esta llamada a la direccin
absoluta del cdigo que se va a ejecutar. En la POO, el programa no puede determinar la direccin del cdigo hasta estar en
tiempo de ejecucin, por lo que se hace necesario algn otro esquema cuando se enva un mensaje a un objeto genrico.
Para resolver el problema, los lenguajes orientados a objetos utilizan el concepto de acoplamiento tardo. Cuando se enva
un mensaje a un objeto, el cdi go al que se est llamando no se detelmina hasta el tiempo de ejecucin. El compilador no
asegura que el mtodo exista, realiza una comprobacin de tipos con los argumentos y devuelve un valor, pero no sabe exac-
tamente qu cdigo tiene que ejecutar.
Para realizar el acoplamiento tardo, Java emplea un bit de cdigo especial en lugar de una ll amada absoluta. Este cdigo
calcula la direccin del cuerpo del mtodo, utilizando la informacin almacenada en el objeto (este proceso se estudi a en
detalle en el Captulo 8, PolimOlfismo). Por tanto, cada objeto puede comportarse de forma di ferente de acuerdo con los con-
tenidos de dicho bit de cdigo especial. Cuando se enva un mensaj e a un objeto, realmente el objeto resuelve lo que tiene
que hacer con dicho mensaje.
En algunos lenguaj es debe establecerse explicitamente que un mtodo tenga la flexibilidad de las propiedades del acopla-
miento tardo (C++ utiliza la palabra clave virtual para ello). En estos lenguaj es, de manera predetenninada, los mtodos
no se acoplan de forma dinmica. En Java, el acoplamiento dinmico es el comportami ento predetenninado y el programa-
dor no tiene que aadi r ninguna palabra clave adicional para definir el polimorfismo.
Considere el ej emplo de las formas. La familia de clases (todas basadas en la misma interfaz uni forme) se ha mostrado en
un diagrama anteriormente en el captulo. Para demostrar el pol imorfismo, queremos escribir un fragmento de cdigo que
ignore los detalles especficos del tipo y que slo sea indicado para la clase base. Dicho cdigo se desacopla de la informa-
cin especfica del tipo y por tanto es ms sencill o de escribir y de comprender. Y, por ejemplo, si se aade un tipo nuevo
como Hexgono a travs de la herencia, el cdigo que haya escrito funcionar tanto para el nuevo tipo de Forma como para
los tipos existentes. Por tanto, el programa es ampliable.
Si escribe un mtodo en Java (lo que pronto aprender a hacer) como el siguiente:
void hacerAlgo(Forma forma) {
borrar. forma () ;
/ / ...
dibujar . forma () i
1 Introduccin a los objetos 11
Este mtodo sirve para cualquier Forma, por lo que es independiente del tipo especfico de obj eto que se est dibuj ando y
borrando. Si alguna otra parte del programa utiliza el mtodo hacerAlgoO:
Circulo circulo = new Circulo() ;
Triangulo triangulo = new Triangulo()j
Linea linea = new Linea () ;
hacerAlgo (c i r culo ) i
hacerAlgo (tri angulo) ;
hacerAlgo (linea ) ;
Las llamadas a JiacerAlgo O funcionarn correctamente, independientemente del tipo exacto del objeto.
De hecho, ste es un buen truco. Considere la lnea:
hacerAlgo (circulo) j
Lo que ocurre aqu es que se est pasando un Circulo en un mtodo que est esperando una Forma. Dado que un Circulo
es una Forma, hacerAlgoO puede tratarlo como tal. Es decir, cualqui er mensaje que hacerAlgoO pueda enviar a Forma,
un circulo puede aceptarlo. Por tanto, actuar as es completamente seguro y lgico.
Llamamos a este proceso de tratar un tipo derivado como si fuera un tipo base upcasting (generalizacin). La palabra sig-
nifica en ingls "proyeccin haci a arriba" y refleja la fonna en que se dibujan habitualmente los diagramas de herencia,
con el tipo base en la parte superior y las clases derivadas abrindose en abanico hacia abajo, upcasting es, por tanto, efec-
tuar una proyeccin sobre un tipo base, ascendiendo por el diagrama de herencia.
"Upcasting" t
,-
o
:
o
o
o
.- ______ J
o
_____ J I
Crculo
B
I
Cuadrado Tringulo
Un programa orientado a objetos siempre contiene alguna generalizacin, porque es la fonna de desvincularse de tener que
conocer el tipo exacto con que se trabaja. Veamos el cdigo de hacer AlgoO:
forma .borrar() i
II
forma.dibu j ar() ;
Observe que no se dice, "si eres un Circulo, hacer esto, si eres un Cuadrado, hacer esto, etc.". Con este tipo de cdigo lo
que se hace es comprobar todos los tipos posibles de Forma, lo que resulta lioso y se necesitara modificar cada vez que se
aadiera una nueva clase de Forma. En este ejemplo, slo se dice: "Eres una fonna, te puedo borrarO y dibujarO tenien-
do en cuenta correctamente los detalles".
Lo que ms impresiona del cdigo del mtodo hacerAlgoO es que, de alguna manera se hace lo correcto. Uamar a
dibujarO para Circulo da lugar a que se ejecute un cdigo diferente que cuando se le ll ama para un Cuadrado o una
Linea, pero cuando el mensaje dibujarO se enva a una Forma annima, tiene lugar el comportamiento correcto basn-
dose en el tipo real de la Forma. Esto es impresionante porque, como se ha dicho anteriormente, cuando el compilador
Java est compilando el cdigo de hacerAlgoO, no puede saber de forma exacta con qu tipos est tratando. Por ell o, habi-
tualmente se espera que llame a la versin de borrarO y dibujarO para la clase base Forma y no a la versin especfica
de Crculo, Cuadrado o Linea. Y sigue ocurriendo lo correcto gracias al polimorfi smo. El compilador y el sistema de
tiempo de ejecucin controlan los detalles; todo lo que hay que saber es qu ocurre y, lo ms importante, cmo disear
haciendo uso de ello. Cuando se enva un mensaje a un objeto, el objeto har lo correcto incluso cuando est implicado el
proceso de generalizacin.
La jerarqua de raz nica
Uno de los aspectos de la POO que tiene una importancia especial desde la introduccin de e++ es si todas las clases en
lti ma instancia deberan ser heredadas de una ni ca clase base. En Java (como en casi todos los dems lenguajes de POO
12 Piensa en Java
excepto e++) la respuesta es afinnativa. Y el nombre de esta clase base es simplemente Object. Resulta que las ventajas
de una jerarqua de raz nica son enormes.
Todos los objetos de una jerarqua de raz nica tienen una interfaz en comn, por 10 que en ltima instancia son del mismo
tipo fundamental. La alternativa (proporcionada por e++) es no saber que todo es del mismo tipo bsico. Desde el punto de
vista de la compatibilidad descendente, esto se ajusta al modelo de e mejor y puede pensarse que es menos restrictivo, pero
cuando se quiere hacer programacin orientada a objetos pura debe construirse una jerarqua propia con el fin de proporcio-
nar la misma utilidad que se construye en otros lenguajes de programacin orientada a objetos. Y en cualquier nueva biblio-
teca de clases que se adquiera, se emplear alguna otra interfaz incompatible. Requiere esfuerzo (y posiblemente herencia
mltiple) hacer funcionar la nueva interfaz en un diseo propio. Merece la pena entonces la "flexibilidad" adicional de
e++? Si la necesita (si dispone ya de una gran cantidad de cdigo en e), entonces es bastante valiosa. Si parte de cero, otras
alternativas como Java a menudo resultan ms productivas.
Puede garantizarse que todos los objetos de una jerarqua de raz nica tengan una determinada funcionalidad. Es posible
realizar detenninadas operaciones bsicas sobre todos los objetos del sistema. Pueden crearse todos los objetos y el paso de
argumentos se simplifica enonnemente.
Una jerarqua de raz nica facilita mucho la implementacin de un depurador de memoria, que es una de las mejoras fun-
damentales de Java sobre C++. y dado que la informacin sobre el tipo de un objeto est garantizada en todos los objetos,
nunca se encontrar con un objeto cuyo tipo no pueda determinarse. Esto es especialmente importante en las operaciones en
el nivel del sistema, corno por ejemplo el tratamiento de excepciones y para proporcionar un mayor grado de flexibilidad en
la programacin.
Contenedores
En general, no se sabe cuntos objetos se van a necesitar para resolver un determinado problema o cunto tiempo va a lle-
var. Tampoco se sabe cmo se van a almacenar dichos objetos. Cmo se puede saber cunto espacio hay que crear si no se
conoce dicha infonnacin hasta el momento de la ejecucin?
La solucin a la mayora de los problemas en el diseo orientado a objetos parece algo poco serio, esta solucin consiste en
crear otro tipo de objeto. El nuevo tipo de objeto que resuelve este problema concreto almacena referencias a otros objetos.
Por supuesto, se puede hacer 10 mismo con una matriz, elemento que est disponible en la mayora de los lenguajes. Pero
este nuevo objeto, denominado contenedor (tambin se llama coleccin, pero la biblioteca de Java utiliza dicho trmino con
un sentido diferente, por lo que en este libro emplearemos el trmino "contenedor"), se ampla por s mismo cuando es nece-
sario acomodar cualquier cosa que se quiera introducir en l. Por tanto, no necesitamos saber cuntos objetos pueden alma-
cenarse en un contenedor. Basta con crear un objeto contenedor y dejarle a l que se ocupe de los detalles.
Afortunadamente, los buenos lenguajes de programacin orientada a objetos incluyen un conj unto de contenedores como
parte del paquete. En e++, ese conjunto forma parte de la biblioteca estndar e++ y a menudo se le denomina STL
(Standard Template Library, biblioteca estndar de plantillas). Smalltalk tiene un conjunto muy completo de contenedores,
mientras que Java tiene tambin numerosos contenedores en su biblioteca estndar. En algunas bibliotecas, se considera que
uno o dos contenedores genricos bastan y sobran para satisfacer todas las necesidades, mientras que en otras (por ejemplo,
en Java) la biblioteca tiene diferentes tipos de contenedores para satisfacer necesidades distintas: varios tipos diferentes de
clases List (para almacenar secuencias), Maps (tambin denominados matrices asociativas y que se emplean para asociar
objetos con otros objetos), Sets (para almacenar un objeto de cada tipo) y otros componentes como colas, rboles, pilas, etc.
Desde el punto de vista del diseo, lo nico que queremos es disponer de un contenedor que pueda manipularse para resol-
ver nuestro problema. Si un mismo tipo de contenedor satisface todas las necesidades, no existe ninguna razn para dispo-
ner de varias clases de contenedor. Sin embargo, existen dos razones por las que s es necesario poder disponer de diferentes
contenedores. En primer lugar, cada tipo de contenedor proporciona su propio tipo de interfaz y su propio comportamiento
externo. Umi pila tiene una interfaz y un comportamiento distintos que una cola, que a su vez es distinto de un conjunto o
una lista. Es posible que alguno de estos tipos de contenedor proporcione una solucin ms flexible a nuestro problema que
los restantes tipos. En segundo lugar, contenedores diferentes tienen una eficiencia distinta a la hora de realizar determina-
das operaciones. Por ejemplo, existen dos tipos bsicos de contenedores de tipo Lis!: ArrayList (lista matricial) y LinkedList
(lista enlazada). Ambos son secuencias simples que pueden tener interfaces y comportamientos externos idnticos. Pero cier-
tas operaciones pueden llevar asociado un coste radicalmente distinto. La operacin de acceder aleatoriamente a los elemen-
tos contenidos en un contenedor de tipo ArrayList es una operacin de tiempo constante. Se tarda el mismo tiempo
1 Introduccin a los objetos 13
independientemente de cul sea el elemento que se haya seleccionado. Sin embargo, en un contenedor de tipo LinkedList
resulta muy caro desplazarse a lo largo de la lista para seleccionar aleatoriamente un elemento, y se tarda ms tiempo en
localizar un elemento cuanto ms atrs est situado en la lista. Por otro lado, si se quiere insertar un elemento en mitad de
la secuencia, es ms barato hacerlo en un contenedor de tipo LinkedList que en otro de tipo ArrayList. Estas y otras ope-
raciones pueden tener una eficiencia diferente dependiendo de la estructura subyacente de la secuencia. Podemos comenzar
construyendo nuestro programa con un contenedor de tipo LinkedList y, a la hora de juzgar las prestaciones, cambiar a otro
de tipo ArrayList. Debido a la abstraccin obtenida mediante la interfaz List, podemos cambiar de un tipo de contenedor a
otro con un impacto mnimo en el cdigo.
Tipos parametrizados (genricos)
Antes de Java SES, los contenedores albergaban objetos del tipo universal de Java: Objee!. La j erarqua de raz nica indi-
ca que todo es de tipo Objeet, por lo que un contenedor que almacene objetos de tipo Object podr almacenar cualquier
cosa.
6
Esto haca que los contenedores fueran fciles de reutilizar.
Para utilizar uno de estos contenedores, simplemente se aaden a l referencias a objetos y luego se las extrae. Sin embar-
go, puesto que el contenedor slo permite almacenar objetos de tipo Object, al aadir una referencia a objeto al contene-
dor, esa referencia se transforma en una referencia a Object perdiendo as su carcter. Al extraerla, se obtiene una referencia
a Object y no una referencia al tipo que se hubiera almacenado. En estas condiciones, cmo podemos transformar esa refe-
rencia en algo que tenga el tipo especfico de objeto que hubiramos almacenado en el contenedor?
Lo que se hace es volver a utilizar el mecanismo de transfonnacin de tipos (cast), pero esta vez no efectuamos una gene-
ralizacin, subiendo por la jerarqua de herencia, sino que efectuamos una especializacin, descendiendo desde la jerarqua
hasta alcanzar un tipo ms especfico. Este mecanismo de transformacin de tipos se denomina especializacin (downcas-
ting). Con el mecanismo de generalizacin (upcasting) , sabemos por ejemplo que un objeto Circulo es tambin de tipo
Forma, por lo que resulta seguro realizar la transformacin de tipos. Sin embargo, no todo objeto de tipo Object es nece-
sariamente de tipo Circulo o Forma por lo que no resulta tan seguro realizar una especializacin a menos que sepamos con-
cretamente lo que estamos haciendo.
Sin embargo, esta operacin no es del todo peligrosa, porque si efectuamos una conversin de tipos y transformamos el obje-
to a un tipo incorrecto, obtendremos un error de tipo de ejecucin denominado excepcin (lo que se describe ms adelante).
Sin embargo, cuando extraemos referencias a objetos de un contenedor, tenemos que disponer de alguna forma de recordar
exactamente lo que son, con el fin de poder realizar la conversin de tipos apropiada.
El mecanismo de especializacin y las comprobaciones en tiempo de ejecucin requieren tiempo adicional para la ejecucin
del programa y un mayor esfuerzo por parte del programador. No sera ms lgico crear el contenedor de manera que ste
supiera el tipo de los elementos que almacena, eliminando la necesidad de efectuar conversiones de tipos y evitando los erro-
res asociados? La solucin a este problema es el mecanismo de tipos parametrizados. Un tipo parametrizado es una clase
que el compilador puede personalizar automticamente para que funcione con cada tipo concreto. Por ejemplo, con un con-
tenedor parametrizado, el compilador puede personalizar dicho contenedor para que slo acepte y devuelva objetos Forma.
Uno de los cambios principales en Java SES es la adicin de tipos parametrizados, que se denominan genricos en Java. El
uso de genricos es fci lmente reconocible, ya que emplean corchetes angulares para encerrar alguna especificacin de tipo,
por ejemplo, puede crearse un contenedor de tipo ArrayList que almacene objetos de tipo Forma del siguiente modo:
ArrayList<Forma> formas new ArrayList<Forma> () ;
Tambin se han efectuado modificaciones en muchos de los componentes de las bibliotecas estndar para poder aprovechar
el uso de genricos. Como tendremos oportunidad de ver, los genricos tienen una gran importancia en buena parte del cdi-
go utilizado en este libro.
Creacin y vida de los objetos
Una de las cuestiones criticas a la hora de trabajar con los objetos es la forma en que stos se crean y se destruyen. Cada
objeto consigue una serie de recursos, especialmente memoria, para poder simplemente existir. Cuando un objeto deja de
6 Los contenedores no permiten almacenar primitivas, pero la caracterstica de alltobxing de Java SES hace que esta restriccin tenga poca importancia.
Hablaremos de esto en detalle ms adelante en el libro.
14 Piensa en Java
ser necesario, es preciso eliminarlo, para que se liberen estos recursos y puedan emplearse en alguna otra cosa. En los casos
ms simples de programacin, el problema de borrar los objetos no resulta demasiado complicado. Creamos el objeto, lo
usamos mientras que es necesario y despus lo destruimos. Sin embargo, no es dificil encontrarse situaciones bastante ms
complejas que sta.
Suponga por ejemplo que estamos diseando un sistema para gestionar el trfico areo de un aeropuerto (el mismo modelo
servira para gestionar piezas en un almacn o para un sistema de alquiler de vdeos o para una tienda de venta de masco-
tas). A primera vista, el problema parece muy simple: creamos un contenedor para almacenar las aeronaves y luego crea-
mos una nueva aeronave y la insertamos en el contenedor por cada una de las aeronaves que entren en la zona de control
del trfico areo. De cara al borrado, simplemente basta con eliminar el objeto aeronave apropiado en el momento en que
el avin abandone la zona.
Pero es posible que tengamos algn otro sistema en el que queden registrados los datos acerca de los aviones; quiz se trate
de datos que no requieran una atencin tan inmediata como la de la funcin principal de control del trfico areo. Puede que
se trate de un registro de los planes de vuelo de todos los pequeos aeroplanos que salgan del aeropuerto. Entonces, po-
dramos definir un segundo contenedor para esos aeroplanos y, cada vez que se creara un objeto aeronave, se introducira
tambin en este segundo contenedor si se trata de un pequeo aeroplano. Entonces, algn proceso de segundo plano podra
realizar operaciones sobre los objetos almacenados en este segundo contenedor en los momentos de inactividad.
Ahora el problema ya es ms complicado: cmo podemos saber cundo hay que destruir los objetos? An cuando nosotros
hayamos terminado de procesar un objeto, puede que alguna otra parte del sistema no lo haya hecho. Este mismo probl ema
puede surgir en muchas otras situaciones, y puede llegar a resultar enonnemente complejo de resolver en aquellos sistemas
de programacin (como C++) en los que es preciso borrar explcitamente un objeto cuando se ha terminado de utilizar.
Dnde se almacenan los datos correspondientes a un objeto y cmo se puede controlar el tiempo de vida del mismo? En
C++, se adopta el enfoque de que el control de la eficiencia es el tema ms importante, por lo que todas las decisiones que-
dan en manos del programador. Para conseguir la mxima velocidad de ejecucin, las caractersticas de almacenamiento y
del tiempo de vida del objeto pueden determinarse mientras se est escribiendo el programa, colocando los objetos en la pila
(a estos objetos se los denomina en ocasiones variables automticas o de mbito) o en el rea de almacenamiento esttico.
Esto hace que lo ms prioritario sea la velocidad de asignacin y liberacin del almacenamiento, y este control puede resul-
tar muy til en muchas situaciones. Sin embargo, perdemos fl exibil idad porque es preciso conocer la cantidad, el tiempo de
vida y el tipo exacto de los objetos a la hora de escribir el programa. Si estamos tratando de resolver un problema ms gene-
ral, como por ejemplo, un programa de diseo asistido por computadora, un sistema de gestin de almacn o un sistema de
control de trfico areo, esta solucin es demasiado restrictiva.
La segunda posibilidad consiste en crear los objetos dinmicamente en un rea de memoria denominada cmulo. Con este
enfoque, no sabemos hasta el momento de la ejecucin cuntos objetos van a ser necesarios, cul va a ser su tiempo de vida
ni cul es su tipo exacto. Todas estas caractersticas se determinan en el momento en que se ejecuta el programa. Si hace
falta un nuevo objeto, simplemente se crea en el cmulo de memoria, en el preciso instante en que sea necesario. Puesto que
el almacenamiento se gestiona dinmicamente en tiempo de ejecucin, la cantidad de tiempo requerida para asignar el alma-
cenamiento en el cmulo de memoria puede ser bastante mayor que el tiempo necesario para crear un cierto espacio en la
pila. La creacin de espacio de almacenamiento en la pila requiere normalmente tilla nica instruccin de ensamblador, para
desplazar hacia abajo el puntero de la pila y otra instruccin para volver a desplazarlo hacia arriba. El tiempo necesario
para crear un espacio de almacenamiento en el cmulo de memoria depende del diseo del mecani smo de almacenamiento.
La solucin dinmica se basa en la suposicin, generalmente bastante lgica, de que los objetos suelen ser complicados, por
lo que el tiempo adicional requerido para localizar el espacio de almacenamiento y luego liberarlo no tendr demasiado
impacto sobre el proceso de creacin del objeto. Adems, el mayor grado de flexibilidad que se obtiene resulta esencial para
resolver los problemas de programacin de carcter general.
Java utiliza exclusivamente un mecanismo dinmico de asignacin de memoria
7
. Cada vez que se qui ere crear un objeto, se
utiliza el operador new para constmir una instancia dinmica del objeto.
Sin embargo, existe otro problema, referido al tiempo de vida de un objeto. En aquellos lenguajes que permiten crear obje-
tos en la pila, el compilador determina cul es la duracin del objeto y puede destruirlo automticamente. Sin embargo, si
creamos el obj eto en el cmulo de memoria, el compilador no sabe cul es su tiempo de vida. En un lenguaje como C++,
7 Los tipos primitivos, de los que hablaremos en breve, representun un caso especial.
1 Introduccin a los objetos 15
es preciso determinar mediante programa cundo debe destruirse el objeto, lo que puede provocar prdidas de memori a si
no se real iza esta tarea correctamente (y este problema resulta bastante comn en los programas C++). lava proporciona una
caracterstica denominada depurador de memoria, que descubre automti camente cundo un determinado obj eto ya no est
en uso, en cuyo caso lo destruye. Un depurador de memoria resulta mucho ms cmodo que cualqui er otra solucin alter-
nativa, porque reduce el nmero de problemas que el programador debe controlar, y reduce tambin la cantidad de cdigo
que hay que escribir. Adems, lo que resulta ms importante, el depurador de memoria proporciona un ni vel mucho mayor
de proteccin contra el insidioso problema de las fugas de memoria, que ha hecho que muchos proyectos en C++ fraca-
saran.
En Java, el depurador de memoria est diseado para encargarse del problema de liberacin de la memoria (aunque esto no
incluye otros aspectos relativos al borrado de un obj eto). El depurador de memoria "sabe" cundo ya no se est usando un
objeto, en cuyo caso libera automticamente la memori a correspondiente a ese objeto. Esta caracterstica, combinada con el
hecho de que todos los objetos heredan de la clase raz Object, y con el hecho de que slo pueden crearse obj etos de una
manera (en el cmulo de memoria). hace que el proceso de programacin en Java sea mucho ms simple que en C++, hay
muchas menos decisiones que tomar y muchos menos problemas que resolver.
Tratamiento de excepciones: manejo de errores
Desde la apari cin de los lenguajes de programacin, el tratami ento de los errores ha constituido un problema peculiannen-
te dificil. Debido a que resulta muy complicado di sear un buen esquema de tratamiento de errores! muchos lenguajes sim-
plemente ignoran este problema, dejando que lo resuelvan los diseftadores de bibliotecas, que al final terminan desarrollando
soluciones parci ales que funcionan en muchas situaciones pero cuyas medidas pueden ser obviadas fcilmente; generalmen-
te, basta con ignorarlas. Uno de los problemas princi pales de la mayora de los esquemas de tratamiento de errores es que
dependen de que el programador tenga cuidado a la hora de seguir un convenio preestablecido que no resulta obligatorio
dentro del lenguaj e. Si el programador no tiene cuidado (lo cual suele suceder cuando hay prisa por terminar un proyecto),
puede olvi darse fci lmente de estos esquemas.
Los mecanismos de tratamiento de excepciones integran la gestin de errores directamente dentro del lenguaje de progra-
macin. en ocasiones. dentro incluso del sistema operativo. Una excepcin no es ms que un objeto "generado" en el lugar
donde se ha producido el error y que puede ser "capturado" mediante una rutina apropiada de tratamiento de excepciones
diseada para gestionar dicho tipo particular de error. Es como si el tratamiento de excepciones fuera una ruta de ejecucin
paralela y diferente, que se toma cuando algo va mal. Y, como se utili za una ruta de ejecucin independiente. sta no tiene
porqu interferir con el cdigo que se ejecuta nonnalmente. Esto hace que el cdigo sea ms simple de escribir. porque no
es necesario comprobar constantemente la existencia de eITores. Adems. las excepciones generadas se diferencian de los
tpicos valores de error devueltos por los mtodos o por los indicadores activados por los mtodos para avisar que se ha pro-
ducido una condicin de error; tanto los valores como los indicadores de error podran ser ignorados por el programador.
Las excepciones no pueden ignorarse. por 10 que se garantiza que en algn momento sern tratadas. Finalmente. las excep-
ciones proporcionan un mecanismo para recuperarse de manera fiable de cualquier situacin errnea. En lugar de limitarse
a salir del programa. a menudo podemos corregir las cosas y restaurar la ejecucin, lo que da como resultado programas
mucho ms robustos.
El tratamiento de excepciones de Java resulta muy sobresal iente entre los lenguaj es de programacin, porque en Java el tra-
tamiento de excepciones estaba previsto desde el principio y estamos obligados a utili zarlo. Este esquema de tratamiento de
excepciones es el nico mecani smo aceptable en Java para informar de la existencia de errores. Si no se escribe el cdigo
de manera que trate adecuadamente las excepciones se obtiene un error en tiempo de compil acin. Esta garanta de cohe-
rencia puede hacer que, en ocasiones. el tratamiento de errores sea mucho ms sencillo.
Merece la pena resaltar que el tratamiento de excepciones no es una caracterstica orientada a objetos, aunque en los len-
guajes de programacin orientada a objetos las excepciones se representan normalmente medi ante un objeto. Los mecanis-
mos de tratamiento de excepciones ya existan antes de que hicieran su aparicin los lenguajes orientados a objetos.
Programacin concurrente
Un concepto fundamental en el campo de la programacin es la idea de poder gestionar ms de una tarea al mismo tiempo.
Muchos problemas de programacin requieren que el programa detenga la tarea que estuviera realizando, resuelva algn
16 Piensa en Java
otro problema y luego vuelva al proceso principal. A lo largo del tiempo, se ha tratado de aplicar diversas soluciones a este
problema. Inicialmente, los programadores que tenan un adecuado conocimiento de bajo nivel de la mquina sobre la que
estaban programando escriban rutinas de servicio de interrupcin, y la suspensin del proceso principal se llevaba a cabo
mediante una interrupcin hardware. Aunque este esquema funcionaba bien, resultaba complicado y no era portable, por lo
que traducir un programa a un nuevo tipo de mquina resultaba bastante lento y muy caro.
En ocasiones, las interrupciones son necesarias para gestionar las tareas con requisitos crticos de tiempo, pero hay una
amplia clase de problemas en la que tan slo nos interesa dividir el problema en una serie de fragmentos que se ejecuten por
separado (tareas), de modo que el programa completo pueda tener un mej or tiempo de respuesta. En un programa, estos frag-
mentos que se ejecutan por separado, se denominan hebras y el conjunto general se llama concurrencia. Un ejemplo bas-
tante comn de concurrencia es la interfaz de usuario. Utilizando distintas tareas, el usuario apretando un botn puede
obtener una respuesta rpida, en lugar de tener que esperar a que el programa finalice con la tarea que est actualmente rea-
lizando.
Normalmente, las tareas son slo una forma de asignar el tiempo disponible en un nico procesador. Pero si el sistema ope-
rativo soporta mltiples procesadores, puede asignarse cada tarea a un procesador distinto, en cuyo caso las tareas pueden
ejecutarse realmente en paralelo. Una de las ventajas de incluir los mecanismos de concurrencia en el nivel de lenguaje es
que el programador no tiene que preocuparse de si hay varios procesadores o slo uno; el programa se divide desde el punto
de vista lgico en una serie de tareas, y si la mquina dispone de ms de un procesador, el programa se ej ecutar ms rpi-
do, sin necesidad de efectuar ningn ajuste especial.
Todo esto hace que la concurrencia parezca algo bastante sencillo, pero existe un problema: los recursos compartidos. Si se
estn ejecutando varias tareas que esperan poder acceder al mismo recurso, tendremos un problema de contienda entre las
tareas. Por ejemplo, no puede haber dos procesadores enviando informacin a una misma impresora. Para resolver el pro-
blema, los recursos que puedan compartirse, como por ejemplo una impresora, deben bloquearse mientras estn siendo uti-
lizados por una tarea. De manera que la forma de funcionar es la siguiente: una tarea bloquea un recurso, completa el trabajo
que tuviera asignado y luego elimina el bloqueo para que alguna otra tarea pueda emplear el recurso.
Los mecanismos de concurrencia en Java estn integrados dentro de lenguaje y Java SES ha mejorado significativamente el
soporte de biblioteca para los mecanismos de concurrencia.
Java e Internet
Si Java no es, en defmitiva, ms que otro lenguaje informtico de programacin, podramos preguntamos por qu es tan
importante y por qu se dice de l que representa una autntica revolucin dentro del campo de la programacin. La res-
puesta no resulta obvia para aqullos que provengan del campo de la programacin tradicional. Aunque Java resulta muy
til para resolver problemas de programacin en entornos autnomos, su importancia se debe a que permite resolver los pro-
blemas de programacin que surgen en la World Wide Web.
Qu es la Web?
Al principio, la Web puede parecer algo misterioso, con todas esas palabras extraas como "surfear," "presencia web" y
"pginas de inicio". Resulta til, para entender los conceptos, dar un paso atrs y tratar de comprender lo que la Web es real-
mente, pero para ello es necesario comprender primero lo que son los sistemas cliente/servidor, que constituyen otro campo
de la informtica ll eno de conceptos bastante confusos.
Informtica cliente/servidor
La idea principal en la que se basan los sistemas cliente/servidor es que podemos disponer de un repositorio centralizado de
informacin (por ejemplo, algn tipo de datos dentro de una base de datos) que queramos distribuir bajo demanda a una
serie de personas o de computadoras. Uno de los conceptos clave de las arquitecturas cliente/servidor es que el repositorio
de informacin est centralizado, por lo que puede ser modificado sin que esas modificaciones se propaguen hasta los con-
sumidores de la informacin. El repositorio de informacin, el software que distribuye la informacin y la mquina o mqui-
nas donde esa informacin y ese software residen se denominan, en conjunto, "servidor". El software que reside en las
mquinas consumidoras, que se comunica con el servidor, que extrae la infonnacin, que la procesa y que luego la muestra
en la propia mquina consumidora se denomina cliente.
1 Introduccin a los objetos 17
El concepto bsico de informtica cliente/servidor no es, por tanto, demasiado complicado. Los problemas surgen porque
disponemos de un nico servidor tratando de dar servicio a mltiples clientes al mismo ti empo. Generalmente, se utiliza
algn tipo de sistema de gestin de bases de datos de modo que el diseador "equilibra" la disposicin de los datos entre
distintas tablas, con el fin de optimizar el uso de los datos. Adems, estos sistemas permiten a menudo que los cli entes inser-
ten nueva informacin dentro de un servidor. Esto quiere decir que es preciso garantizar que los nuevos datos de un cliente
no sobreescriban los nuevos datos de otro cliente, al igual que hay que garantizar que no se pierdan datos en el proceso de
aadirlos a la base de datos (este tipo de mecanismos se denomina procesamiento de transacciones) . A medida que se real i-
zan modificaciones en el software de cliente, es necesario disear el software, depurarlo e instalarlo en las mquinas clien-
te, lo que resulta ser ms complicado y ms caro de lo que en un principio cabra esperar. Resulta especialmente
problemtico soportar mltiples tipos de computadoras y de sistemas operativos. Finalmente, es necesario tener en cuenta
tambin la cuestin crucial del rendimiento: puede que tengamos cientos de clientes enviando cientos de solicitudes al ser-
vidor en un momento dado, por 10 que cualquier pequeo retardo puede llegar a ser verdaderamente crtico. Para minimizar
la latencia, los programadores hacen un gran esfuerzo para tratar de descargar las tareas de procesamiento que en ocasiones
se descargan en la mquina cliente, pero en otras ocasiones se descargan en otras mquinas situadas junto al servidor, utili-
zando un tipo especial de software denominado middleware, (el middleware se utiliza tambin para mejorar la facilidad de
mantenimiento del sistema).
Esa idea tan simple de distribuir la infonnacin tiene tantos niveles de complejidad que el problema global puede parecer
enigmticamente insoluble. A pesar de lo cual, se trata de un problema crucial: la informtica cliente/servidor representa
aproximadamente la mitad de las actividades de programacin en la actualidad. Este tipo de arquitectura es responsable de
todo tipo de tareas, desde la introduccin de pedidos y la realizacin de transacciones con tarjetas de crdito basta la distri-
bucin de cualquier tipo de datos, como por ejemplo cotizaciones bursti les, datos cientficos, infonnacin de organismos
gubernamentales. En el pasado, lo que hemos hecho es desarrollar soluciones individuales para problemas individuales,
inventando una nueva solucin en cada ocasin. Esas soluciones eran dificiles de disear y de utilizar, y el usuario se vea
obl igado a aprender una nueva interfaz en cada caso. De este modo, se lleg a un punto en que era necesario resolver el pro-
blema global de la infonntica cliente/servidor de una vez y para siempre.
La Web como un gigantesco servidor
La Web es, en la prctica, un sistema gigantesco de tipo cliente/servidor. En realidad, es todava ms complejo, ya que lo
que tenemos es un conjunto de servidores y clientes que coexisten en una misma red de manera simultnea. El usuario no
necesita ser consciente de ello, por 10 que lo nico que hace es conectarse con un servidor en cada momento e interactuar
con l (an cuando para llegar a ese servidor haya sido necesario ir saltando de servidor en servidor por todo el mundo hasta
dar con el correcto).
Inicialmente, se trataba de un proceso muy simple de carcter unidireccional: el usuario enviaba una solicitud al servidor y
ste le devolva un archivo, que el software explorador de la mquina (es decir, el cliente) se encargaba de interpretar, efec-
tuando todas las tareas de formateo en la propia mquina local. Pero al cabo de muy poco tiempo, los propietarios de servi-
dores comenzaron a querer hacer cosas ms complejas que simplemente suministrar pginas desde el servidor. Queran
disponer de una capacidad completa cliente/servidor, de forma que el cliente pudiera, por ejemplo enviar informacin al ser-
vidor, realizar bsquedas en una base de datos instalada en el servidor, aadir nueva infonnacin al servidor o realizar un
pedido (lo que requiere medidas especiales de seguridad). stos son los cambios que hemos vivido en los ltimos aos en el
desarrollo de la Web.
Los exploradores web representaron un gran avance: permitieron implementar el concepto de que un mismo fragmento de
infonnacin pudiera visualizarse en cualquier tipo de computadora sin necesidad de efectuar ninguna modificacin. Sin
embargo, los primeros exploradores eran bastante primitivos y se colapsaban rpidamente debido a las demandas que se les
baca. No resultaban peculiarmente interactivos y tendan a sobrecargar al servidor tanto como a la propia red Internet, por-
que cada vez que haca falta hacer algo que requera programacin, era necesario devolver la infonnacin al servidor para
que ste la procesara. De esta fonna, poda tardarse varios segundos o incluso minutos en averiguar simplemente que ha-
bamos tecleado incorrectamente algo dentro de la solicitud. Como el explorador era simplemente un mecanismo de visua-
lizacin no poda realizar ni siquiera la ms simple de las tareas (por otro lado, resultaba bastante seguro ya que no poda
ejecutar en la mquina local ningn programa que pudiera contener errores o virus).
Para resolver este problema, se han adoptado diferentes enfoques. Para empezar se han mejorado los estndares grficos
( para poder disponer de mejores animaciones y vdeos dentro de los exploradores. El resto del problema slo puede resol -
18 Piensa en Java
verse incorporando la capacidad de ejecutar programas en el extremo cliente, bajo control del explorador. Esto se denomi-
na programacin del lado del c1ientc.
Programacin del lado del cliente
El diseo inicial de la Web, basado en una arquitectura servidor/explorador, permita disponer de contenido interacti vo, pero
esa interacti vidad era completamente proporcionada por el servidor. El servidor generaba pginas estticas para el explora-
dor cliente, que si mplemente se encargaba de interpretarlas y mostrarlas. El lenguaje bsico HTML (HyperText Markup
Language) contiene una serie de mecanismos simpl es para la introduccin de datos: recuadros de introduccin de texto, casi-
llas de verificacin, botones de opcin, li stas normales y listas desplcgables, as como un botn que slo poda programar-
se para bonar los datos del formulario o enviarlos al servidor. Ese proceso de envo se ll evaba a cabo a travs de la interfaz
CGI (Common Gateway lntelface) incluida en todos los servidores web. El texto incorporado en el envo le dice a la inter-
faz CGI 10 que tiene que hacer. La accin ms comn, en este caso, consiste en ejecutar un programa ubicado en un servi-
dor en un directorio normalmente llamado "cgi-bin" (si observa la barra de direcciones situada en la parte superior del
explorador cuando pulse un botn en una pgina web, en ocasiones podr ver las palabras "cgi-bin" como parte de la direc-
cin). Estos programas del lado del servidor pueden escribirse en casi cualquier lenguaje. Perl es uno de los lenguajes ms
utilizados para este tipo de tareas, porque est diseado especficamente para la manipulacin de textos y es un lenguaje
interpretado, por 10 que se puede instalar en cualquier servidor independientemente de cul sea su procesador o su sistema
operativo. Sin embargo, otro lenguaje, Python (WwHePython.org) se est abriendo camino rpidamente, debido a su mayor
potencia y su mayor simplicidad.
Hay muchos sitios web potentes en la actualidad diseados estrictamente con CGI, y lo cierto es que con CGI se puede hacer
prcticamente de todo. Sin embargo, esos sitios web basados en programas CGI pueden ll egar a ser rpidamente bastante
complicados de mantener, y adems pueden aparecer problemas en lo que se refiere al tiempo de respuesta. El tiempo de
respuesta de un programa CGl depende de cuntos datos haya que enviar, de la carga del servidor y de la red Internet (ade-
ms, el propio arranque de un programa COI tiende a ser bastante lento). Los primeros diseadores de la Web no previeron
la rapidez con que el ancho de banda disponible iba a agotarse debido a los tipos de aplicaciones que la gente llegara a de-
sarrollar. Por ejemplo, es casi imposible disear de manera coherente una aplicacin con grficos dinmicos, porque es nece-
sario crear un archivo GIF (Graphics !nterchange Formal) y enviarlo del servidor al cliente para cada versin de grfico.
Adems, casi todos los usuarios hemos experimentado lo engorroso del proceso de validacin de los datos dentro de un for-
mulario enviado a travs de la Web. El proceso es el siguiente: pul samos el botn de envo de la pgina; se envan los datos
al servidor, el servidor arranca un programa CGI que descubre un error, fonnatea una pgina HTML en la que nos informa
del error y devuelve la pgina al cl iente; a continuacin, es necesario que el usuario retroceda una pgina y vuelva a inten-
tarlo. Este enfoque no slo resulta lento sino tambin poco elegante.
La solucin consiste en usar un mecanismo de programacin del lado del cliente. La mayora de las computadoras de sobre-
mesa que incluyen un explorador wcb son mquinas bastante potentes, capaces de realizar tareas muy complejas; con el
enfoque ori ginal basado en HTML esttico, esas potentes mqui nas simplemente se limitan a esperar sin hacer nada, hasta
que el servidor se digna a enviarles la siguiente pgina. La programacin del lado del cliente pennite asignar al explorador
web todo el trabajo que pueda llevar a cabo, con lo que el resultado para los usuarios es una experiencia mucha ms rpida
y ms interactiva a la hora de acceder a los sitios web.
El problema con las expli caciones acerca de la programacin del lado del cliente es que no se diferencian mucho de las
explicaciones relativas a la programacin en general. Los parmetros son prcticamente idnticos, aunque la plataforma sea
distinta: un explorador web es una especie de sistema operativo limitado. En ltimo tnnino, sigue siendo necesario dise-
ar programas, por lo que los problemas y soluciones que nos encontramos dentro del campo de la programacin del lado
del cliente son bastante tradicionales. En el resto de esta seccin, vamos a repasar algunos de los principales problemas y
tcnicas que suelen encontrarse en el campo de la programacin del lado del cliente.
Plug-ns
Uno de los avances ms significativos en la programacin del lado del cliente es el desarrollo de lo que se denomina plug-
in. Se trata de un mecanismo mediante el que un programador puede aadir algn nuevo tipo de funcionalidad a un explo-
rador descargando un fragmento de cdigo que se inserta en el lugar apropiado dentro del explorador. Ese fragmento de
cdigo le dice al explorador: "A partir de ahora puedes real izar este nuevo tipo de actividad" (slo es necesario descargar el
1 Introduccin a los objetos 19
plug-in una vez). Podemos aadir nuevas fannas de comportamiento, potentes y rpidas, a los exploradores mediante
plug-ins, pero la escritura de unplug-in no resulta nada trivial, y por eso mismo no es conveniente acometer ese tipo de tarea
como parte del proceso de construccin de un sitio \Veb. El valor de un plug-in para la programacin del lado del cliente es
que pernlite a los programadores avanzados desarrollar extensiones y aadrselas a un explorador sin necesidad de pedir per-
miso al fabricante del explorador. De esta fonna, los plug-ins proporcionan una especie de "puerta trasera" que pennite la
creacin de nuevos lenguajes de programacin del lado del cliente (aunque no todos los lenguajes se implementan como
plug-ins).
Lenguajes de script
Los plug-ins dieron como resultado el desarrollo de lenguajes de scrip! para los exploradores. Con un lenguaje de script, el
cdigo fuente del programa del lado del cliente se integra directamente dentro de la pgina HTML, y el plllg-in que se encar-
ga de interpretar ese lenguaj e se activa de manera automtica en el momento de visualizar la pgina HTML. Los lenguajes
de script suelen ser razonablemente fci les de comprender, y como estn formados simplemente por texto que se incluye
dentro de la propia pgina HTML, se cargan muy rpidamente como parte del acceso al servidor mediante el que se obtie-
ne la pgina. La desventaja es que el cdigo queda expuesto, ya que cualquiera puede verlo (y copiarlo). Generalmente, sin
embargo, los programadores no ll evan a cabo tareas extremadamente sofisticadas con los lenguajes de script, as que este
problema no resulta particularmente grave.
Uno de los lenguajes de scripl que los exploradores web suelen soportar sin necesidad de un plllg-in es JavaScript (ellen-
guaje JavaScript slo se asemeja de forma bastante vaga a Java, por lo que hace falta un esfuerzo de aprendizaje adicional
para ll egar a dominarlo; recibi el nombre de JavaScript simplemente para aprovechar el impulso inicial de marketing de
Java). Lamentablemente, cada explorador web implementaba originalmente JavaScript de forma distinta a los restantes
ex ploradores web, e incluso en ocasiones, de fonna diferente a otras versiones del mismo explorador. La estandarizacin de
JavaScript mediante el diseo del lenguaje estndar ECMAScript ha resuelto parcialmente este problema, pero tuvo que
transcurrir bastante ti empo hasta que los distintos exploradores adoptaron el estndar (el problema se compl ic porque
Microsoft trataba de conseguir sus propios objetivos presionando en favor de su lenguaje VBScript, que tambin se aseme-
jaba vagamente a JavaScript). En general , es necesario llevar a cabo la programacin de las pginas utilizando una especie
de mnimo comn denominador de JavaScripr, si lo que queremos es que esas pginas puedan visualizarse en todos los tipos
de exploradores. Por su parte, la solucin de errores y la depuracin en JavaScript son un autntico lo. Como prueba de lo
dificil que resulta disear un problema complejo con JavaScript, slo muy recientemente alguien se ha atrevido a crear una
aplicacin compleja basada en l (Google, con GMail), y ese desarrollo requiri una dedicacin y una experiencia realmen-
te notables.
Lo que todo esto nos sugiere es que los lenguajes de script que se emplean en los exploradores web estn di seados, real-
mente, para resolver tipos especficos de problemas, principalmente el de la creacin de interfaces grficas de usuario (GUT)
ms ricas e interacti vas. Sin embargo, un lenguaje de script puede resolver quiz un 80 por ciento de los problemas que
podemos encontrar en la programacin del lado del cliente. Es posible que los problemas que el lector quiera resolver estn
incluidos dentro de ese 80 por ciento. Si esto es as, y teniendo en cuenta que los lenguaj es de scripl permiten realizar los
desarrollos de fonna ms fcil y rpida, probablemente sera conveniente ver si se puede resolver un tipo concreto de pro-
blema empleando un lenguaje de script, antes de considerar otras soluciones ms complejas, como la programacin en Java.
Java
Si un lenguaje de scripl puede resolver el 80 por ciento de los problemas de la programacin del lado del cliente, qu pasa
con el otro 20 por ciento, con los "problemas realmente dificiles"? Java representa una solucin bastante popular para este
tipo de problemas. No slo se trata de un potente lenguaje de programacin diseado para ser seguro, inteplataforma e inter-
nacional, sino que continuamente est siendo ampliado para proporcionar nuevas caractersticas del lenguaj e y nuevas
bibliotecas que penniten gestionar de manera elegante una seri e de problemas que resultan bastante dificiles de tratar en los
lenguajes de programacin tradi cionales, como por ejemplo la concurrencia, el acceso a bases de datos, la programacin en
red y la infonutica distribuida. Java permite resolver los problemas de programacin del lado del cliente utilizando applets
y Java Web Starl.
Un applet es un mini-programa que slo puede ejecutarse sobre un explorador web. El applet se descarga automticamen-
te como parte de la pgina web (de la misma forma que se descarga automticamente, por ejemplo, un grfico). Cuando se
acti va el applet, ejecuta un programa. Este mecani smo de ejecucin automtica forma parte de la belleza de esta solucin:
nos proporciona una fonna de distribuir automticamente el software de cliente desde el servidor en el mi smo momento en
20 Piensa en Java
que el usuario necesita ese software de cliente, y no antes. El usuario obtiene la ltima versin del software de cliente, libre
de errores y sin necesidad de realizar complejas reinstalaciones. Debido a la forma en que se ha diseado Java, el progra-
mador slo tiene que crear un programa simple y ese programa funcionar automticamente en todas las computadoras que
dispongan de exploradores que incluyan un intrprete integrado de Java (lo que incluye la inmensa mayora de mquinas).
Puesto que Java es un lenguaje de programacin completo podemos llevar a cabo la mayor cantidad de trabajo posible en
el cliente, tanto antes como despus de enviar solicitudes al servidor. Por ejemplo, no es necesario enviar una solicitud a tra-
vs de Internet simplemente para descubrir que hemos escrito mal una fecha o algn otro parmetro; asimismo, la compu-
tadora cliente puede encargarse de manera rpida de la tarea de dibujar una serie de datos, en lugar de esperar a que el
servidor genere el grfico y devuelva una imagen al explorador. De este modo, no slo aumentan de forma inmediata la velo-
cidad y la capacidad de respuesta, sino que disminuyen tambin la carga de trabajo de los servidores y el trfico de red, evi-
tando as que todo Internet se ralentice.
Alternativas
Para ser honestos, los applets Java no han llegado a cumplir con las expectativas iniciales. Despus del lanzamiento de Java,
pareca que los applets era lo que ms entusiasmaba a todo el mundo, porque iban a permitir finalmente realizar tareas serias
de programacin del lado del cliente, iban a mejorar la capacidad de respuesta de las aplicaciones basadas en Internet e iban
a reducir el ancho de banda necesario. Las posibilidades que todo el mundo tena en mente eran inmensas.
Sin embargo, hoy da nos podemos encontrar con unos applets realmente interesantes en la Web, pero la esperada migra-
cin masiva hacia los applets no lleg nunca a producirse. El principal problema era que la descarga de 10MB necesaria
para instalar el entorno de ejecucin JRE (Java Runtime Environment) era demasiado para el usuario medio. El hecho de
que Microsoft decidiera no incluir el entorno JRE dentro de Internet Explorer puede ser lo que acab por determinar su acia-
go destino. Pero, sea como sea, lo cierto es que los applets Java no han llegado nunca a ser utilizados de forma masiva.
A pesar de todo, los applets y las aplicaciones Java Web Start siguen siendo adecuadas en algunas situaciones. En todos
aquellos casos en que tengamos control sobre las mquinas de usuario, por ejemplo en una gran empresa, resulta razonable
distribuir y actualizar las aplicaciones cliente utilizando este tipo de tecnologas, que nos pueden ahorrar una cantidad con-
siderable de tiempo, esfuerzo y dinero, especialmente cuando es necesario realizar actualizaciones frecuentes.
En el Captulo 22, Interfaces grficas de usuario, analizaremos una buena tecnologa bastante prometedora, Flex de
Macromedia, que permite crear equivalentes a los applels basados en Flash. Como el reproductor Flash Player est dispo-
nible en ms del 98 por ciento de todos los exploradores web (incluyendo Windows, Linux y Mac), puede considerarse como
un estndar de facto. La instalacin O actualizacin de Flash Player es asimismo rpida y fcil. El lenguaje ActionScript est
basado en ECMAScript, por lo que resulta razonablemente familiar, pero Flex permite realizar las tareas de programacin
sin preocuparse acerca de las especifidades de los exploradores, por 10 que resulta bastante ms atractivo que JavaScript.
Para la programacin del lado del cliente se trata de una alternativa que merece la pena considerar.
.NETy C#
Durante un tiempo, el competidor principal de los applets de Java era ActiveX de Microsoft, aunque esta tecnologa reque-
ra que en el cliente se estuviera ejecutando el sistema operativo Windows. Desde entonces, Microsoft ha desarrollado un
competidor de Java: la plataforma .NET y el lenguaje de programacin C#. La plataforma .NET es, aproximadamente, equi-
valente a la mquina virtual Java (NM, Java Virtual Machine; es la plataforma software en la que se ejecutan los progra-
mas Java) y a las bibliotecas Java, mientras que C# tiene similitudes bastante evidentes con Java. Se trata, ciertamente, del
mejor intento que Microsoft ha ll evado a cabo en el rea de los lenguajes de programacin. Por supuesto, Microsoft parta
con la considerable ventaja de conocer qu cosas haban funcionado de manera adecuada y qu cosas no funcionaban tan
bien en Java, por lo que aprovech esos conocimientos. Desde su concepcin, es la primera vez que Java se ha encontrado
con un verdadero competidor. Como resultado, los diseadores de Java en Sun han analizado intensivamente C# y las razo-
nes por las que un programador podra sentirse tentado a adoptar ese lenguaje, y han respondido introduciendo significati-
vas mejoras en Java, que han resultado en el lanzamiento de Java SE5.
Actualmente, la debilidad principal y el problema ms importante en relacin con .NET es si Microsoft pennitir portarlo
completamente a otras plataformas. Ellos afIrman que no hay ningn problema para esto, y el proyecto Mono (www.go-
mono.com) dispone de una implementacin parcial de .NET sobre Linux, pero hasta que la implementacin sea completa y
Microsoft decida no recortar ninguna parte de la misma, sigue siendo una apuesta arriesgada adoptar .NET como solucin
interplataforma.
Introduccin a los objetos 21
Redes Internet e intranet
La Web es la solucin ms general para el problema de las arquitecturas cliente/servidor, por lo que tiene bastante sentido
utilizar esta misma tecnologa para resolver un cierto subconjunto de ese problema: el problema clsico de las arquitecturas
cliente/servidor internas a una empresa. Con las tcnicas tradicionales cliente/servidor, nos encontramos con el problema de
la existencia de mltiples tipos de computadoras cliente, as como con la dificultad de instalar nuevo software de cliente;
los exploradores web y la programacin del lado del cliente permiten resolver fcilmente ambos problemas. Cuando se uti-
liza tecnologa web para una red de infonnacin restringida a una empresa concreta, la arquitectura resultante se denomina
intranet. Las intranets proporcionan un grado de seguridad mucho mayor que Internet, ya que podemos controlar fisicamen-
te el acceso a los equipos de la empresa. En trminos de formacin, una vez que los usuarios comprenden el concepto gene-
ral de explorador les resulta mucho ms fcil asumir las diferencias de aspecto entre las distintas pginas y applets, por lo
que la curva de aprendizaje para los nuevos tipos de sistemas se reduce.
El problema de seguridad nos permite analizar una de las divisiones que parecen estarse formando de manera automtica en
el mundo de la programacin del lado del cliente. Si nuestro programa se est ejecutando en Internet no sabemos en que pla-
taforma se ejecutar y adems es necesario poner un cuidado adicional en no diseminar cdigo que contenga errores. En
estos casos, es necesario disponer de un lenguaje interplatafonna y seguro, como por ejemplo, un lenguaje de script o Java.
S nuestra aplicacin se ejecuta en una intranet, es posible que el conjunto de restricciones sea distinto. No resulta extrao
que todas las mquinas sean plataformas Tntel/Windows. En una intranet, nosotros somos responsables de la calidad de nues-
tro propio cdigo y podemos corregir los errores en el momento en que se descubran. Adems, puede que ya dispongamos
de una gran cantidad de cdigo heredado que haya estado siendo utilizado en alguna arquitectura cliente/servidor ms tra-
dicional , en la que es necesario instalar fi sicamente los programas cliente cada vez que se lleva a cabo una actualizacin. El
tiempo que se pierde a la hora de instalar actualizaciones es, precisamente, la principal razn para comenzar a utilizar explo-
radores, porque las actualizaciones son invisibles y automticas (Java Web Start tambin constituye una solucin a este
problema). Si trabajamos en una intranet de este tipo, la solucin ms lgica consiste en seguir la ruta ms corta que nos
permita utilizar la base de cdigo existente, en lugar de volver a escribir todos los programa en un nuevo lenguaje.
Al enfrentarse con este ampl io conjunto de soluciones para los problemas de la programacin del lado del cliente, el mejor
plan de ataque consiste en realizar un anli sis de coste-beneficio. Considere las restricciones que afectan a su problema y
cul sera la ruta ms corta para encontrar una solucin. Puesto que la programacin del lado del cliente sigue siendo una
programacin en sentido tradicional, siempre resulta conveniente adoptar el enfoque de desarrollo ms rpido en cada situa-
cin concreta. sta es la mejor manera de prepararse para los problemas que inevitablemente encontraremos a la hora de
desarrollar los programas.
Programacin del lado del servidor
En nuestro anlisis, hemos ignorado hasta ahora la cuestin de la programacin del lado del servidor, que es probablemen-
te donde Java ha tenido su xito ms rorundo. Qu sucede cuando enviamos una solicitud a un servidor? La mayor parte
de las veces, la solicitud dice simplemente "Envame este archivo". A continuacin, el explorador interpreta el archivo de
la forma apropiada, como pgina HTML, como imagen, como UI1 applel de Java, como programa en lenguaje de scripl, etc.
Las solicitudes ms complicadas dirigidas a los servidores suelen implicar una transaccin de base de datos. Una situacin
bastante comn consiste en enviar una sol icitud para que se realice una bsqueda completa en una base de datos, encargn-
dose a continuacin el servidor de dar formato a los resultados como pgina HMTL y enviar sta al explorador (por supues-
to, si el cliente dispone de un mayor grado de inteli gencia, gracias a la utilizacin de Java o de un lenguaje de script, pueden
enviarse los datos en bruto y formatearlos en el extremo cliente, lo que sera ms rpido e impondra una menor carga de
trabajo al servidor). Otro ejemplo: puede que queramos registrar nuestro nombre en una base de datos para unimos a un
grupo o realizar un pedido, lo que a su vez implica efectuar modificaciones en la base de datos. Estas solicitudes de base de
datos deben procesarse mediante algn tipo de cdigo situado en el lado del cliente; es a este tipo de programas a los que
nos referimos a la hora de hablar de programacin del lado del cliente. Tradicionalmente, la programacin del lado del clien-
te se llevaba a cabo utilizando Perl, Python, C++, o algn otro lenguaje para crear programas CG!, pero con el tiempo se
han desarrol1ado otros sistemas ms sofisticados, entre los que se incluyen los servidores web basados en Java que permi-
ten realizar todas las tareas de programacin del lado del servidor en lenguaje Java, escribiendo lo que se denomina servlets.
Los servlets y sus descendientes, las pginas JSP, son dos de las principal es razones por las que las empresas que desarro-
llan sitios web estn adoptando Java, especialmente porque dichas tecnologas eliminan los problemas derivados de tratar
22 Piensa en Java
con exploradores que dispongan de capacidades diferentes. Los temas de programacin del lado del servidor se tratan en
Thinking in Enterprise Java en el sitio web www.MindView.net.
A pesar de todo lo que hemos comentado acerca de Java y de Internet, Java es un lenguaje de programacin de propsito
general, que pennite resolver los mismos tipos de problemas que podemos resolver con otros lenguaj es. En este sentido,
la ventaja de Java no radica slo en su portabilidad, sino tambi n en su programabilidad, su robustez, su amplia biblio-
teca estndar y las numerosas bibliotecas de otros fabricantes que ya estn disponibles y que continan siendo desarro-
lladas.
Resumen
Ya sabemos cul es el aspecto bsico de un programa procedimental: definiciones de datos y llamadas a funciones. Para
comprender uno de esos programas es preciso analizarlo, examinando las llamadas a funcin y util izando conceptos de bajo
nivel con el fin de crear un modelo mental del programa. sta es la razn por la que necesitamos representaciones inter-
medias a la hora de disear programas procedimentales: en s mismos, estos programas tienden a ser confusos, porque se
utiliza una forma de expresarse que est ms orientada hacia la computadora que hacia el programa que se trata de resolver.
Como la programacin orientada a objetos aade numerosos conceptos nuevos, con respecto a los que podemos encontrar
en un lenguaje procedimental, la intuicin nos dice que el programa Java resultante ser ms complicado que el programa
procedimental equivalente. Sin embargo, la realidad resulta gratamente sorprendente: un programa Java bien escrito es,
generalmente, mucho ms si mple y mucho ms fcil de comprender que Wl programa procedimentaL Lo que podemos ver
al analizar el programa son las definiciones de los objetos que representan los conceptos de nuestro espacio de problema (en
lugar de centrarse en la representacin realizada dentro de la mquina),junto con mensajes que se envan a esos objetos para
representar las actividades que tienen lugar en ese espacio de problema. Uno de los atractivos de la programacin orienta-
da a objetos, es que con un programa bien diseado resulta fcil comprender el cdigo sin ms que leerlo. Asimismo, suele
haber una cantidad de cdigo bastante menor, porque buena parte de los problemas puede resolverse reuti lizando cdigo de
las bibliotecas existentes.
La programacin orientada a objetos y el lenguaje Java no resultan adecuados para todas las situaciones. Es importante eva-
luar cules son nuestras necesidades reales y detenninar si Java permitir satisfacerlas de fonna ptima o si, por el contra-
rio, es mejor emplear algn otro sistema de programacin (incluyendo el que en la actualidad estemos usando). Si podemos
estar seguros de que nuestras necesidades van a ser bastante especi alizadas en un futuro prximo, y si estamos sujetos a res-
tricciones especficas que Java pueda no satisfacer, resulta recomendable investigar otras alternativas (en particular, mi reco-
mendacin sera echarle un vistazo a Python; vase www.Python.org). Si decide, a pesar de todo, utilizar el lenguaje Java,
al menos comprender, despus de efectuado ese anli sis, cules sern las opciones existentes y por qu resultaba conve-
niente adoptar la decisin que ftnalmente haya tomado.
Todo es un objeto
"Si hablramos un lenguaj e diferente, percibiramos un mundo algo distinto".
Ludwig Wittgenstein (1889-1951)
Aunque est basado en C++, Java es un lenguaje orientado a objetos ms "puro".
Tanto e++ como Java son lenguajes hbridos, pero en Java los diseadores pensaron que esa hibridacin no era tan impor-
tante con en C++. Un lenguaje hbrido pennite utilizar mltiples estilos programacin; la razn por la que e++ es capaz de
soportar la compatibilidad descendente con el lenguaje C. Puesto que e++ es un superconjunto del lenguaje C. incluye
muchas de las caractersticas menos deseables de ese lenguaje, lo que hace que algunos aspectos del e++ sean demasiado
complicados.
El lenguaje Java presupone que el programador slo quiere realizar programacin oricntada a objetos. Esto quiere decir que.
antes de empezar, es preciso cambiar nuestro esquema mental al del mundo de la orientacin a objetos (a menos que ya
hayamos efectuado esa transicin). La ventaja que se obtiene gracias a este esfuerzo adicional es la capacidad de programar
en un lenguaje que es ms fcil de aprender y de utilizar que muchos otros lenguajes orientados a objetos. En este captulo
veremos los componentes bsicos de un programa Java y comprobaremos que (casi) todo en Java es un objeto.
Los objetos se manipulan mediante referencias
Cada lenguaje de programacin di spone de sus propios mecanismos para manipular los elementos almacenados en memo-
ria. En ocasiones. el programador debe ser continuamente consciente del tipo de manipulacin que se est efectuando.
Estamos tratando con el elemento directamente o con algn tipo de representacin indirecta (un puntero en C o C++), que
haya que tratar con una sintaxis especial?
Todo esto se simplifica en Java. En Java, todo se trata como un objeto, utilizando una nica sintaxis coherente. Aunque f,.o-
lamas todo como un objeto) los identificadores que manipulamos son en realidad "referencias" a objetos.
l
Podramos ima-
ginarnos una TV (el objeto) y un mando a distancia (la referencia): mientras di spongamos de esta referencia tendremos una
conexin con la televisin, pero cuando alguien nos dice "cambia de canal" o "baja el volumen", lo que hacemos es mani-
pular la referencia, que a su vez modifica el objeto. Si queremos movemos por la habitacin y continuar controlando la TV,
llevamos con nosotros el mando a distancia/referencia, no la televisin.
I Este punto puede suscitar enconados debates. Ilay personas que sostienen que "claramente se lrala de un puntero", pero esto esta presuponiendo una
detenninada implemcntacin subyacente. Asimismo. las referencias en Ja\a se parecen mucho mas sintacticamcnte a las referencias C++ que a los punte-
ros. En la primera edicin de este libro decidi utilizar eltcmlino "descriptor" porque las referencias C++ y las referencias Java tienen diferencias notables.
Yo mismo provena del mundo del lenguaje C++ y no quera confundir a los programadores de C++. que supona que constituiran la gran mayora de per-
sonas interesadas en el lenguaje Java. En la segunda edicin, decid que "referencia" era eltmlino ms comnmente utilizado. y que cualquiera que pro-
viniera del mundo de C++ iba a enfrentarse a problemas mucho mas graves que la tenninologa de las referencias. por lo que no tena sentido usar una
palabra distinta. Sin embargo. hay personas que estn en desacucrdo incluso con el tnnino "referencia". En un detemlinado libro. pude leer quc "rcsultu
completamentc equivocado decir que Java soporta el paso por referencia". o que los identificadores de los objetos Java (de acuerdo con el autor del libro)
son en realidad "referencias a objctos". Por lo que (contina el autor) todo se pasa e" la prctica por valor. Segn esle autor, no se efecta un paso por
referencia, sino quc se "pasa una referencia a objeto por \'<llor". Podramos discutir acerca de la precisin de estas complicadas explicaciones, pero creo
que el enfoque que he adoptado en este libro simpli fica la compresin del concepto sin generar ningn tipo de problema (los puristas del lenguaje po-
dran sostener que cstoy mintiendo. pero a ~ o respondera que lo que estoy haciendo es proporcionar una abstraccin apropiada).
24 Piensa en Java
Asimismo, el mando a distancia puede existir de manera independiente, sin necesidad de que exista una televisin. En otras
palabras, el hecho de que dispongamos de una referencia no implica necesariamente que haya un objeto conectado a la
misma. De este modo, si querernos almacenar una palabra o una frase podemos crear una referencia de tipo String:
String s;
Pero con ello slo habremos creado la referencia a un objeto. Si decidiramos enviar un mensaje a s en este punto, obten-
dramos un error porque s no est asociado a nada (no hay televisin). Por tanto, una prctica ms segura consiste en inicia-
lizar siempre las referencias en el momento de crearlas:
String s = "asdf" ;
Sin embargo, aqu estamos utilizando una caracterstica especial de Java. Las cadenas de caracteres pueden inicializarse
con un texto entre comillas. Normalmente, ser necesario emplear un tipo ms general de inicializacin para los restantes
objetos.
Es necesario crear todos los objetos
Al crear una referencia, lo que se desea es conectarla con un nuevo objeto. Para ello, en general , se emplea el operador new.
La palabra clave ne\\' significa: "Crea un nuevo ejemplar de este tipo de objeto", Por tanto, en el ejemplo anterior podra-
mos escribir:
String s = new String ( "asdf" );
Esto no slo di ce: "Crea un nuevo objeto String", sino que tambin proporciona infonnacin acerca de cmo crear el obje-
to suministrando una cadena de caracteres ini cial.
Por supuesto, Java incluye una pltora de tipos predefinidos, adems de String. Lo ms importante es que tambin pode-
mos crear nuestros propios tipos. De hecho, la creacin de nuevos tipos es la actividad fundamental en la programacin Java,
yeso es precisamente lo que aprenderemos a hacer en el resto del libro.
Los lugares de almacenamiento
Resulta til tratar de visualizar la fonna en que se dispone la infonnacin mientras se ejecuta el programa; en particular, es
muy til ver cmo est organizada la memoria. Disponemos de cinco lugares distintos en los que almacenar los datos:
l. Registros. Se trata del tipo de almacenamiento ms rpido, porque se encuentra en un lugar distinto al de los dems
tipos de almacenamiento: dentro del procesador. Sin embargo, el nmero de registros est muy limitado, por lo que
los regi stros se asignan a medida que son necesarios. No disponemos de un control directo sobre los mismos, ni tam-
poco podremos encontrar en los programas que los registros ni siquiera existan (e y C++, por el contrario, penniten
sugerir al compilador que el almacenamiento se haga en un registro).
2. La pila. Esta zona se encuentra en el rea general de memoria de acceso aleatorio (RAM), pero el procesador pro-
porciona un soporte directo para la pila, gracias al pl/nlero de pila. El puntero de pila se desplaza hacia abajo para
crear nueva memoria y bacia arriba para liberarla. Se trata de una fonna extraordinariamente rpida y eficiente de
asignar espacio de almacenamiento, slo superada en rapidez por los registros. El sistema Java debe conocer, a la
hora de crear el programa, el tiempo de vida exacto de todos los elementos que se almacenan en la pila. Esta restric-
cin impone una serie de limites a la flexibilidad de los programas, por lo que aunque parte del almacenamiento den-
tro de Java se lleva a cabo en la pila (en concreto, las referencias a objetos), los propios objetos Java no se colocan
nunca en la pila.
3. El cmulo. Es un rea de memoria de propsito general (tambin situada dentro de la RAM) en la que se almacenan
todos los objetos Java. El aspecto ms atractivo del cmulo de memoria, a diferencia de la pila, es que el compila-
dor no necesita conocer de antemano durante cunto tiempo se va a ocupar ese espacio de almacenamiento dentro
del cmulo. Por tanto, disponemos de un alto grado de flexibilidad a la hora de utilizar el espacio de almacenamien-
to que el cmulo de memoria proporciona. Cada vez que hace falta un objeto, simplemente se escribe el cdigo para
crearlo utilizando new, y el espacio de almacenamiento correspondiente en el cmulo se asigna en el momento de
ejecutar el cdigo. Por supuesto, esa flexibilidad tiene su precio: puede que sea necesario un tiempo ms largo para
asignar y liberar el espacio de almacenamiento del cmulo de memoria, si lo comparamos con el tiempo necesario
2 Todo es un objeto 25
para el almacenamiento en la pila (eso suponiendo que pudiramos crear objelOs en la pila en Java, al igual que se
hace en e++).
4. Almacenamiento constante. Los valores constantes se suelen situar directamente dentro del cdigo de programa, lo
que resulta bastante seguro, ya que nunca varan. En ocasiones, las constantes se almacenan por separado, de fcnna
que se pueden guardar opcionalmente en la memoria de slo lectura (ROM, read only mem01Y), especialmente en los
sistemas integrados.
2
5. Almacenami ento fuera de la RAM. Si los datos residen fuera del programa, podrn continuar existiendo mientras
el programa no se est ejecutando, sin que el programa tenga control sobre los mismos. Los dos ejemplos principa-
les son los objetos stream, en los que los objetos se transfonnan en flujos de bytes, generalmente para enviarlos a
otra mquina, los objetos persislenles en los que los objetos se almacenan en disco para que puedan conservar su
estado incluso despus de tenninar el programa. El truco con estos tipos de almacenamiento consiste en transfonnar
los objetos en algo que pueda existir en el otro medio de almacenamiento, y que, sin embargo, pueda recuperarse
para transfonnarlo en un objeto nonnal basado en RAM cuando sea necesario. Java proporciona soporte para lo que
se denomina persistencia ligera y otros mecanismos tales como JDSC e Hibernate proporcionan soporte ms sofis-
ticado para almacenar y extraer objetos util izando bases de datos.
Caso especial: tipos primitivos
Hay un grupo de tipos que se emplean muy a menudo en programacin y que requieren un tratamiento especial. Podemos
considerarlos como tipos "primitivos". La razn para ese tratamiento especial es que crear un objeto con new, una variable
simple de pequeo tamao, no resulta muy eficiente, porque new almacena los objetos en el cmulo de memoria. Para estos
tipos primitivos, Java utiliza la tcnica empleada en Cy C++; es decir, en lugar de crear la variable con new, se crea una
variable "automtica" que no es una referencia. La variable almacena el valor directamente y se coloca en la pila, por lo que
resulta mucho ms eficiente.
Java detennina el tamao de cada tipo primitivo, Estos tamaos no cambian de una arquitectura de mquina a otra, a dife-
rencia de lo que sucede en la mayora de los lenguajes. Esta invariabilidad de los tamaos es una de las razones por la que
los programa Java son ms portables que los programas escritos en la mayora de los dems lenguajes.
Tipo primitivo Tamao Mnimo Mximo Tipo envoltorio
boolean -
- -
Bool ean
char 16 bits Unicode O Unicode 2
16
- 1 Character
byte 8 bits -128 + 127 Byte
sbort 16 bits
_ 215
+2
15
_ 1 Shorl
int 32 bits - 2"
+2
31
_ 1 Integer
long 64 bits
_ 263
+2
63
_1
Long
fl oat 32 bits IEEE754 IEEE754 Float
double 64 bits IEEE754 IEEE754 Doubtc
void -
- -
Void
Todos los tipos numricos tienen signo. por lo que Java no podr encontrar ningn tipo sin signo,
El tamao del tipo bool ean no est especificado de manera explcita; tan slo se define para que sea capaz de aceptar los
valores literales t r ue o false,
Las clases "envoltorio" para los tipos de datos primitivos penniten definir un objeto no primitivo en el cmulo de memoria
que represente a ese tipo primitivo, Por ejemplo:
'2 Un ejemplo sera el conjunto dc cadenas de caracteres. Todas las cadenas literales y constantes que tengan un valor de tipo carcter se toman de (anna
automtica y se asignan a un almacenamiento esttico especial.
26 Piensa en Java
ehar e = 'x';
Charaeter eh = new Charaeter (e ) ;
o tambin podra emplear:
Charaeter eh = new Character {'x' )
La caracterstica de Java SES denominada allloboxing permite realizar automticamente la conversin de un tipo primitivo
a un tipo envoltorio:
Charaeter eh = 'x';
y a la inversa:
ehar e = eh;
En un captulo posterior veremos las razones que existen para utilizar envoltorios con los tipos primitivos.
Arimtica de alta precisin
Java incluye dos clases para reali zar operaciones aritmticas de alta precisin: BigJnteger y BigDecimal . Aunque estas cIa-
ses caen aproximadamente en la misma categora que las clases envoltorio, ninguna de las dos dispone del correspondiente
tipo primitivo.
Ambas clases disponen de mtodos que proporcionan operaciones anlogas a las que se realizan con los tipos primitivos.
Es decir, podemos hacer con un objeto Biglnteger o BigDecimal cualquier cosa que podamos hacer con una variable nt
o float ; simplemente deberemos utili zar llamadas a mtodos en lugar de operadores. Asimismo, como son ms comple-
jas, las operaciones se ejecutarn ms lentamente. En este caso, sacrificamos parte de la velocidad en aras de la preci-
sin.
Biglnteger soporta nmeros enteros de precisin arbitraria. Esto quiere decir que podemos representar valores enteros de
cualquier tamao sin perder ninguna infonnacin en absoluto durante las operaciones.
BigDecimal se utiliza para nmeros de coma fija y precisin arbitraria. Podemos utilizar eS(Qs nmeros, por ejemplo, para
realizar clculos monetarios precisos.
Consulte la documentacin del kit JOK para conocer ms detalles acerca de los constructores y mtodos que se pueden invo-
car para estas dos clases.
Matrices en Java
Casi todos los lenguajes de programacin soportan algn tipo de matriz. La utilizacin de matri ces en e y e++ es peligro-
sa, porque dichas matrices son slo bloques de memoria. Si un programa accede a la matriz fuera de su correspondiente blo-
que de memoria o utiliza la memoria antes de la inicializacin (lo cual son dos errores de programacin comunes), los
resultados sern impredecibles.
Uno de los principales objetivos de Java es la seguridad, por lo que en Java no se presentan muchos de los problemas que
aquejan a los programadores en e y C++. Se garantiza que las matrices Java siempre se inicialicen y que no se pueda acce-
der a ellas fuera de su rango autorizado. Las comprobaciones de rango exigen pagar el precio de gastar una pequea canti-
dad de memoria adicional para cada matriz, as como de verificar el ndice en tiempo de ejecucin, pero se supone que el
incremento en productividad y la mejora de la seguridad compensan esas desventajas (y Java puede en ocasiones optimizar
estas operaciones).
Cuando se crea una matriz de objetos, lo que se crea realmente es una matriz de referencias, y cada una de esas matrices se
inicializa automticamente con un valor especial que tiene su propia palabra clave: nuU. Cuando Java se encuentra un va lor
null , comprende que la referencia en cuestin no est apuntando a ningn objeto. Es necesario asignar un objeto a cada refe-
rencia antes de utilizarla, y si se intenta lIsar una referencia que siga teniendo el valor null , se infonnar del error en tiem-
po de ejecucin. De este modo, en Java se evitan los errores comunes relacionados con las matrices.
Tambin podemos crear una matriz de valores primitivos. De nuevo, el compil ador se encarga de garantizar la iniciali za-
cin, rellenando con ceros la memoria correspondiente a dicha matriz.
Hablaremos con detalle de las matrices en captulos posteriores.
2 Todo es un objeto 27
Nunca es necesario destruir un objeto
En la mayora de los lenguajes de programacin, el concepto de tiempo de vida de una variable representa una parte signi -
ficativa del esfuerzo de programacin. Cunto va a durar la variable? Si hay que destruirla, cundo debemos hacerl o? La
confusin en lo que respecta al tiempo de vida de las variables puede generar una gran cantidad de errores de programacin,
yen esta seccin vamos a ver que Java si mplifica enonnemente este problema al encargarse de realizar por nosotros todas
las tareas de limpieza,
mbito
La mayora de los lenguajes procedimentales incluyen el concepto de mbito. El mbito detemlina tanto la visibilidad como
el tiempo de vida de los nombres definidos dentro del mismo. En e, e++ y Java, el mbito est determinado por la coloca-
cin de las llaves n, de modo que, por ejemplo:
int x = 12;
II x
{
int q = 96;
II estn disponibles tanto x como q
}
II slo est disponible x
II q est "fuera del mbito"
Una variable definida dentro de un mbito slo est disponible hasta que ese mbito tennina.
Todo texto situado despus de los caracteres ' 11' y hasta el final de la lnea es un comentario.
El sangrado hace que el cdigo Java sea ms fcil de leer. Dado que Java es un lenguaje de fonnato libre. los espacios, tabu-
ladores y retornos de carro adicionales no afectan al programa resultante.
No podemos hacer lo siguiente, a pesar de que s es correcto en C y e++:
int x 12;
int x = 96; II Ilegal
El compilador nos indiearia que la variable x ya ha sido definida. Por tanto, no est permitida la posibilidad en e y e++ de
"ocultar" una variable en un mbito ms grande, porque los diseadores de Java pensaron que esta caracterstica haca los
programas ms confusos.
mbito de los objetos
Los objetos Java no tienen elmlsmo tiempo de vida que las primitivas. Cuando se crea un objeto Java usando ne\\' , ese obje-
to contina existiendo una vez que se ha alcanzado el final del mbito. Por tanto, si usamos:
String s = new String ( !la string");
II Fin del mbito
la referencia s desaparece al final del mbito. Sin embargo, el objeto String al que s estaba apuntando continuar ocupan-
do memoria. En este fragmento de cdigo, no hay fomla de acceder al objeto despus de alcanzar el ftnal del mbito. por-
que la nica referencia a ese objeto est ahora fuera de mbilO. En captulos posteriores veremos cmo pasar y duplicar una
referencia a un objeto durante la ejecucin de un programa.
Como los objelOs que creamos con nc\\' continuarn existiendo mientras queramos, hay toda una serie de problemas tpicos
de programacin en C++ que en Java se desvanecen. En C++ no slo hay que asegurarse de que los objetos permanezcan
mientras sean necesarios, sino que tambin es preciso destruirlos una vez que se ha tenninado de usarlos.
28 Piensa en Java
Esto suscita una cuestin interesante. Si los objetos continan existiendo en Java perpetuamente, qu es lo que evita llenar
la memoria y hacer que el programa se detenga? ste es exactamente el tipo de problema que poda ocurrir en C++, y para
resolverlo Java recurre a una especie de varita mgica. Java dispone de lo que se denomina depurador de memoria, que exa-
mina todos los objetos que hayan sido creados con new y determina cules no tienen ya ninguna referencia que les apunte.
A continuacin, Java libera la memoria correspondiente a esos objetos, de forma que pueda ser utilizada para crear otros
objetos nuevos. Esto significa que nunca es necesario preocuparse de reclamar explcitamente la memoria. Basta con crear
los objetos y, cuando dejen de ser necesarios, se borrarn automticamente. Esto elimina un cierto tipo de problema de pro-
gramacin: las denominadas "fugas de memoria" que se producen cuando, en otros lenguajes, un programador se olvida de
liberar la memoria.
Creacin de nuevos tipos de datos: class
Si todo es un objeto, qu es lo que detennina cmo se comporta y qu aspecto tiene una clase concreta de objeto? Dicho
de otro modo, qu es lo que establece el tipo de un objeto? Cabra esperar que existiera una palabra clave denominada
"type" (tipo), y de hecho tendria bastante sentido. Hi stricamente, sin embargo, la mayora de los lenguajes orientados a
objetos han utilizado la palabra clave class para decir "voy a definir cul es el aspecto de un nuevo tipo de objeto". La pala-
bra clave class (que es tan comn que la escribiremos en negrita nonnalmente a lo largo del libro) va seguida del nombre
del nuevo tipo que queremos definir. Por ejemplo:
class ATypeName { / * Aqu ira el cuerpo de la clase */ }
Este cdigo permite definir un nuevo tipo, aunque en este caso el cuerpo de la clase slo incluye un comentario (las barras
inclinadas y los asteriscos junto con el texto forman el comentario, lo que veremos en detalle ms adelante en este captu-
lo), as que no es mucho lo que podemos hacer con esta clase que acabamos de definir. Sin embargo, s que podemos crear
un objeto de este tipo utilizando ne\\' :
ATypeName a = new ATypeName{)
Si bien es verdad que no podemos hacer que ese objeto lleve a cabo ninguna tarea til (es decir, no le podemos enviar nin-
gn mensaje interesante) hasta que definamos algunos mtodos para ese objeto.
Campos y mtodos
Cuando se define una clase (y todo lo que se hace en Java es definir clases, crear objetos de esa clase y enviar mensajes a
dichos objetos), podemos incluir dos tipos de elementos dentro de la clase: campos (algunas veces denominados miembros
de datos) y mtodos (en ocasiones denominadosfimciones miembro). Un campo es un objeto de cualquier tipo con el que
podemos comunicamos a travs de su referencia o bien un tipo primiti vo. Si es Wla referencia a un objeto, hay que inicia-
lizar esa referencia para conectarla con un objeto real (usando new, corno hemos visto anterionnente).
Cada objeto mantiene su propio almacenamiento para sus campos; los campos nonnales no son compartidos entre los dis-
tintos objetos. Aqu tiene un ejemplo de una clase con algunos campos definidos:
class DataOnly
int i
double d
boolean b
Esta clase no hace nada, salvo almacenar una serie de datos. Sin embargo, podemos crear un objeto de esta clase de la forma
siguiente:
DataOnly data = new DataOnly()
Podemos asignar valores a los campos, pero primero es preciso saber cmo referimos a un miembro de un objeto. Para refe-
rimos a l , es necesario indicar el nombre de la referencia al objeto, seguida de un punto y seguida del nombre del miem-
bro concreto dentro del objeto:
objectReference.member
Por ejemplo:
data.i
data.d
data.b
47,
1.1 ;
false;
2 Todo es un objeto 29
Tambin es posible que el objeto contenga otros objetos, que a su vez contengan los datos que queremos modificar. En este
caso, basta con utilizar los pWltos correspondientes, como por ejemplo:
rnyPlane.leftTank.capacity = 100 ;
La clase DataOnly no puede hacer nada ms que almacenar datos, ya que no di spone de ningn mtodo. Para comprender
cmo funcionan los mtodos es preciso entender primero los conceptos de argumentos y valores de retorno, que vamos a
describir en breve.
Valores predeterminados de los miembros de tipo primitivo
Cuando hay un tipo de datos primitivo como miembro de una clase, Java garantiza que se le asignar un valor predetenni-
nado en caso de que no se inicialice:
Tipo primitivo Valor predeterminado
boolean fa lse
char ' \uOOOO' (null )
byte (byte)O
shol1 (,hOI1)O
int O
long OL
float O.Of
double O.Od
Los valores predetenninados son slo los valores que Java garantiza cuando se emplea la variable como miembro de lI11a
clase. Esto garantiza que las variables miembro de tipo primiti vo siempre sean inicializadas (a lgo que e++ no hace), redu-
ciendo as una fuente de posibles errores. Si n embargo, este valor inicial puede que no sea correcto o ni siquiera legal para
el programa que se est escribi endo. Lo mejor es ini cializar las variabl es siempre de forma explcita.
Esta garanta de ini cializacin no se apli ca a las variables locales, aquellas que no son campos de clase. Por tanto, si den-
tro de la definicin de un mtodo tuviramos:
int x;
Entonces x adoptara algn valor arbitrario (como en e y C++), no siendo automticamente iniciali zada con el valor cero.
El programador es el responsable de asignar un valor apropiado antes de usar x. Si nos olvidamos, Java representa de todos
modos una mejora con respecto a C++, ya que se obtiene un error en tiempo de compi lacin que nos informa de que la varia-
ble puede no haber sido inicializa (muchos compil adores de C++ nos advierten acerca de la exi stencia de variables no ini-
cializadas, pero en Java esta fa lta de ini ciali zacin const ituye un error).
Mtodos, argumentos y valores de retorno
En muchos lenguajes (como e y e++), el tnninofimcin se usa para describir una subrut ina con nombre. El trmino ms
comnmente utili zado en Java es el de mtodo, queriendo hacer referencia a una forma de " llevar algo a cabo". Si lo desea,
puede continuar pensado en trminos de funciones; se trata slo de una diferencia sintctica, pero en este libro adoptare-
mos el uso comn del trmino "mtodo" dentro del mundo de Java.
Los mtodos de Java determinan los mensajes que un objeto puede recibir. Las partes fundamentales de un mtodo son el
nombre, los argumentos, el tipo de retorno y el cuerpo. sta sera la fonna bsica de un mtodo:
30 Piensa en Java
TipoRet o rno NombreMetodo ( / * Lista de argumentos * / ) {
/ * Cuerpo del mtodo */
El tipo de retomo describe el valor devue lto por el mtodo despus de la invocacin. La lista de argumentos proporciona la
lista de los tipos y nombres de la infonnacin que hayamos pasado al mtodo. El nombre del mtodo y la lista de argumen-
tos (que f0n11an lo que se denomina signaTUra del mtodo) identifican de fanna univoca al mrodo.
Los mtodos pueden crearse en Java nicamente como parte de una clase. Los mtodos slo se pueden invocar para un
objeto
3
, y dicho objeto debe ser capaz de ejecutar esa invocacin del mtodo. Si tratamos de invocar un mtodo correcto
para un objeto obtendremos un mensaje de crror en tiempo de compi lacin. Para invocar un mlOdo para un objeto, hay que
nombrar el objeto seguido de un punto. seguido del nombre del mtodo y de su lista de argumentos, como por ejemplo:
NOmbreObjeto.NombreMetodo (argl, arg2, arg3 ) ;
Por ejemplo, suponga que tenemos un mtodo f( ) que no tiene ningn argumento y que devuelve una valor de tipo int .
Entonces, si tuviramos un objeto denominado a para el que pudiera invocarse f( ) podramos escribir:
int x = a.f O i
El tipo del va lor de retomo debe ser compatible con el tipo de x.
Este acto de invocar un mtodo se denomina comnmente enviar un mensaje a un objeto. En el ejemplo anterior, el men-
saje es f() Y el objeto es a. A menudo, cuando se quiere resumir lo que es la programacin orientada a objetos se suele decir
que consiste simplemente en "enviar mensajes a objetos".
La lista de argumentos
La li sta de argumentos del mtodo especifica cul es la infonnacin que se le pasa al mtodo. Como puede suponerse, esta
informacin (como todo lo dems en Java) adopta la f0n11a de objetos. Por tanto. lo que hay que especificar en la lista de
argumentos son los tipos de los objetos que hay pasar y el nombre que hay que utilizar para cada uno. Como en cualquier
otro caso dentro de Java en el que parece que estuviramos gestionando objetos. lo que en realidad estaremos pasando son
referencias
4
. Sin embargo. el tipo de la referencia debe ser correcto. Si el argumento es de tipo String, ser preciso pasar un
objeto String. porque de lo contrario el compilador nos dara un error.
Supongamos un cierto mtodo que admite un objeto St ring como argumento. Para que la compilacin se realice correcta-
mente. la definicin que habra que incluir dentro de la definicin de la clase sera como la siguiente:
int storage (String s )
return s . length () * 2 i
Este mtodo nos dice cuntos bytes se requieren para almacenar la infon118cin contenida en un objeto String concreto (el
tamaiio de cada char en un objeto Stri ng es de 16 bits, o dos bytes, para soponar los caracteres Unicode). El argumento es
de tipo Stri ng y se denomina s. Una vez que se pasa s al mtodo, podemos tratarlo como cualquier otro objeto (podemos
enviarle mensajes). Aqu, lo que se hace es invocar el mtodo lengt h(), que es uno de los mtodos definidos para los obje-
tos de tipo String; este mtodo devuelve el nmero de caracteres que hay en una cadena.
Tambin podemos ver en el ejemplo cmo se emplea la palabra clave return. Esta palabra clave se encarga de dos cosas:
en primer lugar. quiere decir "sal del mtodo, porque ya he tenninado". en segundo lugar, si el mtodo ha generado un valor,
ese valor se indica justo a continuacin de una instruccin return, en este caso, el valor de retomo se genera evaluando la
expresin s. length() * 2.
Podemos devolver un valor de cualquier lipo que deseemos, pero si no queremos devolver nada, tenemos que especificar-
lo, indicando que el mtodo devuelve un valor de tipo void. He aqu algunos ejemplos:
) Los mtodos de tipo statie. de los que pronto hablaremos. pueden invocarse para la e/ase, en lugar de para un objeto especifico .
. Con la e.xcepein usual de los tipos de datos "especiales" que antes hemos mencionado: boolean, ehar, byte, short, int , long, nout y double. En gene-
ral, sin embargo. to que se pasan son objetos, lo que realmente quiere decir que se pasan referencias a objetos.
boolean flag () { return true i }
double naturalLogBase () { return 2.718 i }
void nothing () { return; }
void nothing2 (1 {)
2 Todo es un objeto 31
Cuando el tipo de retorno es void, la palabra clave return se utiliza slo para salir del mtodo, por lo que resulta necesaria
una vez que se alcanza el final del mtodo. Podemos volver de un mtodo en cualquier punto, pero si el tipo de retomo es
distinto de "oid, entonces el compilador nos obligar (mediante los apropiados mensajes de error) a devolver un valor del
tipo apropiado, independientemente del lugar en el que salgamos del mtodo.
Llegados a este punto, podra parecer que un programa consiste, simplemente, en una serie de objetos con mtodos que acep-
tan otros objetos como argumentos y envan mensajes a esos otros objetos. Realmente. esa descripcin es bastante precisa,
pero en el siguiente captulo veremos cmo llevar a cabo el trabajo detallado de bajo nivel , tomando decisiones dentro de
un mtodo. Para los propsitos de este captulo, el envo de mensajes nos resultar ms que suficiente.
Construccin de un programa Java
Hay otras cuestiones que tenemos que entender antes de pasar a disear nuestro primer programa Java.
Visibilidad de los nombres
Uno de los problemas en cualquier lenguaje de programacin es el de control de los nombres. Si utilizamos un nombre en
un mdulo de un programa y otro programador emplea el mismo nombre en otro mdulo, cmo podemos distinguir un
nombre del otro y cmo podemos evitar la "colisin" de los dos nombres? En e, este problema es especialmente signifi-
cativo, porque cada programa es, a menudo, un conjunto manejable de nombres. Las clases e++ (en las que se basan las
clases Java) anidan las funciones dentro de clases para que no puedan colisionar con los nombres de funcin anidados den-
tro de otras clases. Sin embargo, C++ sigue pennitiendo utilizar datos globales y funciones globales, as que las colisiones
continan siendo posibles. Para resolver este problema, e++ introdujo los denominados espacios de nombres util izando
palabras clave adicionales.
Java consigui evitar todos estos problemas adoptando un enfoque completamente nuevo. Para generar un nombre no ambi-
guo para una biblioteca, los creadores de Java emplean los nombres de dominio de Int ernet en orden inverso. ya que se
garantiza que los nombres de dominio son unvocos. Puesto que mi nombre de dominio es MindView.net, una biblioteca
de utilidades llamada foibles se denominara, por ejemplo, net. mindview.utility.foibles. Despus del nombre de dominio
invertido, los puntos tratan de representar subdirectorios.
En Java 1.0 y Java 1.1 , las extensiones de dominio com, edu, org, net, etc., se escriban en maysculas por convenio, por
lo que el nombre de la biblioteca sera NET.mindview.utility.foibles. Sin embargo, durante el desarroIJo de Java 2 se des-
cubri que esto produca problemas, por lo que ahora los nombres completos de paquetes se escriben en minsculas.
Este mecanismo implica que todos los archivos se encuentran, automticamente, en sus propios espacios de nombres y que
cada clase dentro de un archivo tiene un identificador unvoco; el lenguaje se encarga de evitar automticamente las coli-
siones de nombres.
Utilizacin de otros componentes
Cada vez que se quiera utilizar una clase predefinida en un programa, el compilador debera saber cmo localizarla. Por
supuesto, puede que la clase ya exista en el mismo archivo de cdigo fuente desde el que se la est invocando. En ese caso,
basta con utilizar de manera directa la clase, an cuando esa clase no est detinida hasta ms adelante en el archivo (Java
elimina los problemas denominados de "referencia anticipada").
Qu sucede con las clases almacenadas en algn otro archivo? Cabra esperar que el compilador fuera lo suficientemente
imeligente como para localizar la clase, pero hay un pequeo problema. l magine que queremos emplear una clase con un
nombre concreto, pero que existe ms de una definicin para dicha clase (probablemente, defuciones distintas). O peor,
imagine que est escribiendo un programa y que a medida que lo escribe aiiade una nueva clase a la bibijoteca que entra en
conflicto COI1 el nombre de otra clase ya existente.
32 Piensa en Java
Para resolver este problema, es preciso eliminar todas las ambigedades potenciales. Esto se consigue infonnando al com-
pilador Java de exactamente qu clases se desea emplear, utilizando para ello la palabra clave importo import le dice al
compilador que cargue un paquete, que es una biblioteca de clases (en otros lenguajes, una biblioteca podra estar compues-
ta por funciones y datos, as como clases, pero recuerde que todo el cdigo Java debe escribirse dentro de una clase).
La mayor parte de las veces utilizaremos componentes de las bibliotecas estndar Java incluidas con el compilador.
Con estos componentes, no es necesario preocuparse sobre los largos nombres de dominio invertidos, basta con decir, por
ejemplo:
import java .util.ArrayList
para infonnar al compilador que queremos utilizar la clase ArrayList de Java. Sin embargo, util contiene di versas clases,
y podernos usar varias de eUas sin declararlas todas ellas explcitamente. Podemos conseguir esto fcilmente utilizando '*'
como comodn:
import java.util.*;
Resulta ms comn importar una coleccin de clases de esta fonna que importar las clases individualmente.
La palabra clave static
Nonnalmente, cuando creamos una clase, lo que estamos haciendo es describir el aspecto de los objetos de esa clase y el
modo en que stos van a comportarse. En la prctica, no obtenemos ningn objeto hasta que creamos uno empleando new,
que es el momento en que se asigna el almacenamiento y los mtodos del objeto pasan a estar di sponibles.
Existen dos situaciones en las que esta tcnica no resulta sufi ciente. Una de ellas es cuando deseamos di sponer de un nico
espacio de almacenamiento para un campo concreto, independientemente del nmero de objetos de esa clase que se cree, o
incluso si no se crea ningn objeto de esa clase. La otra situacin es cuando hace falta un mtodo que no est asociado con
ningn objeto concreto de esa clase; en otras palabras, cuando necesitamos un mtodo al que podamos invocar incluso aun-
que no se cree ningn objeto.
Podemos conseguir ambos efectos utilizando la palabra clave static. Cuando decimos que algo es static, quiere decir que
ese campo o mtodo concretos no estn li gados a ninguna instancia concreta de objeto de esa clase. Por tanto, an cuando
nunca creramos un objeto de esa clase, podramos de todos modos invocar el mtodo st atic o acceder a un campo stati c.
Con los campos y mtodos nonnales, que no tienen el atributo static, es preciso crear un objeto y usar ese objeto para acce-
der al campo o al mtodo, ya que los campos y mtodos que no son de tipo static deben conocer el objeto en particular con
el que estn trabajando.
5
Algunos lenguajes orientados a objetos utilizan los tnninos datos de la clase y mtodos de la clase, para indicar que esos
datos y mtodos slo existen para la clase como un todo, y no para ningn objeto concreto de esa clase. En ocasiones, en la
literatura tcnica relacionada con Java tambin se utilizan esos tnninos.
Para hacer que un campo o un mtodo sea static, basta con colocar dicha palabra clave antes de la definicin. Por ejemplo,
el siguiente cdigo generara un campo sta tic y le inicializara:
class StaticTest
static int i 47
Ahora, aunque creramos dos objetos StaticTest, existira un nico espacio de almacenamiento para StaticTest.i. Ambos
objetos compartiran el mi smo campo i. Considere el fragmento de cdigo siguiente:
StaticTest stl
StaticTest st2
new StaticTest()
new StaticTest();
En este punto, tanto st1.i corno st2.i tienen el mismo valor, 47, ya que ambos hacen referencia a la misma posicin de
memoria.
5 Por supuesto, puesto que los mtodos slatic no necesitan que se cree ningn objeto antes de utilizarlos, no pueden acceder directamente a ningn miem-
bro o mtodo que no sea stalic, por el procedimiento de invocar simplemente esos otros miembros sin hacer referencia a un objeto nominado (ya que los
miembros y mtodos que no son sto tic deben estar asociados con un objeto concreto).
2 Todo es un objeto 33
Hay dos fannas de hacer referencia a una variable sta tic. Como se indica en el ejemplo anterior, se la puede expresar a tra-
vs de un objeto, escribiendo por ejemplo, st2.i. Podemos hacer referencia a ella directamente a travs del nombre de la
clase, lo que no puede hacerse con ningn nombre que no sea de tipo sta tic.
StaticTest.i++
El operador ++ incrementa en una unidad la variable, En este punto, tanto st1.i como st2.i tendrn el valor 48.
La fomla preferida de hacer referencia a una variable stane es utilizando el nombre de la clase. Esto no slo pennite enfa-
tizar la naturaleza de tipo static de la variable, sino que en algunos casos proporciona al compilador mejores oportunidades
para la optimizacin.
A los mtodos statie se les apli ca una lgica similar. Podemos hacer referencia a un mtodo statie a travs
de un objeto, al igual que con cualquier otro mtodo, o bien mediante la sintaxis adicional especial NombreClase.meto-
do(). Podemos definir un mtodo stane de manera a si milar:
class Incrementable {
static void increment () { StaticTest. i++; }
Podemos ver que el mtodo inerement() de lnerementable incrementa el dato i de tipo static utilizando el operador ++.
Podemos invocar increment( l de la forma tipica a travs de un objeto:
Incrementable sf = new Incrementable();
sf.increment() ;
0, dado que inerement( ) es un mtodo statie, podemos invocarlo directamente a travs de su clase:
Incrementable.increment () ;
Aunque statie, cuando se aplica a un campo, modifica de manera evidente la fonna en que se crean los datos (se crea un
dato global para la clase, a diferencia de los campos que no son sta tic, para los que se crea un campo para cada objeto),
cuando se aplica esa palabra clave a un mtodo no es tan dramtico. Un uso importante de static para los mtodos consis-
te en pemlitimos invocar esos mtodos sin necesidad de crear un objeto. Esta caracterstica resulta esencial, como veremos,
al definir el mtodo main( l , que es el punto de entrada para ejecutar una aplicacin.
Nuestro primer programa Java
Finalmente, vamos a ver nuestro primer programa completo. Comenzaremos imprimiendo una cadena de caracteres y a con-
tinuacin la fecha, empleando la clase Date de la biblioteca estndar de Java.
II HelloDate.java
import java.util.*;
public class HelloDate
public static void main{String[] args)
System.out.println{"Hello, it's: " );
System.out.println(new Date{;
Al principio de cada archivo de programa, es necesario incluir las instmcciones import necesarias para cargar las clases adi-
cionales que se necesiten para el cdigo incluido en ese archivo. Observe que hemos dicho "clases adicionales". La razn
es que existe una cierta biblioteca de clases que se carga automticamente en todo archivo Java: java.lang. Inicie el explo-
rador web y consulte la documentacin de Sun (si no ha descargado la documentacin del kit IDK de hllp://java.slIn.com,
hgalo ahora.
6
Observe que esta documentacin no se suministra con el IDK; es necesario descargarla por separado). Si con-
sulta la lista de paquetes, podr ver todas las diferentes bibliotecas de clases incluidas con Java. Seleccione java.lang; esto
har que se muestre una li sta de todas clases que fonnan parte de esa biblioteca. Puesto que java.lang est implcitamente
6 El compilador Java y la documentacin suministrados por Sun tienden a cambiar de manera frecuente, y el mejor lugar para obtenerlos cs directamente
en el sitio web de Sun. Descargandose esos archivos podr disponer siempre de la versin ms reciente.
34 Piensa en Java
en todo archivo de cdigo Java. dichas clases estarn disponibles de manera automtica. No existe ninguna clase Date en
java.lang, lo que quiere decir que es preciso importar otra biblioteca para usar dicha clase. Si no sabe la biblioteca en la que
se encuentra una clase concreta o si quiere ver todas las clases disponibles, puede seleccionar "Tree" en la documentacin
de Java. Con eso. podr localizar todas las clases incluidas con Java. Entonces, utilice la funcin "Buscar" del explorador
para encontrar Date. Cuando lo haga. podr ver que aparece como java.util.Date, lo que nos permite deducir que se encuen-
tra en la biblioteca util y que es necesario emplear la instruccin import java.util.* para usar Date.
Si vuelve atrs y selecciona java.lang y luego System, podr ver que la clase System tiene varios campos; si selecciona
out, descubrir que se trata de un objeto statie PrintStream. Puesto que es de tipo stane, no es necesario crear ningn obje-
to empleando new. El objeto out est siempre disponible, por lo que podemos usarlo directamente. Lo que puede hacerse
con este objeto out est detenninado por su tipo: PrintStream. En la descripcin se muestra PrintStream como un hiper-
vnculo, por lo que al hacer c1ic sobre l podr acceder a una lista de todos los mtodos que se pueden invocar para
PrintStream. Son bastantes mtodos, y hablaremos de ellos ms adelante en el libro. Por ahora, el nico que nos interesa
es println(), que en la prctica significa "imprime en la consola lo que te vaya pasar y termina con un carcter de avance
de lnea". Asi. en cualquier programa Java podemos escribir algo corno esto:
System.out.println("Una cadena de caracteres");
cuando queramos mostrar infonnacin a travs de la consola.
El nombre de la clase coincide con el nombre del archivo. Cuando estemos creando un programa independiente como ste,
una de las clases del archivo deber tener el mismo nombre que el propio archivo (el compilador se quejar si no lo hace-
mos as). Dicha clase debe contener un mtodo denominado main( ) con la siguiente signarura y tipo de retorno:
public static void main(String[] args)
La palabra clave public quiere decir que el mtodo est disponible para todo el mundo (lo que describiremos en detalle en
el Capnlio 6, Control de acceso). El argumento de main( ) es una matriz de objetos String. En este programa, no se em-
plean los argumentos (args), pero el compi lador Java insiste en que se incluya ese parmetro. ya que en l se almacenarn
los argumentos de la lnea de comandos.
La lnea que imprime la fecha es bastante interesante:
System.out .print ln(new Date();
El argumento es un objeto Date que slo se ha creado para enviar su valor (que se convierte automticamente al tipo String)
a println(). Una vez finalizada esta inslnlccin, dicho objeto Date resulta innecesario. y el depurador de memoria podr
encargarse de l en cualquier momento. Nosotros no necesitamos preocupamos de borrar el objeto.
Al examinar la documentacin del JDK en hflp: //java.sl/tI.com, podemos ver que System tiene muchos mtodos que nos
permiten producir muchos efectos interesantes (una de las caractersticas ms potentes de Java es su amplio conjunto de
bibliotecas estndar). Por ejemplo:
//: object/ShowProperties.java
public class ShowProperties {
public static void main (String [] args) {
System. getProperties () .list (Syst em. out) ;
System.out.println(System.getProperty("user.name")) ;
System.out.println(
System.getProperty("java.library.path")) ;
}
111,-
La primera lnea de main() muestra todas las "propiedades" del sistema en que se est ejecutando el programa, por lo que
nos proporciona la informacin del entorno. El mtodo list() enva los resultados a su argumento. System.out. Ms ade-
lant e en el libro veremos que los resultados pueden enviarse a cualquier parte, como por ejemplo, a un archivo. Tambin
podemos consultar una propiedad especifica, por ejemplo, en este caso, el nombre de usuario y java.library.path (un poco
ms adelante explicaremos los comentarios bastante poco usuales situados al principio y al final de este fragmento de
cdigo).
2 Todo es un objeto 35
Compilacin y ejecucin
Para compilar y ejecutar este programa. y cualquier otro programa de este libro, en primer lugar hay que tener un entorno
de programacin Java. Hay disponibles diversos entornos de programacin Java de Olros fabricantes, pero en el libro vamos
a suponer que el lector est utilizando el kit de desarrollo JDK (Java Developer's Kit) de Sun, el cual es gratuito. Si est uti-
lizando otro sistema de desarrollo
7
, tendra que consultar la documentacin de dicho sistema para ver cmo compilar y eje-
cutar los programas.
Acceda a Internet y vaya a hftp: //java.sun.com. All podr encontrar infon113cin y vnculos que le llevarn a travs del pro-
ceso de descarga e instalacin del JDK para su platafonna concreta.
Una vez instalado el JDK, y una vez modificada la infonnacin de ruta de la computadora para que sta pueda encontrar
javac y java, descargue y desempaquete el cdigo fuente correspondiente a este libro (puede encontrarlo en
WH'l I'.MindView.nel). Esto crear un subdirectorio para cada captulo del libro. Acceda al subdirectorio objects y escriba :
javac HelloDate.java
Este comando no debera producir ninguna respuesta. Si obtiene algn tipo de mensaje de error querr decir que no ha ins-
talado el JDK correctamente y tendr que investigar cul es el problema.
Por el contrario, si lo nico que puede ver despus de ejecutar el comando es una nueva lnea de comandos. podr escribir:
java HelloDate
y obtendr el mensaje y la fecha como salida.
Puede utilizar este proceso para compilar y ejecutar cada uno de los programas de este libro. Sin embargo, podr ver que el
cdigo fuente de este libro tambin di spone de un archivo denominado build.xml en cada captulo, que contiene comandos
"Ant" para construir automticamente los archivos correspondientes a ese captulo. Los arch.ivos de generacin y Ant (inclu-
yendo las instrucciones para descargarlos) se describen ms en detalle en el suplemento que podr encontrar en
hllp:// MindVielV.net/ Books/BellerJava, pero una vez que haya instalado Ant (desde hllp://jakara.apache. org/al1t) basta con
que escriba ' anf en la lnea de comandos para compi lar y ejecutar los programas de cada captulo. Si no ha instalado Ant
roda va, puede escribir los comandos javac y java a mano.
Comentarios y documentacin embebida
Existen dos tipos de comentarios en Java. El primero de ellos es el comentario de estilo C heredado de C++. Estos comen-
tarios comienzan con /* y cOlllinan, posiblemente a lo largo de varias lnea, hasta encontrar */. Muchos programadores
empiezan cada lnea de un comando multilnea con un *, por lo que resulta bastante comn ver comentarios como ste:
1* Este comentario
* ocupa varias
* lneas
, /
Recuerde, sin embargo, que en todo lo que hay entre /* y */ se ignora, por lo que no habra ninguna diferencia si dijramos:
1* Este comentario ocupa
varias lneas *1
El segundo tipo de comentario proviene de C++. Se trata del comentario monolnea que comienza con 11 y contina hasta
el fina l de la lnea. Este tipo de comentario es bastante cmodo y se suele utili zar a menudo debido a su sencillez. No es
necesari o desplazarse de un sitio a otro del teclado para encontrar el carcter / y luego el carcter * (en su lugar, se pulsa
la misma tecla dos veces), y tampoco es necesario cerrar el c-omentari o. As que resulta bastante habinlal encontrar comen-
tarios de este estil o:
II ste es un comentario monolnea.
7 El compilador "jikes" de IHM es una alternativa bastante comn, y es significativamente ms rpido que Java e de $un (aunque se estn construyendo
gnlpoS de archivos utilizando AIIf, la diferencia no es muy significati va). Tambin hay diversos proyectos de cdigo fuente abierto para crear compilado-
res Java, entornos de ejecucin y bibliOlecas.
36 Piensa en Java
Documentacin mediante comentarios
Posiblemente el mayor problema de la tarea de documentar el cdigo es el de mantener dicha documentacin. Si la docu-
mCI1Iacin y el cdigo estn separados, resulta bastante tedioso modificar la documentacin cada vez que se cambia el cdi-
go. La solucin parece si mple: integrar el cdigo con la documentacin. La fonna ms fcil de hacer esto es incluir todo en
un mi smo archivo. Sin embargo, para ello es necesario disponer de una sintaxi s especial de comentarios que pennita mar-
car la documentacin, as como de una herramienta para extraer dichos comentari os y fonnatearlos de manera til. Esto es
precisamenlc lo que Java ha hecho.
La herramienta para extraer los comentarios se denomina Javadoc, y fonna parte de la instalacin del IDK. Utiliza parte de
la tecnologa del compi lador l ava para buscar los marcadores especiales de comentari os incluidos en el programa. No slo
extrae la infom1acin sealada por dichos marcadores, si no que tambin extrae el nombre de la clase o el nombre del mto-
do asociado al comentario. De esta fonna, podemos generar una documentacin de programa bastante digna con una canti-
dad mnima de trabajo.
La sali da de lavadoc es un archivo HTML que se puede examinar con el explorador web. Javadoc pennite crear y mante-
ner un nico archivo fuente y generar automticamente una documentacin bastante til. Gracias a Javadoc, di sponemos de
un estndar bastante sencill o para la creacin de documentacin, por lo que podemos esperar, o incluso exigir, que todas las
bibliotecas Java estn documentadas.
Adems, podemos escribir nuestras propias rutinas de gestin lavadoc, denominadas doclets, si queremos realizar operacio-
nes especiales con la informacin procesada por lavadoc (por ejemplo, para generar la salida en un fonnato distinto). Los
doclets se explican en el supl emento que podr encontrar en http://MindVif!l,v.net/Books/BetterJava.
Lo que sigue es simplemente una introduccin y una breve panormica de los fundamentos de lavadoc. En la documenta-
cin del IDK podr encontrar una descripcin ms exhausti va. Cuando desempaquete la documentacin, vaya al subdirec-
torio "tooldoes" (o baga clic en el vinculo "tooldocs").
Sintaxis
Todos los comandos de lavadoc estn incluidos en comentari os que ti enen el marcador 1**. Esos comentari os tenninan con
*/ como es habitual. Existen dos fonnas principales de utili zar Javadoc: mediante HTML embebido o medi ante "marcado-
res de documentacin". Los marcadores de documentacin autnomos son comandos que comienzan con '@' y se incluyen
al principio de una linea de comentarios (sin embargo, un carcter '*' inicial ser ignorado) . Los marcadores de documen-
tacin en lnea pueden incluirse en cualquier punto de un comentario Javadoc y tambin comienzan con '@'. pero estn
encerrados entre llaves.
Existen tres "tipos" de documentacin de comentarios. que se corresponden con el elemento al que el comentario precede:
una clase, un campo o un mtodo. En otras palabras, el comentario de clase aparece justo antes de la definicin de la clase,
el comentario de campo aparece justo antes de la definicin de un campo y el comentario de un mtodo aparece justo antes
de la definicin del mismo. He aqu un senci llo ejemplo:
11: object/Documentationl.java
1** Comentario de clase * /
public class Documentationl {
1** Comentario de campo *1
public int i i
1** Comentario de mtodo *1
public void f () {}
/11 , -
Observe que Javadoc slo procesar la documentacin de los comentari os para los mi embros public y
protected. Los comentarios para los miembros private y los miembros con acceso de paquete (vase el Captulo 6, Control
de acceso) se ignoran, no generndose ninguna salida para ellos (si n embargo, puede utili zar el indi cador private para incluir
tambin la documentacin de los miembros private). Esto tiene bastante sentido, ya que slo los miembros de tipo public y
protected estn disponibles fuera del archivo, por lo que esto se ajusta a la perspectiva del programador de clientes.
La salida del cdigo anteri or es un archivo HTML que ti ene el mi smo fonnat o estndar que el resto de la documentacin
lava, por lo que los usuarios estarn acostumbrados a ese fonnato y podrn navegar fc ilmente a travs de las clases que
2 Todo es un objeto 37
hayamos definido. Merece la pena introducir el ejemplo de cdigo anterior, procesarlo mediante Javadoc y observar el
archivo HTML resultante, para ver el tipo de salida que se genera.
HTML embebido
Javadoc pasa los comentarios HTML al documento HTML generado. Esto nos pennite utilizar completamente las caracte-
rsticas HTML; sin embargo, el motivo principal de uso de ese lenguaje es dar formato al cdigo, como por ejemplo en:
/1 : object/Documentation2.java
/ **
* <pre>
* System.out.println (new Date ()) ;
* </pre>
* /
/1/ , -
Tambin podemos usar cdigo HTML como en cualquier otro documento web, para dar faonato al texto nomlal de las des-
cripciones:
ji : object / Documentat ion3. j ava
/ **
* Se puede <em>inc!uso</ em> insertar una lista :
* <01:;.
* <li:;. Elemento uno
* <li:;. Elemento dos
* <li:;. Elemento tres
* </ 01>
* /
11/ , -
Observe que, dentro del comentario de documentacin, los asteriscos situados al principio de cada lnea son ignorados por
Javadoc, junto con los espacios iniciales. Javadoc refonnatea todo para que se adapte al estilo estndar de la documenta-
cin. No utilice encabezados como <hl > o <hr> en el HTML embebido, porque Javadoc inserta sus propios encabezados
y los que nosotros incluyamos interferirn con ellos.
Puede utilizarse HTML embebido en todos los tipos de comentarios de documentacin: de clase, de campo y de mtodo.
Algunos marcadores de ejemplo
He aqu algunos de los marcadores Javadoc disponibles para la documentacin de cdigo. Antes de tratar de utilizar Javadoc
de fonna seria, consulte la documentacin de referencia de Javadoc dentro de )a documentacin del IDK, para ver las dife-
rentes formas de uso de Javadoc.
@see
Este marcador pennite hacer referencia a la documentacin de otras clases. Javadoc generar el cdigo HTML necesario,
hipervinculando los marcadores @see a los olros fragmentos de documentacin. Las fom13s posibles de uso de este marca-
dor son:
@see nombreclase
@see nombreclase-completamente-cualificado
@see nombreclase-completamente-cualificado #nombre-mtodo
Cada uno de estos marcadores aade una entrada "See Also" (Vase tambin) hipervinculada al archivo de documentacin
generado. Javadoc no comprueba los hipervnculos para ver si son vlidos.
{@Iink paquete.clase#miembro etiqueta}
Muy similar a @see, exceplO porque se puede ut ilizar en lnea y emplea la etiqueta como texto del bipervnculo, en lugar
de "See Also".
38 Piensa en Java
{@docRoot}
Genera la mta relativa al direclOrio raz de la documentaci n. Resulta til para introducir hipervnculos explcitos a pginas
del rbol de documentacin.
{@inheritDoc}
Este indicador hereda la documentacin de la clase base ms prxima de esta clase, insertndola en el comentario del docu-
mento actual.
@version
Tiene la fonna:
Version informacin-versi n
en el que informacin-versin es cualquier infonnacin significativa que queramos incluir. Cuando se aade el indicador
@version en la lnea de comandos Javadoc, la infonnacin de versin ser mostrada de manera especial en la documenta-
cin HTM L generada.
@author
Tiene la fonna:
@author informacin-autor
donde informacin-autor es, normalmente, nuestro nombre, aunque tambin podramos incluir nuestra direccin de correo
electrnico o cualqui er otra informacin apropiada. Cuando se incluye el indicador @author en la lnea de comandos
Javadoc, se inserta la infonnacin de autor de manera especial en la documentacin HTML generada.
Podemos incluir mltiples marcadores de autor para incluir una li sta de autores, pero es preciso poner esos marcadores de
forma consecutiva. Toda la informacin de autor se agrupar en un nico prrafo dentro del cdigo HTML generado.
@since
Este marcador pem1ite indicar la versin del cdigo en la que se empez a utilizar una caracterstica concreta. En la docu-
mentacin HTML de Java, se emplea este marcador para indicar qu versin del JDK se est utilizando.
@param
Se utiliza para la documentacin de mtodos y tiene la fonna:
@param nombre-parmetro descripcin
donde nombre-parmetro es el identificador dentro de la li sta de parmetros del mtodo y descripcin es un texto que
puede continuar en las lneas siguientes. La descripcin se considera tenninada cuando se encuentra un nuevo marcador de
documentacin. Puede haber mltipl es marcadores de este tipo, nonnalmente uno por cada parmetro.
@return
Se utiliza para documentacin de mtodos y su fonnato es el siguiente:
@return descripcin
donde descripcin indica el significado del valor de retomo. Puede conti nuar en las lineas sigui entes.
@throws
Las excepciones se estudian en el Captulo 12, Tratamiento de errores mediante ex.cepciones. Por resumir, se trata de obje-
tos que pueden ser "generados" en un mtodo si dicho mtodo falla. Aunque slo puede generarse un objeto excepcin cada
vez que invocamos un mtodo, cada mtodo concreto puede generar diferentes tipos de excepciones, todas las cuales habr
que describir, por lo que el formato del marcador de excepcin es:
@throws nombre-clase-completamente-cualificado descripcin
2 Todo es un objeto 39
donde nombre-c1ase-complelamente-cualijicado proporciona un nombre no ambiguo de una clase de excepcin definida en
alguna otra part e y descripcin (que puede ocupar las siguientes lineas) indica la razn por la que puede generarse este tipo
concreto de excepcin despus de la ll amada al mtodo.
@deprecated
Se utili za para indicar caractersti cas que han quedado obsoletas debido a la introduccin de alguna otra caracterstica me-
jorada. Este marcador indicativo de la obsolescencia recomienda que no se utilice ya esa caracterst ica concreta, ya que es
probable que en el futuro sea eliminada. Un mtodo marcado como @deprecated hace que el compi lador genere una adver-
tencia si se usa. En Java SE5, el marcador Javadoc @deprecated ha quedado susti tuido por la ano/acin @Dcprecated
(hablaremos de este tema en el Capitulo 20, Anoraciones).
Ejemplo de documentacin
He aqu de nuevo nuestro primer programa Java, pero esta vez con los comentarios de documentacin inc luidos:
JI : object/HelloDate . java
import java.util .*;
/ ** El primer programa de ejemplo del libro.
* Muestra una cadena de caracteres y la fecha actual.
* @author Bruce Eckel
* @author www.MindView.net
* @version 4.0
* /
public class HelloDate
/ ** Punto de entrada a la clase y a la aplicacin.
* @param args matriz de argumentos de cadena
1< @throws exceptions No se generan excepciones
* /
public static vOld main(String[] args)
System.out.println {"Hello, it ! s: 11 ) ;
System.out.println(new Date ()) ;
/ * Output: (5 5% match)
HelIo, it's:
Wed Oet 05 14:39:36 MDT 2005
* /// , -
La primera lnea del archivo utiliza una tcnica propia del autor del libro que consiste en incluir '//:' como marcador espe-
cial en la lnea de comentarios que cont iene el nombre del archivo fuentc. Dicha lnea contiene la infonn3cin de ruta del
archivo (object indica este captulo) seguida del nombre del archivo. La ltima lnea tambin teml ina con un comentari o, y
ste ('///:- ') indica el final del li stado de cdigo fuente, lo que pem1ite actualizarlo automti camente dentro del texto de este
libro despus de comprobarlo con un compilador y ejecutarlo.
El marcador 1* Output: indica el comienzo de la salida que generar este archi vo. Con esta fanna concreta, se puede com-
probar automticamente para verifi car su precisin. En este caso, el texto (550/0 match) indica al sistema de pruebas que la
salida ser bastante distinta en cada sucesiva ej ecucin del programa, por lo que slo cabe esperar un 55 por ciento de corre-
lacin con la salida que aqu se muestre. La mayora de los ejemplos del li bro que generan una salida contendrn dicha sali-
da comentada de esta fonma, para que el lector pueda ver la salida y saber que lo que ha obtenido es correcto.
Estilo de codificacin
El est il o descrito en el manual de convenios de cdigo para Java, Cade Convenlions jor rhe Java Programming Language
8
,
consiste en poner en mayscula la primera letra del nombre de una clase. Si el nombre de la clase est compuesto por varias
Para ahorrar espacio tanto en el libro como en las presentaciones de los seminarios no hemos podido seguir todas las directrices que se marcan en ese
texto. pero el lector podr ver que el estilo empleado en el libro se ajusta lo mximo posible al estndar recomendado en Java.
40 Piensa en Java
palabras, stas se escriben juntas (es decir, no se emplean guiones bajos para separarlas) y se pone en mayscula la prime-
ra letra de cada una de las palabras integrantes, como en:
class AIITheColorsOfTheRainbow { II ...
Para casi todos los dems elementos, como por ejemplo nombres de mtodos, campos (variables miembro) y referencias a
objetos, el estilo aceptado es igual que para las clases, salvo porque la primera letra del identificador se escribe en mins-
cula, por ejemplo:
class AIITheColorsOfTheRainbow (
int anlntegerRepresentingColors
void changeTheHueOfTheColor(int newHue )
II ...
)
II .. .
Recuerde que el usuario se ver obligado a escribir estos nombres tan largos, as que procure que su longitud no sea exce-
siva.
El cdigo Java que podr encontrar en las bibliotecas Sun tambin se ajusta a las directrices de ubicacin de llaves de aper-
tura y cierre que hemos utilizado en este libro.
Resumen
El objetivo de este captulo es explicar los conceptos mnimos sobre Java necesarios para entender cmo se escribe un pro-
grama senci llo. Hemos expuesto una panormica del lenguaje y algunas de las ideas bsicas en que se fundamenta. Sin
embargo, los ejemplos incluidos hasta ahora tenan todos ellos la fonna "haga esto, luego lo otro y despus lo de ms all".
En los dos captulos siguientes vamos a presentar los operadores bsicos utili zados en los programas Java, y luego mostra-
remos cmo controlar el flujo del programa.
Ejercicios
Normalmente, distribuiremos los ejercicios por todo el captulo, pero en ste estbamos aprendiendo a escribir programas
bsicos, por lo que decidimos dejar los ejercicios para el final.
El nmero entre parntesis incluido detrs del nmero de ejercicio es un indicador de la dificultad del ejercicio dentro de
una escala de 1-10.
Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico Tire Thinking in Java Annofafed
Solution Guide, a la venta en
Ejercicio 1:
Ejercicio 2:
Ejercicio 3:
Ejercicio 4:
Ejercicio 5:
Ejercicio 6:
(2) Cree una clase que contenga una variable int y otra char que no estn inicializadas e imprima sus valo-
res para verificar que Java se encarga de realizar una inicializacin predetenninada.
(1) A partir del ejemplo HelloDate.java de este captulo, cree un programa "helio, world" que simplemen-
te muestre dicha frase. Slo necesitar un mtodo dentro de la clase, (el mtodo "main" que se ejecuta al
arrancar el programa). Acurdese de defUlir este mtodo como static y de incluir la lista de argumentos,
incluso aunque no la vaya a utilizar. Compi le el programa con javac y ejectelo con java. Si est utilizan-
do otro entorno de desarrollo distinto de JDK, averige cmo compilar y ejecutar programas en dicho
entorno.
(1) Recopile los fragmentos de cdigo relacionados con ATypeName y transfnnelos en un programa que
se pueda compilar y ejecutar.
(1) Transforme los fragmentos de cdigo DataOnly en un programa que se pueda compilar y ejecutar.
(1) Modifique el ejercicio anterior de modo que los va lores de los datos en DataOnly se asignen e impri-
man en maine ).
(2) Escriba un programa que incluya e invoque el mtodo storage( ) definido como fragmento de cdigo
en el captulo.
2 Todo es un objeto 41
Ejercicio 7: (1) Transfonme los fragmentos de cdigo lncremenlable eo un programa funcional.
Ejercicio 8: (3) Escriba un programa que demuestre que, independientemente de cuntos objetos se creen de una clase
concreta, slo hay Ulla nica instancia de un campo static concreto defmido dentro de esa clase.
Ejercicio 9: (2) Escriba un programa que demuestre que el mecanismo automtico de conversin de tipos funciona
para todos los tipos primitivos y sus envoltori os.
Ejercicio 10: (2) Escriba un programa que imprima tres argumentos extrados de la lnea de comandos. Para hacer esto,
necesitar acceder con el ndice correspondiente a la matri z de objetos String extrada de la linea de
comandos.
Ejercicio 11: (1) Transfom1e el ejemplo AIITheColorsOfTheRainbow en un programa que se pueda compi lar y eje-
cutar.
Ejercicio 12:
Ejercicio 13:
Ejercicio 14:
Ejercicio 15:
Ejercicio 16:
(2) Localice el cdigo para la segunda versin de HelloDate.java, que es el ejemplo simple de documen-
tacin mediante comentarios. Ejecute Javadoc con ese archivo y compruebe los resultados con su explo-
rador web.
( 1) Procese Documentationl.java, Documentalion2.java y Documentation3.java con Javadoc. Veri-
fique la documentacin resultante con su explorador web.
( 1) Aada una lista HTML de elementos a la documentacin del ejercicio anterior.
(1) Tome el programa del Ejercicio 2 y aildale comentarios de documentacin. Extraiga esos comentarios
de documentacin Javadoc para generar un archi vo HTML y visualcelo con su explorador web.
( 1) En el Captulo 5, Inicializacin y limpieza, localice el ejemplo Overloading.java y aada documenta-
cin de tipo Javadoc. Extraiga los comentarios de documentacin Javadoc para generar un archi vo HTML
y visualcelo con su explorador web.
Operadores
En el nivel inferior, los datos en Java se manipulan utili zando operadores.
Puesto que Java surgi a partir de C++, la mayora de estos operadores les sern familiares a casi todos los programadores
de C y C++. Java ha aadido tambin algunas mejoras y ha hecho algunas simplificaciones.
Si est familiarizado con la sintaxi s de e o e++, puede pasar rpidamente por este captulo y el siguiente, buscando aque-
llos aspectos en los que Java se diferencie de esos lenguajes. Por el contrario, si le asaltan dudas en estos dos captulos,
repase el seminario multimedia Thinking in e, que puede descargarlo gratuitamente en www.MindView.nef. Contiene confe-
rencias, presentaciones, ejercicios y soluciones especficamente diseados para familiarizarse con los fundamentos necesa-
rios para aprender Java.
Instrucciones simples de impresin
En el captulo anterior, ya hemos presentado la instruccin de impresin en Java:
System. out. println ( "Un montn de texto que escribir " ) i
Puede observar que esto no es slo un montn de texto que escribir (10 que quiere decir muchos movimientos redundantes
de los dedos), sino que tambin resulta bastante incmodo de leer. La mayoria de los lenguaj es, tanto antes como despus
de Java, han adoptado una tcnica mucho ms sencilla para expresar esta instruccin de uso tan comn.
En el Captulo 6, Control de acceso se presenta el concepto de importacin esttica aadido a Java SES, y se crea una peque-
a biblioteca que pennite simplificar la escritura de las instrucciones de impresin. Sin embargo, no es necesario compren-
der los detalJes para comenzar a utilizar dicha biblioteca. Podemos reescribir el programa del ltimo cap tulo empleando esa
nueva biblioteca:
jj: operators/HelloDate.java
import java.util.;
import static net . mindview.util.Print.*;
public class HelloDate {
pubIic static void main (String [] args) {
print ( "He lIo, it' s: ");
print(new Date());
l ' Output, (55 % match)
HelIo, it's:
Wed Oct 05 14:39:05 MDT 2005
*111 , -
Los resultados son mucho ms limpios. Observe la insercin de la palabra clave static en la segunda instruccin import.
Para emplear esta biblioteca, es necesario descargar el paquete de cdigo del libro desde www. MindView.net o desde algu-
no de los sitios espejo. Descomprima el rbol de cdigo y aada el directorio raiz de dicho rbol de cdigo a la variable de
entorno CLASSPATH de su computadora (ms adelante proporcionaremos una introduccin completa a la variable de ruta
CLASSPATH, pero es muy probable que el lector ya est acostumbrado a lidiar con esa variable. De hecho, es una de las
batallas ms comunes que se presentan al intentar programar en Java).
44 Piensa en Java
Aunque la utilizacin de net.rnindview.util.Pri nt simplifica en0n11emente la mayor parte del cdigo, su uso no est justi-
ficado en todas las ocasiones. Si slo hay unas pocas instrucciones de impresin en un programa, es preferible no incluir la
instruccin import y escribir el comando completo System.out.println().
Ej e rc ic io 1: (1) Escriba un programa que emplee tanto la faona "corta" como la norlnal de la instruccin de impresin.
Utilizacin de los operadores Java
Un operador toma uno o ms argumentos y genera un nuevo valor. Los argumentos se incluyen de forma distinta a las lla-
madas nonnales a mtodos, pero el efecto es el mismo. La suma, el operador ms unaria (+), la resta y el operador menos
unario (-), la multiplicacin (*), la divisin (f) y la asignacin (=) funcionan de fonna bastante similar a cualquier otro len-
guaje de programacin.
Todos los valores producen un valor a partir de sus operandos. Adems, algunos operadores cambian el valor del operando,
lo que se denomina efecto colateral. El uso ms comn de los operadores que modifican sus operandos consiste precisa-
mente en crear ese efecto colateral, pero resulta conveniente pensar que el valor generado tambin est disponible para nues-
tro uso, al igual que sucede con los operadores que no tienen efectos colaterales.
Casi todos los operadores funcionan nicamente con primitivas. Las excepciones son ' =', '=' y ' !=', que funcionan con
todos los objetos (y son una fuente de confusin con los objetos). Adems, la clase St r ing soporta '+' y '+=' .
Precedencia
La precedencia de los operadores define la fom13 en que se evala una expresin cuando hay presentes varios operadores.
Java tiene una serie de reglas especficas que determinan el orden de evaluacin. La ms fcil de recordar es que la mult i-
plicacin y la divisin se realizan antes que la suma y la resta. Los programadores se olvidan a menudo de las restantes
reglas de precedencia, as que conviene uti lizar parntesis para que el orden de evaluacin est indicado de manera explci-
ta. Por ejemplo, observe las instrucciones (1) y (2):
11 : operators/Precedence.java
public class Precedence {
public static void main(String[] args)
int xl, Y = 2, Z = 3;
int a = x + y - 2/2 + z
int b = x + (y - 2 )/(2 + z );
/ / ( 1 )
/ / (2)
System.out.println("a + a + " b = " + b) i
1* Output:
a = S b = 1
* /// ,-
Estas instrucciones ti enen aspecto similar, pero la sa lida nos muestra que sus significados son bastante distintos, debido a la
utili zacin de parntesis.
Observe que la instruccin System.out.prilltln() incluye el operador '+'. En este contexto, '+' significa "concatenacin de
cadenas de caracteres" y, en caso necesario, "conversin de cadenas de caracteres." Cuando el compilador ve un objeto
Stri ng seguido de '+' seguido de un objeto no String, intenta convertirlo a un objeto St ri ng. Como puede ver en la salida,
se ha efectuado correctamente la conversin de int a String pam a y b.
Asignacin
La asignacin se realiza con el operador =. Su significado es: "Toma el valor del lado derecho, a menudo denominado rva-
lor. y cpialo en el lado izquierdo (a menudo denominado Ivalor)". Un rvalor es cualquier constante, variable o expresin
que genere un valor, pero un lvalor debe ser una variable detenninada, designada mediante su nombre (es decir, debe exis-
tir un espacio fi sico para almacenar el valor). Por ejemplo, podemos asignar un valor constante a una variable:
a = 4
3 Operadores 45
pero no podemos asignar nada a un valor constante, ya que una constante no puede ser un valor (no podemos escribir
4 = a;).
La asignacin de primitivas es bastante sencilla. Puesto que la primitiva almacena el valor real y no una referencia a cual-
quier objeto, cuando se asignan primitivas se asigna el contenido de un lugar a otro. Por ejemplo, si escribimos a = b para
primitivas, el contenido de b se copia en a. Si a continuacin modificamos a, el valor de b no se ver afectado por esta modi-
ficacin. Como programador es lo que cabra esperar en la mayora de las situaciones.
Sin embargo, cuando asignamos objetos, la cosa cambia. Cuando manipularnos un objeto lo que manipulamos es la referen-
cia, asi que al as ignar" de un objeto a otro", lo que estamos haciendo en la prctica es copiar una referencia de un lugar a
otro. Esto significa que si escribimos e = d para sendos objetos, lo que al final tendremos es que tanto e corno d apuntan al
objeto al que slo d apuntaba originalmente. He aqu un ejemplo que ilustra este comportamiento:
11 : operators/ Assignment.java
II La asignacin de objetos tiene su truco.
import static net.mindview.util.Print.*
class Tank {
int level;
public class Assignment {
public static void main (String [] args ) {
Tank tI = new Tank ()
Tank t2 = new Tank {) i
tl.level = 9
t2.level = 47
print ( "I: tI.level:
t2.level:
tI = t2
print {"2: tI.level:
t2.level:
tI.level = 27 i
print ( "3: tI.level:
t2.level:
1* Output:
+ tI.level +
+ t2 .level ) i
+ tI.level +
+ t2 .level )
+ tI.level +
+ t2 .level ) i
1: tl.level: 9, t2.level: 47
2: tI.level: 47, t2.level: 47
3 : tI.level: 27, t2.1evel: 27
* /// , -
La clase Tank es simple, y se crean dos instancias de la misma (11 y t2) dentro de main(). Al campo level de cada objeto
Tank se le asigna un valor distinto, luego se asigna t2 a tl , y despus se modifica tI. En muchos lenguajes de programacin
esperaramos que tl y t2 fueran independientes en todo momento, pero como hemos asignado una referencia, al cambiar el
objeto t1 se modifica tambin el objeto t2. Esto se debe a que tanto tI como t2 contienen la misma referencia, que est apun-
tada en el mismo objeto (la referencia original contenida en tI, que apuntaba al objeto que tena un valor de 9, fue sobrees-
crita durante la asignacin y se ha perdido a todos los efectos; su objeto ser eliminado por el depurador de memoria).
Este fenmeno a menudo se denomina creacin de alias, y representa Wla de las caractersticas fundamentales del modo en
que Java trabaja con los objetos. Pero, qu sucede si no queremos que las dos referencias apunten al final a un mismo obje-
to? Podemos reescribir la asignacin a otro nivel y utilizar:
tI.level = t2.level
Esto hace que se mantengan independientes los dos objetos, en lugar de descartar uno de ellos y asociar tI y t2 al mismo
objeto. Ms adelante veremos que manipular los campos dentro de los objetos resulta bastante confuso y va en contra de los
principios de un buen diseo orientado a objetos. Se trata de un tema que no es nada trivial , as que tenga siempre presente
que las asignaciones pueden causar sorpresas cuando se manejan objetos.
Ejercicio 2: (1) Cree una clase que contenga Wl valor float y utilicela para ilustrar el fenmeno de la creacin de alias.
46 Piensa en Java
Creacin de alias en las llamadas a mtodos
El fenmeno de la creacin de alias tambin puede manifestarse cuando se pasa un objeto a un mtodo:
/1 : operators/ PassObject . java
// El paso de objetos a los mtodos puede no ser
/1 lo que cabria esperar.
import static net.mindview.util.Princ.*
class Letter
char c
publi c class PassOb j ect {
static void f (Letter y )
y.e ::: 'z';
public static void main (5cring[] args ) {
Lecter x = new Letter ()
x.e = 'a' i
print ( "l: x.e:
f Ix l ;
print ( " 2: x.e:
/ * Output:
1: x.e: a
2: x.e: z
* /// ,-
+ x. e ) ;
+ x.e ) ;
En muchos lenguajes de programacin, el mtodo f( ) hara una copia de su argumento Lettcr dentro del mbito del mto-
do, pero aqu, una vez ms, lo que se est pasando es una referencia. por lo que la lnea:
lo que est haciendo es cambiar el objeto que est fuera de f( ).
El fenmeno de creacin de alias y su solucin es un tema complejo del que se trata en uno de los supl ementos en Lnea di s-
ponibles para este libro. Sin embargo, conviene que lo tenga presente desde ahora, con el fin de detectar posibles errores.
Ejercicio 3: ( 1) Cree una clase que contenga un valor flo.! y utilcela para ilustrar el fenmeno de la creacin de alias
durante las llamadas a mtodos.
Operadores matemticos
Los operadores matemticos bsicos son iguales a los que hay di sponibles en la mayora de los lenguajes de programacin:
suma (+), resta (-), di visin (f), multiplicacin (*) y mdulo (%, que genera el resto de una divi sin entera). La divisin
entera trunca en lugar de redondear el resultado.
Java tambin utili za la notacin abreviada de e/e++ que realiza una operacin y una asignacin al mismo tiempo. Este tipo
de operacin se denota medi ante un operador seguido de un signo de igual, y es coherente con todos los operadores del len-
guaje (all donde tenga sentido). Por ejemplo, para sumar 4 a la variable x y asignar el resultado a x, uti li ce: x += 4.
Este ejemplo muestra el uso de los operadores matemticos:
11 : eperaters / MathOps.java
JI Ilustra los operadores matemticos.
import java.util. *;
impert static net . mindview. util.Print. * ;
pubIic cIass MathOps
public static void main (String [) args ) {
1*
j
k
j +
j
k
I
II Crea un generador de nme r os aleatorios con una cierta semilla :
Random rand = new Random{47 )
int i, j, k;
II Elegir valor entre 1 y 100:
j = rand . nextInt ( lOO ) + 1;
print {"j 11 + j )
k = rand.nextInt ( lOO) + 1,
print ( "k + k ) ;
i = j + k;
print {"j + k + i ) j
i = j - k;
print ( lIj - k +
i )
i = k
I j i
print ( "k
I
j + i ) ;
i = k
*
j;
print ( "k
* j +
i ) ;
i = k % j;
print ( "k % j + i ) i
j %= k
print ( "j %= k : " + j )
II Pruebas con nmeros en coma flotante:
float u, v, W II Se aplica tambin a los de doble precisin
v = rand.nextFloat () i
print ( "v "+ v )
W = rand . nextFloat ()
print ( "w : 11 + w);
u = v + w
print ( "v + w
u = v - w
print ( "v - w
u = v * w;
pri n t ( IIV * W
U = v I w
+ u ) ;
+ u )
+ u);
print ( "v I w "+ u )
II Lo siguiente tambin funciona para char,
I I byte , short , int, long y double:
U += V;
print ( "u += V + u ) ;
u - = v;
print ( "u v +
u )
u * = V;
print ( "u *= v + u ) ;
u
1=
v;
print ("u
1=
v
"
+ u) i
Output:
59
56
k 115
k 3
j O
k * j 3304
k % j 56
j %= k ; 3
v 0.5309454
w 0.05341 22
v + w
v w
v * w
0.5843576
0.47753322
0 .028358962
3 Operadores 47
48 Piensa en Java
v / w , 9.940527
U += V 10.471473
u v 9.940527
u *= v 5.2778773
u /= v 9 . 940527
*///>
Para generar nmeros, el programa crea en primer lugar un objeto Random. Si se crea un objeto Random sin ningn argu-
mento, Java usa la hora actual como semi lla para el generador de nmeros aleatorios, y esto generara una salida diferente
en cada ejecucin del programa. Sin embargo, en los ejemplos del libro, es importante que la salida que se muestra al final
de los ejemplos sea lo ms coherente posible, para poder verificarla con herramientas externas. Proporcionando una semi-
lla (un valor de inicializacin para el generador de nmeros aleatorios que siempre genera la misma secuencia para una
detenninada semilla) al crear el objeto Random, se generarn siempre los mismos nmeros aleatorios en cada ejecucin del
programa, por 10 que la salida se podr verificar.
l
Para generar una salida ms variada, pruebe a eliminar la semilla en los
ejemplos del libro.
El programa genera varios nmeros aleatorios de distintos tipos con el objeto Random simplemente invocando los mto-
dos nextln!( ) y nextFloa!( ) (tambi n se pueden invocar nextLong() o nexIDouble( . El argumento de nextIn!() esta-
blece la cota superior para el nmero generado. La cota superior es cero, lo cual no resulta deseable debido a la posibilidad
de una divisin por cero, por lo que sumamos uno al resultado.
Ejercicio 4: (2) Escriba un programa que calcule la velocidad utilizando una distancia constante y un tiempo constante.
Operadores unarios ms y menos
El menos unario (-) y el ms unario (+) son los mismos operadores que la suma y la resta binarias. El compilador deduce
cul es el uso que se le quiere dar al operador a partir de la fonna en que est escrita la expresin. Por ejemplo, la instruc-
cin:
x = -a
tiene un signifi cado obvio. El compilador tambin podra deducir el uso correcto en:
x = a * -b
pero esto podra ser algo confuso para el lector, por lo que a veces resulta ms claro escribir:
x = a * (-b)
El menos unario invierte el signo de los datos. El ms unario proporciona una simetra con respecto al menos unario, aun-
que no tiene ningn efecto.
Autoincremento y autodecremento
Java, como e, dispone de una serie de abreviaturas. Esas abreviaturas pueden hacer que resulte mucho ms fcil escribir el
cdigo; en cuanto a la lectura, pueden simpl ificarla o complicarla.
Dos de las abreviaturas ms utilizadas son los operadores de incremento y decremento (a menudo denominados operadores
de autoincremento y autodecremento). El operador de decremento es -- y significa "disminuir en una unidad". El operador
de incremento es ++ y significa "aumentar en una unidad". Por ejemplo, si a es un valor ot, la expresin ++a es equivalen-
te a (a = a + 1). Los operadores de incremento y decremento no slo modifican la variable, sino que tambin generan el
va lor de la misma como resultado.
Hay dos versiones de cada tipo de operador, a menudo denominadas prefija y postfl)a. Pre-incremenlO significa que el ope-
rador ++ aparece antes de la variable, mientras que post-incremento significa que el operador ++ aparece detrs de la varia-
ble. De fonna similar, pre-decremenlo quiere decir que el operador - - aparece antes de la variable y post-decremento
significa que el operador - - aparece detrs de la variable. Para el pre-incremento y el pre-decremento (es decir, ++a o
- -a), se realiza primero la operacin y luego se genera el valor. Para el post-incremento y el post-decremento (es decir,
a++ o - -a), se genera primero el valor y luego se realiza la operacin. Por ejemplo:
1 El nmero 47 se utilizaba como "nmero mgico" en una universidad en la que estudi, y desde entonces 10 utilizo.
JI : operators/ Autolnc.java
/1 Ilustra los operadores ++ y - - o
i mport static net.mindview.util . Print.*;
public class Autolnc
public static void main (String [J args ) {
int i = 1;
print ( tli :
print ( H+ +i
print ( lIi++
print ( "i :
print ( "- - i
print ( lIi--
print ( "i
/ * Output:
i 1
++i 2
i ++ 2
i
,
3
- -i 2
i- - 2
i : 1
* /// ,-
+ i I ;
+ ++i ) i
//
Pre-incremento
+ i++ } ;
//
Post-incremento
+
i ) ;
+ - - i I ;
//
Pre-decremento
+ i - - ) i
//
Post-decremento
+
i ) ;
3 Operadores 49
Puede ver que para la fanna prefija, se obtiene el valor despus de rea li zada la operacin, mientras que para la fanna pOS1-
fija, se obtiene el valor antes de que la operacin se realice. Estos son los nicos operadores, adems de los de asignacin,
que tienen efectos colaterales. Modifican el operando en lugar de simplemente utilizar su valor.
El operador de incremento es, preci samente, una de las explicaciones del por qu del nombre e++, que quiere decir "un paso
ms all de C". En una de las primeras presentaciones reali zadas acerca de Java, Bi ll Joy (uno de los creadores de Java),
dijo que "Java=C++- _" (C ms ms menos menos), para sugerir que Java es C++ pero si n todas las compl ej idades inne-
cesarias, por lo que resulta un lenguaje mucho ms simple. A medida que vaya avanzando a lo largo del libro, podr ver que
muchas partes son ms simples, aunque en algunos otros aspectos Java no resulta mucho ms sencillo que C++.
Operadores relacionales
Los operadores relacionales generan un resultado de tipo bool ean. Evalan la relacin existente entre los valores de los ope-
randos. Una expresin relacional produce el valor t rue si la relacin es cierta y false si no es cierta. Los operadores relacio-
nales son: menor que ), mayor que (, menor o igual que =), mayor o igual que (>=), equi valente (=) y no equi valente
(!=) . La equival encia y la no equi valencia funcionan con todas las primitivas, pero las otras comparaciones no funcionan
con el tipo boolean. Puesto que los valores boolean slo pueden ser true o fa lse, las relaciones "mayor que" y "menor que"
no tienen sentido.
Comprobacin de la equivalencia de objetos
Los operadores relacionales = y != tambin funcionan con todos los objetos, pero su significado suele confundir a los que
comienzan a programar en Java. He aqu un ejemplo:
JJ : operators J Equivalence.java
public class Equivalence {
public static void main (String[] args ) {
Integer nI = new Integer (47 ) i
Integer n2 = new Integer (47 ) i
System. out . println (nl n2 ) ;
System.out . println (nl != n2 ) i
50 Piensa en Java
} / * Output ,
f a l se
t r ue
* /// , -
La instruccin System.out.println(n t = n2) imprimir el resultado de la comparacin booleana que contiene. Parece que
la sal ida debera ser "tTue" y luego "false", dado que ambos objetos Integer son iguales, aunque el contenido de los obje-
tos son los mi smos, las referencias no son iguales. Los operadores = y != comparan referencias a objetos, por lo que la
salida realmente es "false" y luego 'true". Naturalmente, este comportamiento suele sorprender al principio a los programa-
dores.
Qu pasa si queremos comparar si el contenido de los objetos es equivalente? Entonces debemos utilizar el mtodo espe-
cial equals() di sponible para todos los objetos (no para las primiti vas, que funcionan adecuadamente con = y ~ ) . He aqu
la fonna en que se emplea:
11 : operators/ EqualsMethod.java
public class EqualsMethod {
public static void main {String(] args ) {
Integer nl = new Integer (47 ) ;
Integer n2 = new Integer (47 ) ;
System.out.println (nl.equals {n2 )
1* Output:
true
* /// ,-
El resultado es ahora el que esperbamos. Aunque, en realidad, las cosas no son tan senci ll as. Si creamos nuestra propia
clase, como por ejemplo:
11: operators/EqualsMethod2.java
11 equals {) predeterminado no compara los contenidos.
class Value
int i
publ ic class EqualsMethod2 {
public stati c void main {String(] args ) {
Value v1 = new Value ()
Value v2 = new Value ()
v1.i = v2.i = 100 ;
System.out.println (vl.equal s( v 2)
1* Output:
false
* /// ,-
los resultados vuelven a confundirnos. El resultado es false, Esto se debe a que el comportami ento predetenninado de
equals() consiste en comparar referencias. Por tanto, a menos que sustituyamos equals() en nuestra nueva clase, no obten-
dremos el comportamiento deseado. Lamentablemente, no vamos a aprender a sustituir unos mtodos por otros hasta el cap-
tulo dedicado a la Reutilizacin de clases, y no veremos cul es la forma adecuada de definir equals() hasta el Captulo 17,
Anlisis detallado de los contenedores, pero mientras tanto tener en cuenta el comportamiento de equals() nos puede aho-
rrar algunos quebraderos de cabeza.
La mayora de las clases de biblioteca Java implementan equals( ) de modo que compare el contenido de los objetos, en
lugar de sus referencias.
Ejercicio 5: (2) Cree una clase denominada Dog (perro) que contenga dos objetos String: name (nombre) y says
(ladrido). En main(), cree dos objetos perro con los nombres "spot" (que ladre di ciendo " Ruf!1") y
"scruffy" (que ladre diciendo, "Wurf1"). Despus, muestre sus nombres y el sonido que hacen al ladrar.
3 Operadores 51
Ejercicio 6: (3) Continuando con el Ejerci cio 5, cree una nueva referencia Dog y asgnela al objeto de nombre "spot".
Realice una comparacin utili zando = y cquals() para todas las referencias.
Operadores lgicos
Cada uno de los operadores lgicos AND (&&), OR (11) y NOT (!) produce un valor boolean igua l a Irue o false basndo-
se en la relacin lgica de sus argumentos. Este ejemplo utiliza los operadores relacionales y lgicos:
JI: operators/Bool.java
1/ Operadores relacionales y lgicos.
import java.util. * ;
import static net.mindview util.Print. *
public class Bool {
public static void ma in (String[] args ) {
Random rand = new Random (47 )
// !
//!
int i = rand.next lnt (lOQl i
int j = rand,nextlnt ( l OO);
print ( " i + i) ;
print ( "j +
j) ;
print ( " i > j is
"
+ (i > j) ) ;
print( " i < is
"
+
(i < j) ) ;
print( " i >= j is + (i >= j) ) ;
print( " i <= j is +
(i <= j) ) ;
print("i j is + (i j) ) ;
print("i != j is +
(i != j) ) ;
//
Tratar int como boolean no es
print ( ti i && j is +
( i && j) ) ;
print (" i
11
j is
"
+
(i
11
j) ) ;
II ! print ( "!i is " + ti ) i
/ 0
i
j
i >
i <
i >=
i <=
i
i ! =
(i <
(i <
print( " (i < la) && (j < la) is
((i < 10) && (j < 10)) );
print( " (i < 10) 11 ( j < 10) is
+ ((i < 10) 11 (j < 10)) ) ;
Output :
58
55
j is true
j is false
j is true
j is false
j is false
j is true
10) && (j < 10) is false
10)
11
( j < 10 ) is false
0///,-
legal en Java:
Slo podemos aplicar AND, OR o NOT a valores de tipo boolean. No podemos emplear un valor que no sea boolea" como
si fuera un valor booleano en una expresin lgica como a diferencia de lo que sucede en e y C++. Puede ver en el ejem-
plo los intentos fallidos de realizar esto, desacti vados mediante marcas de comentarios '''!' (esta si ntaxis de comentarios
penni te la eliminac in automtica de comentarios para facilitar las pruebas). Sin embargo, las expresiones subsigui entes
generan valores de tipo boolean utilizando comparaciones relacionales y a continuacin reali zan operaciones lgicas con
los resultados.
Observe que un valor boolean se convierte automt icamente a una fonna de tipo texto apropiada cuando se les usa en luga-
res donde lo que se espera es un valor de tipo String.
52 Piensa en Java
Puede reemplazar la definici n de valores int en el programa anterior por cualquier otro tipo de dato primiti vo excepto
boolean. Sin embargo, teni endo en cuenta que la comparacin de nmeros en coma flotante es muy estricta, un nmero que
difiera de cualquier otro, aunque que sea en un valor pequesimo seguir siendo distinto. Asimismo, cualquier nmero
situado por encima de cero, aunque sea pequesimo, seguir siendo di stinto de cero.
Ej ercicio 7: (3) Escriba un programa que si mul e el proceso de lanzar una moneda al aire.
Cortocircuitos
Al tratar con operadores lgicos, nos encontramos con un fenmeno denominado "cortocircuito". Esto quiere decir que la
expresin se evaluar nicamente hasta que la veracidad o la falsedad de la expresin completa pueda ser detenninada de
forma no ambigua. Como resultado, puede ser que las ltimas partes de una expresin lgica no ll eguen a evaluarse. He aqu
un ejemplo que ilustra este fenmeno de cortocircuito.
11 : operators/ShortCircuit.java
II Ilustra el comportamiento de cortocircuito
II al evaluar los operadores lgicos.
import static net . mindview.util.Print. * ;
public class ShortCircuit {
static boolean testl(int val)
print("testl(" + val + ") " ) i
print (" result: " + (val < 1)) i
return val < 1;
static boolean test2 (int val) {
print (tttest2(" + val + ")");
print( tt result: 11 + (va l < 2));
return val < 2'
static boolean test3 (int val)
print ( " test3 (" + val + ti),,}
print ( "result: I! + (val < 3});
return val < 3
public static void main(String[] args}
boolean b = testl(O} && test2(2) && test3 (2);
print ("expression is " + b);
1* Output:
testl(O)
result: true
test2(2)
result: false
expression is false
* ///,-
Cada una de las comprobaciones realiza una comparacin con el argumento y devuelve true o fa lse. Tambin imprime la
informacin necesaria para que veamos que est siendo invocada. Las pruebas se utilizan en la expresin:
testl (O) && test2 (2) && test3 ( 2 )
Lo natural sera pensar que las tres pruebas llegan a ejecutarse, pero la salida muestra que no es as. La primera de las prue-
bas produce un resultado t r ue, por lo que contina con la evaluacin de la expresin. Sin embargo, la segunda prueba pro-
duce un resultado false. Dado que esto quiere decir que la expresin completa debe ser false, por qu continuar con la
evaluacin del resto de la expresin? Esa evaluacin podra consumir una cantidad considerable de recursos. La razn de
que se produzca este tipo de cortocircuito es. de hecho, que podemos mejorar la velocidad del programa si no es necesario
evaluar todas las partes de una expresin lgica.
3 Operadores 53
Literales
Nomlalmente, cuando insertamos un valor literal en un programa, el compilador sabe exactamente qu tipo asignarle.
En ocasiones, sin embargo, puede que ese tipo sea ambiguo. Cuando esto sucede, hay que guiar al compilador afadiendo
cierta infonnacin adicional en la fonna de caracteres asociados con el valor literal. El cdigo siguiente muestra estos
caracteres:
/1 : operators/ Literals.java
import static net.mindview.util .Print.*
public class Literals {
public static void main (String [] args ) {
int il = Ox2f /1 Hexadecimal (minscula )
print ( "i!: " + Integer.toBinaryString ( il )) i
int i2 = OX2F / 1 Hexadecimal (mayscula)
print (" i2: 11 + Integer. toBinaryString (i2) ) ;
int i3 = 0177; j i Octal (cero inicial)
print ( It i3: " + Integer. toBinaryString (i3 l l ;
char c = Oxffff II mximo valor hex para char
print {"c: 11 + Integer.toBinaryString (c )) ;
byte b = Ox7f; II mximo valor hex para byte
print ("b: 11 + Integer. toBinaryString (b) ) ;
short s = Ox7fff II mximo valor hex para short
print { liS:
long nI
long n2 =
long n3 =
float f1
11 + Integer.toBinaryString {s )l ;
200L II sufijo long
2001; II sufijo long (pero puede
200;
1;
float f2 = IF /1 sufijo float
float f3 = lf II sufijo float
double dI = Id; II sufijo double
double d2 = ID; II sufijo double
1I (Hex y Octal tambin funcionan con long)
/ * Output:
il: 101111
i2 : 101111
i 3: 1111111
c: 1111111111111111
b, 1111111
s: 111111111111111
' 111 ,-
ser confuso)
Un carcter si tuado al final de un valor literal pennite establecer su tipo. La L mayscula o minscula significa long (sin
embargo, utilizar una I minscula es confuso, porque puede parecerse al nmero uno). Una F mayscula o minscula sig-
nifica float. Una D mayscula o minscula significa double.
Los valores hexadecimales (base 16), que funcionan con todos los tipos de datos enteros, se denotan mediante el prefijo O,
O OX seguido de 0-9 o a-f en mayscula o minscula. Si se intenta inicializar una variable con un valor mayor que el mxi-
mo que puede contener (independientemente de la fonna numrica del valor), el compilador dar un mensaje de error.
Observe, en el cdigo anterior, los valores hexadecimales mximos pennitidos para char, byte y short. Si nos excedemos
de stos, el compilador transfoffilar automticamente el valor a nt y nos dir que necesitamos una proyeccin hacia abajo
para la asignacin (definiremos las proyecciones posterionnente en el captulo). De esta fonua, sabremos que nos hemos
pasado del lmite pennitido.
Los valores octales (base 8) se denotan incluyendo un cero ini cial en el nmero y utilizando slo los dgitos 0-7.
No existe ninguna representacin literal para los nmeros binarios en e, e++ o Java. Sin embargo, a la hora de trabajar con
notacin hexadecimal y octal, a veces resulta til mostrar la fOffila binaria de los resultados. Podemos hacer esto fcilmen-
54 Piensa en Java
te con los mtodos slalic loBinarySlring( ) de las clases lnleger y Long. Observe que, cuando se pasan tipos mas peque-
os a Integer.toBinaryString(), el tipo se convierte automticamente a int .
Ejercicio 8: (2) Demuestre que las notaciones hexadecimal y octal funcionan con los valores long. Utilice
Long.loBinarySlring( ) para mostrar los resultados.
Notacin exponencial
Los exponentes utilizan una notacin que a m personalmente me resulta extraa:
// : operatorsjExponents.java
jI "e" significa "ID elevado a".
public class Exponents {
public static void main(String[] args)
JI 'e' en mayscula o minscula funcionan igual:
float expFloat : 1.3ge-43f
expFloat = 1.39E-43f
System.out.println(expFloac) ;
double expDouble = 47e47ct // 'd' es opcional
double expDouble2 = 47e47; // automticamente double
System.out.println(expDouble) i
1* Output:
1.39E-43
4.7E48
* /// ,-
En el campo de las ciencias y de la ingeniera. e' hace referencia a la base de los logaritmos nanlrales, que es aproximada-
mente 2,718 (en Java hay disponible un valor double ms preci so, que es Malb.E). Esto se usa en expresiones de exponen-
ciacin, como por ejemplo 1. 39 X e-
43
, que significa 1.39 X 2.71S--
B
. Sin embargo, cuando se invent el lenguaje de
programacin FORTRAN, decidieron que e significara "diez elevado a", lo cual es una deci sin extraa, ya que FORTRAN
fue diseado para campos de la ciencia y de la ingeniera, asi que cabra esperar que sus di seadores tendran en cuenta lo
confuso de introducir esa ambigedad
2
. En cualquier caso, esta costumbre fue tambin introducida en e, e++ y ahora en
Java. Por tanto, si el lector est habituado a pensar en e como en la base de los logaritmos naturales, tendr que hacer una
traduccin mental cuando vea una expresin como 1.39 e-43f en Java; ya que qui ere decir 1.39 X 10-
43
.
Observe que no es necesario utilizar el carcter sufijo cuando el compilador puede deducir el tipo apropiado. Con:
long n3 = 200;
no existe ninguna ambigedad, por lo que una L despus del 200 sera superfluo. Sin embargo, con:
tloat f4 = le-43f 11 10 elevado a
el compilador nonnalmente considera los nmeros exponenciales como de tipo double, por lo que sin la f final , nos dara
un error en el que nos informara de que hay que usar una proyeccin para convertir el valor double a noat.
Ejercicio 9: (1) Visualice los nmeros ms grande y ms pequeo que se pueden representar con la notacin exponen-
cial en el tipo noal y en el tipo double.
2 John Kirkham escribe: "Comenc a escribir programas infonnticos cn 1962 en FORTRAN 11 en un IBM 1620. Por aquel entonces y a lo largo de las
dcadas de 1960 y 1970, FORTRAN era un lenguaje donde todo se escriba en maysculas. Probablemente, la razn era que muchos de los disposi li vos
de entrada eran antiguas unidades de teletipo que utilizaban el cdigo Baudot de cinco bits que no dispona de minsculas. La hE" en la notacin expo-
nencial era tambin mayscula y no se confunda nunca con la base de los logaritmos naturales "e" que siempre se escribe en minscula. La "E" simple-
mente quera decir exponencial. que era la base para el sistema de numeracin que se estaba utilizando, que nonnahncnle era 10. En aquella poca, los
programadores tambin empleaban los nmeros oCUlles. Aunque nunca vi que nadie lo utilizara, si yo hubiera visto un nmero octal en notacin exponen-
cial. habra considerado que cSlaba en base 8. La primera vez que vi un exponencial utiliumdo una "e" fue a finales de la dcada de 1970 y tambien a m
me pareci confuso: el problema surgi cuando empezaron a utilizarse minsculas en FORTAN, no al principio. De hecho. disponamos de funciones que
podian usarse cuando se quisiera empicar la base de los logaritmos naturales, pero todas esas funciones se escriban en maysculas.
3 Operadores 55
Operadores bit a bit
Los operadores bit a bit penniten manipular bits individuales en un tipo de datos entero primitivo. Para generar el resulta-
do. los operadores bit a bit realizan operaciones de lgebra booleana con los bits correspondientes de los dos argumentos.
Los operadores bit a bit proceden de la orientacin a bajo nivel del lenguaje e, en el que a menudo se manipula el hardwa-
re directamente y es preciso configurar los bits de los registros hardware. Java se diseii. originalmente para integrarlo en
codificadores para televisin, por lo que esta orientacin a bajo nivel segua teniendo sentido. Sin embargo, lo ms proba-
ble es que no utilicemos demasiado esos operadores bit a bit en nuestros programas.
El operador bit a bit ANO (& ) genera un uno en el bit de sal ida si ambos bits de ent rada son iguales a uno; en caso contra-
rio, genera un cero. El operador OR bit a bit (1) genera un uno en el bit de sali da si alguno de los bits de entrada es un uno
y genera cero slo si ambos bits de entrada son cero. El operador bit a bit EXCLUSIVE OR o XOR (A) genera un uno en
el bit de sal ida si uno de los dos bits de entrada es un uno pero no ambos. El operador bit a bit NOT (-. tambin denomina-
do operador de complemento a lII/O) es lIn operador unario, que slo admite un argumento (todos los dems operadores bit
a bit son operadores binarios). El operador bit a bit NOT genera el opuesto al bit de entrada, es uno si el bit de entrada es
cero y es cero si el bit de entrada es uno.
Los operadores bit a bit y los operadores lgicos utili zan los mismos caracteres. por lo que resulta til recurrir a un tmco
mnemnico para recordar cul es el signi fi cado correcto. Como los bits son "pequeos" slo se utiliza un carcter en los
operadores bit a bil.
Los operadores bit a bit pueden combi narse con el signo = para unir la operacin y la asignacin: & =. 1= y A= son opera-
dores legtimos (puesto que - es un operador unari o. no se puede combi nar con el signo =).
El tipo boolean se trata como un valor de un nico bit , por lo que es algo distinto de los otros tipos primitivos. Se puede
realizar una operacin ANO, OR o XOR bit a bit. pero no se puede real izar una operacin NOT bit a bit (presumiblemen-
te, para evi tar la confusin con la operacin lgica NOT). Para los valores booleanos, los operadores bit a bit tienen el
mismo efecto que los operadores lgicos, salvo porque no se aplica la regla de cortocircuito. Asimi smo, las operaciones bit
a bit con valores booleanos incluyen un operador lgico XOR que no fomla parte de la lista de operadores " lgicos". No se
pueden empl ear valores booleanos en expresiones de desplazamiento, las cuales vamos a describir a continuacin.
Ejercicio 10: (3) Escriba un programa con dos valores constantes, uno en el que haya unos y ceros binarios alternados,
con un cero en el dgito menos significati vo, y el segundo con un valor tambin alternado pero con un tino
en el dgito menos significati vo (consejo: lo ms fcil es usar constantes hexadeci mal es para esto). Tome
estos dos valores y combnelos de ladas las fonnas posibles utilizando los operadores bit a bit, y visuali-
ce los resultados uti li zando Integer.toBin.ryStr ing().
Operadores de desplazamiento
Los operadores de desplazamiento tambin sirven para manipular bits. Slo se les puede utilizar con tipos primitivos
enteros. El operador de desplazami ento a la izqui erda <) genera como resultado el operando situado a la izquierda del ope-
rador despus de desplazarlo hacia la izquierda el nmero de bits especificado a la derecha del operador (i nsertando ceros
en los bits de menor peso). El operador de desplazamiento a la derecha con signo () genera como resultado el operando
situado a la izqui erda del operador despus de desplazarlo hacia la derecha el nmero de bils especi ficado a la derecha del
operador. El desplazami ento a la derecha con signo utili za lo que se denomina extensin de signo: si el valor es positi-
vo, se insenan ceros en los bits de mayor peso; si el valor es negati vo, se insertan UIlOS en los bits de mayor peso. Java ha
ailadido tambin un desplazamiento a la derecha si n signo >. que utiliza lo que denomina extensin con ceros: indepen-
dientemente del signo. se insertan ceros en los bits de mayor peso. Este operador no existe ni en e ni C++.
Si se desplaza un valor de tipo charo byte o short, ser convertido a int antes de que el desplazamiento tenga lugar y el
resultado ser de tipo nt. Slo se utilizarn los bits de menor peso del lado derecho; esto evita que se realicen despla-
zamientos con un nmero de posiciones superior al nmero de bit s de un valor int. Si se est operando con un valor long,
se obtendr un resultado de tipo long y slo se emplearn los seis bits de menor peso del lado derecho. para as no poder
desplazar ms posiciones que el nmero de bit s de un valor long.
Los desplazamient os se pueden combinar con el signo igual <= o = o >=). El Ivalor se sustimye por ellva lor des-
plazado de acuerdo con lo que el rvalor marque. Existe un problema. sin embargo, con el desplazamiento a la derecha sin
56 Pi ensa en Java
signo combinado con la asignacin. Si se usa con valores de tipo byte o short, no se obtienen los resultados correctos. En
lugar de ell o, se transfom1an a Dt y luego se desplazan a la derecha, pero a continuacin se truncan al volver a asignar los
valores a sus variables, por lo que se obtiene - 1 en esos casos. El siguiente ejemplo ilustra esta situacin:
/1: operatorsJURShift.java
/1 Prueba del desplazamiento a la derecha sin signo.
import stat i c net.mindview.util.Print.*
public class URShift
public sta tic void main(String[] args)
int i = - 1;
print(Integer.toBinaryString(i)) ;
i >= 10 i
print(Integer.toBinaryString(i)) ;
long 1 = -1;
print(Long.toBinaryString{l)) ;
1 >0: la;
print(Long.toBinaryString(l)) ;
short s = - 1;
print(Integer.toBinaryString(s)) ;
s :>:= la;
print(Integer.toBinaryString(s)) ;
byte b = -1,
print(Integer . toBinaryString(b)) ;
b >= 10;
print(Integer.toBinaryString(b)) ;
b = -1 i
print(Integer.toBinaryString(b)) ;
print(Integer.toBinaryString(b>10)) ;
1* Output:
11111111111111111111111111111111
1111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
1111111111111111111111
* /// , -
En el ltimo desplazamiento, el valor resultante no se asigna de nuevo a b, sino que se imprime directamente, obtenindo-
se el comportamiento correcto.
He aqu un ejemplo que ilustra todos los operadores para el manejo de bits:
11: operators/BitManipulation .java
/1 Uso de los operadores bit a bit.
import java.util . *
import static net.mindview.util.Print.*;
public class BitManipulation (
public static void main(String[] args) {
Random rand = new Random(47 );
int i = rand.nextlnt();
int j = rand.nextlnt();
printBinarylnt ( "-1", -1) i
printBinarylnt ( " +1!l, +1) i
int maxpos = 2147483647;
printBinarylnt ("maxpos", maxpos) i
int maxneg = -2147483648;
printBinarylnt ("maxneg", maxneg);
printBinarylnt (11 i 11, i);
printBinarylnt (11_1 ", -i) i
printBinarylnt (11 - i", - i) i
printBinarylnt (" j", j) i
printBinarylnt (11 i & j", i & j) i
printBinarylnt (" i j ", i I j);
printBinarylnt (" i A j" I i A j);
printBinarylnt("i c< S", i ce 5);
printBinarylnt("i 5", i 5) j
printBinarylnt("(-i} 5", (-i) 5);
printBinarylnt("i > 5", i > 5);
printBinarylnt ( " {-i} > 5", (-i) > S);
long 1 = rand.nextLong()
long ID = rand.nextLong()
printBinaryLong ( "-IL", -lL);
printBinaryLong ("+lL", +lL);
long 11 = 9223372036854775807L;
printBinaryLong ("maxpos", 11);
long 11n = -92233720368s477s80BL
printBinaryLong ( "maxneg", l1n);
printBinaryLong ( " 1 tl, 1)
printBinaryLong(tl-1
11
, -1);
printBinaryLong (It _1
11
, -1);
printBinaryLong ("m", m)
printBinaryLong (111 &
printBinaryLong (It 1
printBinaryLong (11 1 '"
printBinaryLong ( " 1
m"
m"
m"
5"
1
1
1
1
& mi;
mi;
,
mi;
51 ;
printBinaryLong("1 Sil, 1 S);
printBinaryLong(1I (-1) S", {-l} 5) i
printBinaryLong(
1t
1 :> 5", 1 :> S)
printBinaryLong("(-1) :> S", (-1) > S)
static void printBinaryInt(String s, int i)
print (s + 11, int: 11 + i + ", binary: \n +
Integer.toBinaryString(i)
static void printBinaryLong(String s, long 1)
print (s + It, long: It + 1 + 11, binary: \n +
Long. toBinaryString (1l )
/* Output:
- 1, int: -1, binary:
11111111111111111111111111111111
+1, int: 1, binary:
1
maxpos, int: 2147483647, binary:
1111111111111111111111111111111
maxneg, int: -2147483648, binary:
10000000000000000000000000000000
i, int: -1172028779, binary:
10111010001001000100001010010101
-i, int: 1172028778, binary:
1000101110110111011110101101010
3 Operadores 57
58 Piensa en Java
-i, int: 1172028779, binary:
1000101110110111011110101101011
j, int: 1717241110, binary:
1100110010110110000010100010110
i & j, int: 570425364, binary:
100010000000000000000000010100
i I j, int: -25213033, binary:
11111110011111110100011110010111
i " j, int: -595638397, binary:
11011100011111110100011110000011
i 5, int: 1149784736, binary:
1000100100010000101001010100000
i 5, int: -36625900, binary:
111111011101000 1001 0001000010100
(-i) S, int : 36625899, binary:
100010 11101101110111101011
i > 5, int: 97591828, binary:
1011101 000100100010000 10100
(-i) > 5, int: 36625899, binary:
10001011101101110111101011
Los dos mtodos del final , printBinarylnt() y printBinaryLong( j , toman un valor int o long, respectivamente, y lo impri-
men en formato binario junto con una cadena de caracteres descriptiva. Adems de demostrar el efecto de todos los opera-
dores bit a bit para valores int y long, este ejemplo tambin muestra los valores mnimo, mximo, + 1 Y - t para int y long
para que vea el aspecto que tienen. Observe que el bit ms alto representa el signo: O significa positi vo y 1 significa nega-
ti vo. En el ejemplo se muestra la salida de la parte correspondiente a los valores in!.
La representacin binaria de los nmeros se denomina complemento a dos con signo.
Ejercicio 11: (3) Comience con un nmero que tenga un uno binario en la posicin ms signifi cati va (consejo: utilice
una constante hexadecimal ). Emplee el operador de desplazamiento a la derecha con signo, desplace el
valor a travs de todas sus posiciones binarias, mostrando cada vez el resultado con
Integer.toBinaryString( ).
Ejercicio 12: (3) Comience con un nmero cuyos dgitos binarios sean todos iguales a uno. A continuacin desplcelo
a la izqui erda y utilice el operador de desplazamiento a la derecha si n signo para desplazarl o a travs de
todas sus posiciones binarias, visualizando los resultados con lnteger.toBinaryString().
Ejercicio 13: (1) Escriba Wl mtodo que muestre valores char en fonnato binario. Ejectelo utilizando varios caracte-
res di ferentes.
Operador ternario if-else
El operador ternario, tambi n llamado operador condicional resulta inusual porque tiene tres operandos. Realmente se trata
de un operador, porque genera un valor a diferencia de la instruccin if-else ordinaria, que veremos en la siguiente seccin
del captulo. La expresin tiene la fomla:
exp-booleana ? valorO : valor1
Si exp-booleana se evala como true, se evala valorO y el resultado ser el valor generado por el operador. Si exp-
booleana es false, se evala valorl y su resultado pasar a ser el valor generado por el operador.
Por supuesto, podra utilizarse una instruccin if-else ordinaria (que se describe ms adelante), pero el operador ternario es
mucho ms compacto. Aunque e (donde se origin este operador) se enorgullece de ser un lenguaje compacto, y el opera-
dor ternario puede que se baya introducido en parte por razones de eficienci a, conviene tener cuidado a la hora de emplear-
lo de forma cotidiana, ya que el cdigo resultante puede llegar a ser poco legible.
El operador condi cional es diferente de if-else porque genera un valor. He aqu un ejemplo donde se comparan ambas estruc-
turas:
JI : operators/ TernarylfElse.java
import static net.mindview.util.Print.*
public class TernarylfElse {
static int ternary(int i) {
return i < 10 ? i * 100 i * 10i
static int standardlfElse ( int i)
if(i < 10)
return i * 100 i
else
return i * 10;
public static void main{String[] args)
print(ternary{9)) j
print(ternary{lO)) i
print(scandardlfElse(9)) ;
print(standardlfElse(lO)) ;
/ * Output :
900
100
900
100
* /1/ ,-
3 Operadores 59
Puede ver que el cdigo de ternary( ) es ms compacto de lo que sera si no di spusiramos del operador temario: la ver-
sin sin operador temario se encuentra en standardlfElse( j. Sin embargo, standardlfElse( j es ms fcil de comprender
y adems exige escribir muchos caracteres ms. As que asegrese de ponderar bien las razones a la hora de elegir el ope-
rador ternario; nOffilalmente, puede convenir utilizarlo cuando se quiera configurar una variable con uno de dos valores posi-
bles.
Operadores + y += para String
Existe un uso especial de un operador en Java: los operadores + y += pueden usarse para concatenar cadenas, corno ya
hemos visto. Parece un uso bastante natural de estos operadores, an cuando no encaje demasiado bien con la fonna tradi-
cional en que dichos operadores se emplean.
Esta capacidad le pareci adecuada a los diseadores de C++, por lo que se aadi a C++ un mecanismo de sobrecarga de
operadores para que los programadores e++ pudieran aadir nuevos significados casi a cualquier operador. Lamentable-
mente, la sobrecarga de operadores combinada con alguna de las otras restricciones de C++, resulta una caracterstica exce-
sivamente complicada para que los programadores la incluyan en el diseo de sus clases. Aunque la sobrecarga de
operadores habra sido mucho ms fcil de implementar en Java de lo que lo era en C++ (como se ha demostrado en el len-
guaje C#, que s que dispone de un sencillo mecani smo de sobrecarga de operadores), se consideraba que esta caractersti-
ca segua siendo demasiado compleja, por lo que los programadores Java no pueden implementar sus propios operadores
sobrecargados, a diferencia de los programadores de C++ y C#.
El uso de los operadores para valores String presenta ciertos comportamientos interesantes. Si una expresin comienza con
un valor String, todos los operandos que siguen tambin tendrn que ser cadenas de caracteres (recuerde que el compilador
transforma automticamente a String lada secuencia de caracteres encerrada entre comillas dobles).
11: operators /Stri ngOperators.java
import static net.mindview.util.Print . *;
public class StringOperators {
public static void main{String[] args) {
int x 0, y = 1, Z = 2;
String s = II X , y, Z "
print{s + x + y + z );
60 Piensa en Java
print (x + " " + s); / / Convierte x a String
s += It (summed) = "i / / Operador de concatenacin
print (s + (x + y + z );
print ( "" + x ) ; / / Abreviatura de Integer . toString ()
/ * Output:
x , y, z 012
O x, y, z
x, y, z (summed) 3
O
*111 ,-
Observe que la salida de la primera instruccin de impresin es ' 012 ' en lugar de slo ' 3', que es lo que obtendria si se estu-
vieran sumando los valores enteros. Esto es porque el compilador Java convierte x, y y z a su representacin String y con-
catena esas cadenas de caracteres, en lugar de efectuar primero la suma. La segunda instruccin de impresin convierte la
variable inicial a String, por lo que la conversin a cadena no depende de qu es lo que haya primero. Por ltimo, podemos
ver el uso del operador += para aadir una cadena de caracteres a s, y el uso de parntesis para controlar el orden de eva-
luacin de la expresin, de modo que los valores enteros se sumen realmente antes de la visuali zacin.
Observe el ltimo ejemplo de main() : en ocasiones, se encontrar en los programas un valor String vaco seguido de + y
una primitiva, como fonna de reali zar la conversin sin necesidad de invocar el mtodo explicito ms engorroso,
(Integer. toString(), en este caso).
Errores comunes a la hora de utilizar operadores
Uno de los errores que se pueden producir a la hora de emplear operadores es el de tratar de no incluir los parntesis cuan-
do no se est del todo seguro acerca de la fonna que se evaluar una expresin. Esto, que vale para muchos lenguajes tam-
bin es cierto para Java.
Un error extremadamente comn en e y e++ seria el siguiente:
while Ix = y ) {
II oo
El programador estaba intentando, claramente, comprobar la equivalencia (-) en lugar de hacer una asignacin. En e y
e++ el resultado de esta asignacin ser siempre true si y es distinto de cero, por lo que probablemente se produzca un bucle
infinito. En Java, el resultado de esta expresin no es de tipo boolean, pero el compilador espera un valor boolean y no rea-
li zar ninguna conversin a partir de un valor int, por lo que dar un error en tiempo de compilacin y detectar el proble-
ma antes de que ni siquiera intentemos ejecutar el programa. Por tanto, este error nunca puede producirse en Java (la nica
posibilidad de que no se tenga un error de tiempo de compilacin, es cuando x e y son de tipo boolean, en cuyo caso x = y
es una expresin legal , aunque en el ejemplo anterior probablemente su li SO se deba a un error).
Un problema similar en e y e++ consiste en utilizar los operadores bit a bit AND y OR en lugar de las versiones lgicas.
Los operadores bit a bit AND y OR uti lizan uno de los caracteres (& o Il mientras que los operadores lgicos AND y OR
utilizan dos (&& y 11). Al igual que con = ~ resulta fcil confundirse y escribir slo uno de los caracteres en lugar de
dos. En Java, el compi lador vuelve a evitar este tipo de error, porque no pennite emplear un determinado tipo de datos en
un lugar donde no sea correcto hacerlo.
Operadores de proyeccin
La palabra proyeccin (casI) hace referencia a la conversin explcita de datos de un tipo a otro. Java cambiar automtica-
mente un tipo de datos a otro cada vez que sea apropiado. Por ejemplo, si se asigna un valor entero a una variable de coma
flotant e, el compilador convertir automticamente el valor int a noat. El mecanismo de conversin nos pennite realizar
esta conversin de manera explcita, o incluso forzarla en situaciones donde nonnalmente no tendra lugar.
Para realizar una proyeccin, coloque el tipo de datos deseado entre parntesis a la izquierda del valor que haya que con-
vertir, como en el siguiente ejemplo:
// : operators/ Casting.java
public class Casting
public sta tic void main (String [] args ) {
int i = 200;
long 1n9 = (10ng) i;
1n9 = i JI "Ensanchamiento," por lo que no se requiere conversin
long lng2 = ( long ) 200 ;
lng2 = 20 0 ;
l/ Una "conversin de estrechamiento !l.
i = (int ) lng2; // Proyeccin requerida
)
/// >
3 Operadores 61
Como podemos ver, resulta posible aplicar una proyeccin de tipo tanto a los valores numricos como a las variables.
Observe que se pueden tambin introducir proyecciones superfluas, por ejemplo, el compilador promocionar automtica-
mente un valor ut a long cuando sea necesario. Sin embargo, podemos utili zar esas proyecciones superfluas con el fin de
resaltar la operacin o de clarificar el cdigo. En otras situaciones, puede que la proyeccin de tipo sea esencial para que el
cdigo llegue a compilarse.
En C y C++, las operaciones de proyeccin de tipos pueden provocar algunos dolores de cabeza. En Java, la proyeccin de
tipos resulta siempre segura, con la excepcin de que, cuando se realiza una de las denominadas conversiones de estrecha-
miento (es decir, cuando se pasa de un tipo de datos que puede albergar ms infomlacin a otro que no permite albergar
tanta), se COITe el riesgo de perder infonnacin. En estos casos, el compilador nos obliga a emplear una proyeccin, como
dicindonos: "Esta conversin puede ser peligrosa, si quieres que lo haga de todos modos, haz que esa proyeccin sea expl-
cita". Con una conversin de ensanchamiento, no hace falta una proyeccin explcita, porque el nuevo tipo pennitir alber-
gar con creces la infonnacin del tipo anterior, de modo que nunca se puede perder infonnacin.
Java pennite proyectar cualquier tipo primitivo a cualquier otro, excepto en el caso de boolean, que no pennite efechlar nin-
gn tipo de proyeccin. Los tipos de clase tampoco penniten efectuar proyecciones: para converti r uno de estos tipos en
otro, deben existir mtodos especiales (posterionnente, veremos que los objetos pueden proyectarse dentro de una/ami/ia
de tipos; un Olmo puede proyectarse sobre un rbol y viceversa, pero no sobre un tipo externo como pueda ser Roca).
Truncamiento y redondeo
Cuando se realizan conversiones de estrechamiento, es necesario prestar atencin a los problemas de truncamiento y redon-
deo. Por ejemplo, si efectuamos una proyeccin de un valor de coma flotante sobre un valor entero, qu es lo que hara
Java? Por ejemplo, si tenemos el valor 29,7 y lo proyectamos sobre un int, el valor resultante ser 30 o 29? La respuesta
a esta pregunta puede verse en el siguiente ejemplo:
/1 : operators / CastingNumbers.java
// Qu ocurre cuando se proyecta un valor float
// o double sobre un valor entero?
import static net.mindview.util.Print.*
public class CastingNumbers {
public st atic void main (String[] args )
double aboye = 0.7, below = 0 .4;
float fabove = D.7f, fbelow = D.4f
print ( " (int l above: " + (int ) above ) ;
print {" (int l below: " + int ) below) i
print ( II (int l fabove: + (int ) fabove ) ;
print ( " (int l fbelow: " + (int ) fbelow)
1* Output:
(int ) above: O
(int ) below: O
(int ) fabove: O
(int ) fbelow: O
* /// ,-
62 Piensa en Java
Asi que la respuesta es que al efectuar la proyeccin de float o double a un valor entero, siempre se trunca el correspon-
diente nmero. Si qui siramos que el resultado se redondeara habra que utilizar los mtodos round() de ja\'a.lang.Math:
11 : operators/RoundingNumbers . java
II Redondeo de valores float y double.
import static net.mindview.ut i l.Print .*;
public class RoundingNumbers {
public static void main(String[) args)
double aboye = 0 . 7, below = 0 .4 ;
float fabove = 0 . 7f, fbelow = 0 .4f;
print {"Math. round (aboye ) :
print ( "Math. round (below) :
print("Math.round {fabove ) :
print ("Math.round{fbelow) :
1* Output:
Math . round (above) : 1
Math. round (below) : O
Math. round (fabove) : 1
Math. round (fbelow) : O
* ///,-
ti + Math .round{above;
" + Math.round {below;
+ Math.round(fabove i
11 + Math. round (fbelow;
Puesto que round( ) es parte de ja\'a.lang, no hace falta ninguna instmccin adicional de importacin para utilizarlo.
Promocin
Cuando comience a programar en Java, descubrir que si hace operaciones matemticas o bit a bit con tipos de datos primi-
tivos ms pequeos que nt (es decir, char, byte o short), dichos valores sern promocionados a nt antes de realizar las
operaciones, y el valor resultante ser de tipo int. Por tanto, si se quiere asignar el resultado de nuevo al tipo ms pequeo,
es necesario emplear una proyeccin (y, como estamos realizando una asignacin a un tipo de menor tamao, perderemos
infonnacin). En general, el tipo de datos de mayor tamao dentro de una expresin es el que detennina el tamao del resul-
tado de esa expresin, si se multiplica un valor float por otro double, el resultado ser double; si se suman un valor nt y
uno long, el resultado ser long.
Java no tiene operador "sizeof"
En C y C++, el operador sizeof() nos dice el nmero de bytes asignado a un elemento de datos. La razn ms importante
para el uso de sizeof( ) en C y C++ es la portabi lidad. Los diferentes tipos de datos pueden tener di ferentes tamaos en
di stintas mquinas, por lo que el programador debe averiguar el tamao de esos tipos a la hora de reali zar operaciones que
sean sensibles al tamao. Por ejemplo, una computadora puede almacenar los enteros en 32 bits, mientras que otras po-
dran almacenarlos en 16 bits. Los programas podran, as , almacenar valores de mayor tamao en variables de tipo entero
en la primera mquina. Como puede imaginarse, la portabilidad es un verdadero quebradero de cabeza para los programa-
dores de C y C++.
Java no necesita un operador sizeof( ) para este propsito, porque todos los tipos de datos tienen el mismo tamao en todas
las mquinas. No es necesario que tengamos en cuenta la portabilidad en este nivel , ya que esa portabilidad fanna parte del
propio di seo del lenguaj e.
Compedio de operadores
El sigui ente ejemplo muestra qu tipos de datos primitivos pueden utilizarse con detenninados operadores concretos.
Bsicamente, se trata del mi smo ejemplo repetido una y otra vez pero empleando diferentes tipos de datos primitivos. El
archivo se compilar si n errores porque las lneas que los incluyen estn desactivas mediante comentarios de tipo I/!.
11 : operators/A110ps.java
1I Comprueba todos los oper adores con todos los tipos de datos primitivos
/1 para mostrar cules son aceptables por el compilador Java.
public class AIIOps {
1/ Para aceptar los resultados de un test booleano:
void f (boolean b) {}
void boolTest(boolean x, boo!ean y)
// Operadores aritmticos:
//! x x * Yi
jj! x x j y;
jj! x x%y;
//!x X+Yi
ji! x x y
/ /! X++;
jj! x--;
I/! x = +y;
jj! x = -y;
1/ Relacionales y lgicos:
jj! f (x > y);
j j! f (x >= y);
jj! f(x < y);
j j! f (x <= y);
f (x y);
f (x ! = y);
f (!y);
x = x && y
x = x 11 y;
1/ Operadores bit a bit:
j j! x -y;
x x & y;
x x y;
x x y;
jj! x x
jj! x x
jj! x X >>>
1 ;
1 ;
1 ;
// Asignacin compuesta:
jj ! x += y;
jj! x -= y;
JI! x *= y;
jj! x j= y;
jj! x %= y;
JI! x = 1;
JI! x = 1;
I/! x >= 1;
x &= y
x "= y
x 1= y;
l/Proyeccin:
//! char e = (char) x;
jj! byte b = (byte ) x;
I/! short s = (short ) x;
jI! int i = ( int ) x
jj! long 1 = (long)x ;
jj! float f = (float)x;
jj! double d = (doubl e )x;
void charTest(char x, char y)
/1 Operadores aritmticos:
x (char ) (x * y);
x = (char) (x j y);
3 Operadores 63
64 Piensa en Java
x (char) (x % y);
x (char) (x + y);
x (char) ( x y) ;
x++;
x--
x = (char) +y
x = (char) -y
II Relacionales y lgicos:
f (x > y);
f (x >= y);
f (x < y);
f (x <= y);
f (x == y);
f(x != y) ;
II!
f (!x)
II!
f (x && y) ;
II!
f(x
11
y) ;
II Operadores bit a bit:
x= (char) -y
x = (char) {x & y};
x = (char) ( x 1 y);
x (char) (x y);
x (char ) (x 1);
x (char) (x 1)
x (char) (x > 1);
II Asignacin compuesta:
X += y;
X y;
X *= y;
x
1= y;
x %= y;
x = 1 ;
x = 1 ;
x >= 1 ;
x &= y;
X A= y
x 1= y;
II Proyeccin:
II! boolean bl = (boolean}x;
byte b = (byte)x;
short s = (short)x
int i = {int}x
long 1 = (long)x
float f = (float)x
double d = (double)x
void byteTest{byte x, byte y)
II Operadores aritmticos:
x (byte) (x* y);
x (byte) (x I y);
x (byte) (x % y);
x (byte) (x + y) ;
x (byte) (x y ) ;
x++;
X--
x = (byte) + y;
x = (byte) - y;
II Relacionales y lgicos:
f (x > y);
f (x >;
y ) ;
f (x < y ) ;
f (x <= y ) ;
f (x -- y ) ;
f (x != y) ;
// ! f (! x l i
//! f (x && y ) ;
// ! f(x
11
y ) ;
1/ Operadores bit a bit:
x (byte) -y;
x (byte) (x & y ) ;
x ( byte ) ( x y ) ;
x ( byte) ( x y ) ;
x ( byte) ( x 1 ) i
x ( byte) (x 1) i
x ( byte) (x > 1 ) i
JI Asignacin compuesta:
x += y;
X y ;
X *= y ;
x /; y ;
x %= Yi
X :::c 1;
x = 1;
x>", 1;
x &= y;
x "'= y;
x 1; y;
JI Proyeccin :
JI ! boolean bl = (boolean)x;
char e = (char ) x;
short s = (short ) x;
int i = ( int ) Xi
long 1 = (l ong) x;
float f = ( fleat ) X i
dauble d = (double)x;
void shortTest (short x, short y ) {
JI Operadores aritmticos:
x (short ) (x * y ) ;
x ( short ) ( x / y ) ;
x ( short ) ( x % y);
x (short ) (x + y ) ;
x ( short) ( x y);
x++ ;
x--
x = (short)+y
x = (short}-y ;
JI Relacionales y lgicos :
f ( x > y ) ;
f (x >= y ) ;
f ( x < y ) ;
f ( x <= y ) ;
f ( x ;= y ) ;
f ( x != y);
// ! f (!x);
// ! f (x && y ) ;
// ! f (x 11 y);
JI Operadores bit a bit :
3 Operadores 65
66 Piensa en Java
x (short)-y
x Ishort ) Ix & y);
x Ishort) Ix y ) ;
x Ishort ) Ix A y );
x (short) ( x 1 ) i
x (short) (x 1) i
x (short) (x > 1) i
II Asignacin compuesta:
X += y;
X -= y;
x
*= y;
x
1=
y;
x %= y;
x = 1
x = 1
x >= 1;
x &= y
x "= Y
x 1= y;
II Proyeccin:
II! boolean bl = (boolean} x
char c = (char }x;
byte b = Ibyte)x;
int i = (int ) Xi
long 1 = Ilong) x;
float f = I float ) x;
double d = Idouble ) x;
void intTest (int x, int y ) {
II Operadores aritmticos:
x x * y
x
x
x
x
x++
x - -
x
x
x
x
1 y;
% y;
+ y;
y;
x = +Yi
x = -Y
II Relacionales y lgicos:
f lx > y);
f l x >= y ) ;
flx < y) ;
f l x <= y ) ;
f lx -- y ) ;
f Ix ! = y );
II ! f I! x ) ;
II ! f Ix && y ) ;
11 ' f Ix 11 y);
II Operadores bit a bit:
x -y
x x & y;
x x
1
y;
x x y;
x x 1;
x x 1 ;
x x> 1 i
II Asignacin compuesta:
X += Yi
x y;
x *= y;
x / = y;
x %= y;
x = 1;
x := 1 i
X >= 1;
x &= Yi
x "'= Yi
x 1= y;
// Proyeccin:
// ! boolean bl (boolean ) x
char e = {char ) x
byte b = (byte ) x;
short s = (short ) x
long 1 = ( long ) x
float f = (float ) x;
double d = {double )x
void longTest ( long x, long y)
1/ Operadores aritmticos:
x x * Yi
x x
/
x x %
x x +
x x -
X++
x--
x = +Yi
X = -Vi
y;
y;
y;
y;
/ 1 Relacionales y lgicos:
f (x > y);
f (x >= y ) i
f (x < y ) ;
f (x <= y ) ;
f (x == y ) ;
f (x != y ) ;
/ / ! f ( !x) ;
/ / ! f (x && y ) ;
/ / ! f ( x 1 1 y ) ;
JI Operadores b i t a bit:
x - Yi
x x & y;
x x y;
x x y;
x x 1 ;
x x 1 ;
x x:> 1;
/1 Asignacin compuesta:
X += y;
X y;
X *= y;
x / = y;
x %= y;
x = 1
x = 1;
x >= 1;
x &= Yi
x "= Yi
3 Operadores 67
68 Piensa en Java
x 1, y;
II Proyeccin:
II! boolean bl = (boolean)x ;
char c = (char)x
byte b ' (byt e l x;
short s = (short) x
int i = (int) x
float f = (float ) x
double d = {double)x
void floatTest ( float X, float y) {
II Operadores aritmticos:
X X * y
x x
I
x x %
x x +
x x -
x++
x--;
X = +y
x = -y
y;
y;
y;
y;
II Relacionales y lgicos:
f (x > y)
f (x >, yl;
f (x < yl;
f Ix <, yl;
f Ix " yl;
f (x !, yl;
II! f (! x l ;
II! f (x && yl;
II! f (x 1 1 yl;
II Operadores bit a bit:
II! x -y;
II! x x & y;
II! x x 1 y;
II! x x y;
II! x x 1;
II ! x x 1;
II! x x> 1;
II Asignacin compuesta:
x += y
x - y
x *= y
x 1, y;
x %= y
II! x = 1
II! x = 1;
II! x >= 1
II ! x &, y;
II! x " y;
II!xl,y;
II Proyeccin ;
II! boolean bl = (boolean)x
char c = (char ) x
byte b = (byte l x;
short s = (short)x
int i = (int )x
long 1 (longl x;
double d = (double l x;
void doubleTesc {double x, double y ) {
// Operadores aritmticos:
x x * Yi
x x I y;
x x % y
x x + y;
x
X++
x--;
x
x = +Yi
X = -y;
y;
/1 Relacionales y lgicos:
f I x > y l ;
f I x >= y l ;
f Ix < y l ;
f Ix <= yl;
f I x == y l ;
f Ix != y l ;
II ! f I 'xl ;
II ! f I x && y l ;
I I ! f Ix 1 1 yl;
/ / Operadores bit a bit:
11 ' x -y;
// ! x X&Yi
II ! x x 1 y;
II ! x x y;
// ! x x 1;
j i! x x:>:> 1;
ji ! x x> 1;
/1 Asignacin compuesta:
x +"" y;
X Yi
x *= Yi
x / = y;
x %= y
JI ! x = 1;
ji ! x = 1;
// ! x >= 1;
II ! x &= y;
II ! x '= y;
II ! x 1= y;
JI Proyeccin:
//1 boolean bl = {boo l ean ) x;
c har e = (char ) x;
byte b = Ibyte l x;
short s = (short ) x;
int i = ( int ) x;
long 1 = Ilong l x;
float f = ( float ) Xi
)
111 ,-
3 Operadores 69
Observe que boolean es bastante limitado. A una variable de este tipo se le pueden asignar los valores true y false, y se
puede comprobar si el valor es verdadero o falso, pero no se pueden sumar valores booleanos ni realizar ningn otro tipo de
operacin con ellos.
En char, byte y short, puede ver el efecto de la promocin con los operadores ari tmticos. Toda operacin aritmtica sobre
cualquiera de estos tipos genera un resultado int, que despus debe ser proyectado explcitamente al tipo original (una con-
versin de estrechamiento que puede perder informacin) para realizar la asignacin a dicho tipo. Sin embargo, con los valo-
70 Piensa en Java
res int no es necesari a una proyeccin. porque todo es ya de tipo int. Sin embargo, no se crea que todas las operaciones son
seguras. Si se multiplican dos valores nt que sean lo suficientemente grandes, se producir un desbordamiento en el resul-
tado, como se ilustra en el siguiente ejemplo:
JJ : operatorsJOverflow. java
JJ Sorpresa! Java permite los desbordamientos.
public class Overflow {
public static void main(String(} args )
int big = Integer.MAX_VALUE
System. out . println("big = " + big)
int bigger = big * 4
System.out.println ( lIbigger = 11 + bigger )
J* Output :
big = 214 748364 7
bigger = -4
* ///,-
No se obti ene ningn tipo de error o advertencia por pane del compilador, y tampoco se genera ninguna excepcin en tiem-
po de ejecucin. El lenguaje Java es muy bueno, aunque no hasta ese punto.
Las asignaciones compuestas no requieren proyecciones para char, byte o short, an cuando estn realizando promociones
que provocan los mi smos resultados que las operaciones aritmticas directas. Esto resulta quiz algo sorprendente pero, por
otro lado, la posibilidad de no incluir la proyeccin simplifica el cdigo.
Como puede ver, con la excepcin de boolcan, podemos proyectar cualquier tipo primiti vo sobre cualquier otro tipo primi-
tivo. De nuevo, recalquemos que es preciso tener en cuenta los efectos de las conversiones de estrechamiento a la hora de
realizar proyecciones sobre tipos de menor en caso contrario, podramos perder informacin inadvertidamente
durante la proyeccin.
Ejercicio 14: (3) Escriba un mtodo que tome dos argumentos de tipo String y utilice todas las comparaciones
boolean para comparar las dos cadenas de caracteres e imprimir los resultados. Para las comparaciones
= y !=, realice tambin la prueba con equals( ). En maine ), invoque el mtodo que haya escrito, utili-
zando varios objetos Stri ng diferentes.
Resumen
Si tiene experiencia con algn lenguaje que emplee una sintaxi s si milar a la de C. podr ver que los operadores de Java son
tan similares que la curva aprendi zaje es prcticamente nula. Si este captul o le ha resultado dificil, asegrese de echar un
vistazo a la presentacin multimedi a Thinking in C. di sponible en
Puede encontrar las sol uci ones a ejercicios seleccionados en el documento elcctrnico The Thi"ki"g in Java Afmolllfed So/mio" Guide, que est dispo-
nible para la vcnta en \\"Il1ul/i"dl'iew.nel .
Control de
. . ,
eJecuclon
Al igual que las criaturas sensibles, un programa debe manipular su mundo y tomar decisiones
durante la ejecucin. En Java, las decisiones se toman mediante las instrucciones de control de
ejecucin.
Java utiliza todas las instrucciones de control de ejecucin de e, por lo que si ha programado antes con e o C++, la mayor
parte de la infonnacin que vamos a ver en este captulo le resultar famil iar. La mayora de los lenguajes de programacin
procedimental disponen de alguna clase de instrucciones de control, y suelen existir solapamientos entre los distintos len-
guajes. En Java, las palabras clave incluyen if-else, while, do-whUe, for, return, break y una instruccin de seleccin deno-
minada switch. Sin embargo, Java no soporta la despreciada instruccin goto (que a pesar de ello en ocasiones representa
la fonna ms directa de resolver ciertos tipos de problemas). Se puede continuar realizando un salto de estilo goto, pero est
mucho ms restringido que un goto tpico.
true y false
Todas las instrucciones condicionales utilizan la veracidad o falsedad de una expresin condicional para determinar la ruta
de ejecucin. Un ejemplo de expresin condicional sera a = b. Aqu, se utili za el operador condicional = para ver si el
va lor de a es equivalente al valor de b. La expresin devuelve true o false. Podemos utilizar cualqui era de los operadores
relacional es que hemos empleado en el cap tulo anterior para escri bir una instruccin condicional. Observe que Java no per-
mite utilizar un nmero como boolean, a diferencia de lo que sucede en e y e++ (donde la veraci dad se asocia con valores
disti ntos cero y la falsedad con cero). Si quiere emplear un valor no boolean dentro de una prueba boolean, como por ejem-
plo if(a), deber primero convertir el valor al tipo boolean usando una expresin condicional, como por ejemplo if(a != O) .
if-else
La instmccin if-else representa la fonna ms bsica de controlar el flujo de un programa. La clusula elsc es opcional, por
lo que se puede ver if de dos fonnas distintas:
o
if (expresin-booleana )
instruccin
if (expresin-booleana )
instruccin
else
instruccin
La expresin-booleana debe producir un resultado boolean. La instruccin puede ser una instmccin simple temlinada en
punto y coma o una instruccin compuesta, que es un gmpo de instmcciones simples encerrado entre llaves. All donde
empleemos la palabra instruccin querremos decir siempre que esa instmccin puede ser simple o compuesta.
Como ejemplo de ir-el se, he aqu un mtodo teste ) que indica si un cierto valor est por encima, por debajo o es equivalen-
te a un nmero objetivo:
72 Piensa en Java
//: control/lfElse.java
import static net.mindview.util.Print.*;
public class IfElse {
static int result = o;
1
- 1
static void test (int testval, int target) {
if(testval > target)
result = +1;
else if(testval < target)
result -1;
else
result O; /1 Coincidencia
public sta tic void main (String [] args) {
test(lO,5);
print(result) ;
test(5,10);
print(result) ;
test(S,5);
print (result) ;
/ * Output:
O
* /// ,-
En la pal1e central de test( ), tambin puede ver una instnlccin "else if ," que no es una nueva palabra clave sino una ins-
tnaccin cisc seguida de una nueva instruccin ir.
Aunque Java, como sus antecesores e y C++, es un lenguaje de "fonnato libre" resulta habitual sangrar el cuerpo de las ins-
trucciones de control de flujo, para que el lector pueda detenninar fcilmente dnde comienzan y dnde tenninan.
Iteracin
Los bucles de ejecucin se controlan mediante whil e, y for, que a veces se clasifican como instrucciones de ite-
racin. Una detenninada instruccin se repite hasta que la expresin-booleana de control se evale como fal seo La fonna
de un bucle whil e es:
while (expresi6n-booleanal
instruccin
La expresin-booleana se evala una vez al principio del bucle y se vuelve a evaluar antes de cada suces iva iteracin de la
instruccin.
He aqu un ejemplo si mple que genera nmeros aleatorios hasta que se cumple una detenninada condicin.
JJ: controlJWhileTest.java
JJ Ilustra el bucle while.
public class WhileTest {
static boolean condition( )
boolean result = Math.random( ) < 0.99;
System.out.print(result + tI, n);
return result;
public static void main(String[] args) {
while(condition())
System.out.println(nInside 'while' " );
System. out. println (U Exited 'while''') i
J* (Ejectelo para ver la salida) *JJJ :-
4 Control de ejecucin 73
El metodo condition() utiliza el mtodo random( ) de tipo sta tic de la biblioteca Math, que genera un valor double com-
prendido entre O y 1 (incluye 0, pero no l.) El valor result proviene del operador de comparacin <, que genera un resulta-
do de tipo booleano Si se imprime un valor boolean, automticamente se obtiene la cadena de caracteres apropiada "true"
o "false", La expresin condicional para el bucle wbile dice: "repite las instrucciones del cuerpo mientras que condition()
devuelva truc",
do-while
La forma de do-while es
do
instruccin
while (expresin-booleana ) j
La nica diferencia entre ""hile y do-while es que la instruccin del bucle do-while siempre se ejecuta al menos una vez,
incluso aunque la expresin se evale como false la primera vez. En un bucle while, si la condicin es false la primera
vez, la instruccin nunca llega a ejecutarse. En la prctica, do-while es menos comn que ",hUe.
for
El bucle for es quiz la forma de iteracin ms habitualmente utilizada. Este bucle realiza una inicializacin antes de la pri-
mera iteracin. Despus realiza una prueba condicional y, al final de cada iteracin, lleva a cabo alguna fOffila de "avance
de paso". La fonna del bucle for es:
for(inicializacin; expresin-booleana ; paso)
instruccin
Cualquiera de las expresiones inicializacin, expresin-booleana o paso puede estar vaca. La expresin booleana se com-
prueba antes de cada iteracin y, en cuanto se evale como false, la ejecucin contina en la lnea que sigue a la instruccin
for o Al final de cada bucle. se ejecuta el paso.
Los bucles for se suelen utilizar para tareas de "recuento";
/1: control/ListCharacters.java
/1 Ilustra los bucles IIfor
ll
enumerando
II todas las letras minsculas ASCII.
public class ListCharacters {
public statie void main(String[] args) {
for{char c = O; C e 128; c++)
if(Character.isLowerCase(c) )
System. out. println ("value: " + (int) c +
character: 11 + el i
/,
Output :
value: 97 character: a
value: 98 character: b
value: 99 character: e
value: 100 character: d
value: 101 character: e
value: 102 character: f
value : 103 character:
9
value: 104 character: h
value: 105 character: i
value: 106 character: j
* /// ,-
Observe que la variable e se define en el mismo lugar en el que se la utiliza, dentro de la expresin de control correspon-
diente al bucle for, en lugar de definirla al principio de main(). El mbito de e es la instruccin controlada por foro
74 Piensa en Java
Este programa tambi n emplea la clase "envoltorio" java.lang.Character. que no slo envuelve ellipo primili vo char den-
tro de un objeto, sino que tambi n proporciona Olras utilidades. Aqu el mlOdo static isLowcrCase( ) se usa para detectar
si el carcter en cuest in es una letra minscula.
Los lenguajes procedimental es tradicionales como e requieren que se definan todas las variabl es al comienzo de un bloque,
de modo que cuando el compilador cree un bloque, pueda asignar espacio para esas variabl es. En Java y C++, se pueden
di stribuir las declaraciones de variables por todo el bloque, definindolas en el lugar que se las necesite. Esto permite un
estilo ms natural de codificacin y hace que el cdigo sea ms fcil de entender.
Ejercicio 1:
Ejercicio 2:
Ejercicio 3:
Ejercicio 4:
Ejercicio 5:
(1) Escriba un programa que imprima los val ores comprendidos entre I y 100.
(2) Escriba un programa que genere 25 valores int aleatorios. Para cada valor, utilice una instruccin
if-elsc para clasificarlo como mayor que, menor que o igual a un segundo valor generado aleatoriamente.
(1) Modifique el Ejercicio 2 para que el cdigo quede rodeado por un bucle while ' infinito". De este mo-
do, el programa se ejecutar basta que lo interrumpa desde el teclado (nomlalmente, pul sando Control-C).
(3) Escriba un programa que utilice dos bucles ror anidados y el operador de mdulo (%) para detectar e
imprimir nmeros primos (nmeros enteros que no son di visibles por ningn nmero excepto por s mis-
mos y por 1).
(4) Repita el Ejercicio 10 del capitulo anterior, utilizando el operador ternario y una comprobacin de tipo
bit a bit para mostrar los unos y ceros en lugar de lnteger.toBinaryString( ).
El operador coma
Anterionnente en el captulo, hemos dicho que el operador coma (no el separador coma que se emplea para separar defi-
niciones y argumentos de mlOdos) slo ti ene un uso en Java: en la expresin de control de un bucle foro Tanto en la parte
correspondiente a la iniciali zacin como en la parte correspondiente al paso de la expresin de control , podemos incluir una
seri e de instrucciones separadas por comas, y dichas insrrucciones se evaluarn secuencialmente.
Con el operador coma, podemos definir mltiples variables dentro de una instruccin for, pero todas ellas deben ser del
mi smo tipo:
11 : control/CornmaOperator . java
public class CommaOperator {
public static void main (String [] args) {
for(int i = 1, j = i + la; i < 5; 1++, j = i * 2)
System.out.println("i = n + i + I! j :: U + j) i
1* Output:
i 1 j 11
i 2 j 4
i 3 j 6
i 4 j B
* ///,-
La definicin int de la instruccin for cubre tanto a i como a j . La parte de iniciali zacin puede tener cualquier nmero de
defmiciones de /In mismo tipo. La capacidad de definir variabl es en una expresin de control est limitada a los bucles foro
No puede emplearse esta tcnica en ninguna otra de las restantes instrucciones de seleccin o iteracin.
Puede ver que, tanto en la parte de inici ali zacin como en la de paso, las instmcciones se evalan en orden secuencial.
Sintaxis foreach
Java SE5 introduce ulla sintaxi s for nueva. ms sucinta, para utili zarl a con matri ces y cont enedores (hablaremos ms en
detalle sobre este tipo de objetos en los Captulos 16, Matrices, y 17, Anlisis detallado de los contenedores). Esta sintaxis
se denomina sintaxisjoreach (para todos), y quiere decir que no es necesario crear una variable int para efeCnl8f un recuen-
to a travs de lUla secuencia de elementos: el bucl e for se encarga de generar cada elemenlO automti camente.
4 Control de ejecucin 75
Por ejemplo, suponga que tiene una matriz de valores float y que quiere seleccionar cada uno de los elementos de la matri z:
1/ : controljForEachFloat . java
import java.util. *i
public class ForEachFloat
public static void main(String(] args) {
Random rand = new Random(47)
float f [] = new float [la] ;
far (int i = O; i < 10 ; i++ }
f[i] = rand.nextFloat() i
for ( float x f)
System.out.println(x) i
/ * Output:
0 .72711575
0.399 82635
0 .5309454
0.0534122
0.16020656
0.57799 757
0 .18847865
0.4170137
0.51660204
0 .73734957
* jjj , -
La matriz se rellena utilizando el antiguo bucle fOf , porque debe accederse a ella mediante un ndice. Puede ver la sintaxi s
foreach en la I nca:
f or (float x : f)
Esto define una variable x de tipo float y asigna secuencialmente cada elemento de fax.
Cualquier mtodo que devuelve una matriz es buen candidato para emplearlo con la sintaxisforeach. Por ejemplo, la clase
String tiene un mtodo toCharArray() que devuelve una matriz de char, por lo que podemos iterar fcilmente a travs de
los caracteres de una matriz:
JI : control/ForEachString . java
public c!ass ForEachString {
public static void mai n (String[] argsJ
for (char e : "An African Swallow". toCharArray ()
System.out.print(c + " " );
1* Output:
A n A f r i e a n
* jjj ,-
s w a 1 1 o w
Como podremos ver en el Captulo 11, Almacenamiento de objetos, la sintaxisjoreach tambin funciona con cualquier obje-
to que sea de tipo Iterable.
Muchas instrucciones for requieren ir paso a paso a travs de una secuencia de valores enteros como sta:
for (int i = o i < 100; i++)
Para este tipo de bloques. la sintaxi sforeach no funcionar a menos que queramos crear primero una matri z de valores int.
Para si mplificar esta tarea, he creado un mtodo denominado range() en net.mindview.utiI.Range que genera automtica-
mente la matriz apropiada. La intencin es que range( ) se utilice como importacin de tipo static:
11 : control/ForEachInt . java
import static net.mindview.util.Range.*
import static net.mindview.util.Print.*
76 Piensa en Java
public class ForEachInt {
public static void main (String [] args) {
for {int i , range(10)) // 0 .. 9
printnb (i + 11 ");
print () i
for {int i , range {5, 10)) // 5 .. 9
printnb (i + 11 " );
print () i
for (int i : range(5, 20, 3)) II 5 . . 20 step 3
printnb(i + " ");
print{)
1* Output :
O 1 2 3 4 5 6 7 8 9
5 6 7 8 9
58111417
* /// ,-
El mtodo range( ) est sobrecargado, lo que quiere decir que puede utilizarse el mismo mtodo con diferentes li stas de
argumentos (en breve hablaremos del mecanismo de sobrecarga). La primera fonna sobrecargada de range() empieza en
cero y genera valores hasta el extremo superior del rango, sin incluir ste. La segunda fonTIa comienza en el primer valor y
va hasta un valor menos que el segundo, y la tercera fonna incluye un valor de paso, de modo que los incrementos se rea-
lizan segn ese valor. range( ) es una versin muy simple de lo que se denomina generador, que es un concepto del que
hablaremos posteriormente en el libro.
Observe que aunque range() pemlite el uso de la sintaxisforeach en ms lugares, mejorando as la legibilidad del cdigo,
es algo menos eficiente, por lo que se est utili zando el programa con el fm de conseguir la mxima velocidad, conviene
que utilice un perfilador, que es una herramienta que mide el rendimiento del cdigo.
Podr observar tambin el uso de priotnb() adems de print(). El mtodo printnb() no genera un carcter de nueva lnea,
por lo que pemlite escribir una lnea en sucesivos fragmentos.
La no slo ahorra tiempo a la hora de escribir el cdigo. Lo ms importante es que facilita la lectura y comu-
nica perfectamente qu es lo que estamos tratando de hacer (obtener cada elemento de la matriz) en lugar de proporcionar
los detalles acerca de cmo lo estamos haciendo ("Estoy creando este ndice para poder usarlo en la seleccin de cada uno
de los elementos de la matri z"). Utilizaremos la sintaxi sforeach siempre que sea posible a lo largo del libro.
return
Diversas palabras clave representan lo que se llama un salIO incondicional, lo que simplemente quiere decir que el salto en
el flujo de ejecucin se produce sin reali zar previamente comprobacin alguna. Dichas palabras clave incluyen return,
break, continue y una forma de saltar a una instruccin etiquetada de fonna similar a la instruccin goto de otros lenguajes.
La palabra clave returD tiene dos objetivos: especifica qu valor devolver un mtodo (si no tiene un valor de retomo de
tipo void) y hace que la ejecucin salga del mtodo actual devolvi endo ese valor. Podemos reescribir el mtodo lest( ) pre-
cedente para aprovechar esta caracterstica:
1/ : control/lfElse2.java
import static net.mindview.util.Print.*
public class IfElse2 {
static int test (int testval, int target) {
if(testval > target }
return +1;
else if{testval < target )
return -1;
el se
return O 1I Coincidencia
1
- 1
pub l ic stat ic void main (String[] args l {
print ( test (1 0 , SI 1;
print ( test (S, 10 ;
print ( test ( 5,5)) ;
/ * Output :

*/1/ ;-
4 Control de ejecucin 77
No hay necesidad de la clusula else, porque el mtodo no continuar despus de ej ecutar una instruccin return.
Si no incluye una instruccin returo en un mtodo que devuel ve un valor void, habr una instruccin returo implcita al
fina l de ese mtodo, as que no siempre es necesario incluir dicha instmccin. Sin embargo, si el mtodo indica que va a
devolver cualqui er otro valor di stinto de void, hay que garantizar que todas las rutas de ej ecucin del cdi go devuel van un
valor.
Ejercicio 6: (2) Modifique los dos mtodos teste ) de los dos programas anteriores para que admitan dos argumentos
adicionales, begin y cnd, y para que se compruebe testval para ver si se encuentra dentro del rango com-
prendido entre begin y end (ambos incluidos).
break y continue
Tambi n se puede controlar el flujo del bucle dentro del cuerpo de cualqui er instruccin de iteracin utili zando break y con-
tinue. break provoca la saLida del bucle sin ejecutar el resto de la instmcciones. La instruccin continue detiene la ejecu-
cin de la iteracin actual y vuelve al principio del bucle para comenzar con la siguiente iteracin.
Este programa muestra ejemplos de break y continue dentro de bucl es ror y while:
// : control / BreakAndContinue.java
// Ilustra las palabras clave bre ak y continue .
import static net . mindview.util . Range .* ;
public class BreakAndContinue {
public static void main (String[ ] args ) {
f or {int i = O; i < 10 0 ; i++ ) {
if (i == 74 ) break; // Fuera del bucle
if {i % 9 1= O) c ontinue; // Siguiente iterac i n
System. out . print (i + " 11 ) ;
System. out.println {) ;
// Uso de f o reach:
for (int i : range (l OO))
if ( i == 74 ) break; // Fuera de l bucl e
if {i % 9 ! = O) centinue; // Siguiente iteracin
System. e ut.print (i + 11 " ) ;
System. out . print ln () ;
int i = O;
/ / Un "bucle infinito
ll
:
while ( t r ue ) {
i++ ;
i nt j = i * 27 i
i f ( j == 1269 ) b reak ; // Fuera del bucle f er
if ( i % 10 ! = O) centi nue; // Principio del bucle
System.out . print (i + 11 " ) ;
/ * Output:
78 Piensa en Java
o 9 18 27 36 45 54 63 72
o 9 18 27 36 45 54 63 72
1 0 2 0 3 0 4 0
* /// ,-
En el bucle for, el valor de i nunca llega a 100. porque la instruccin break hace que el bucle termine cuando vale 74.
Nommlmente. utilizaremos una instruccin break como sta slo si no sabemos cundo va a cumplirse la condicin de ter-
minacin. La instruccin continue hace que la ejecucin vuelva al principio del bucle de iteracin (incrementando por tanto
i) siempre que i no sea divisible por 9. Cuando lo sea, se imprimir el valor.
El segundo bucle for muestra el uso de la sintaxi sforeach y como sta produce los mismos resultados.
Finalmente, podemos ver un bucle while " infinito" que se estara ejecutando, en teora, por siempre. Sin embargo, dentro
del bucle hay una instmccin break que har que salgamos del bucle. Adems, podemos ver que la instruccin continue
devuelve el control al principio del bucle sin ejecutar nada de lo que hay despus de dicha instruccin continue (por tanto,
la impresin slo se produce en el segundo bucle cuando el valor de i es divi sible por 10). En la salida, podemos ver que se
imprime el valor O, porque O % 9 da como resultado O.
Una segunda forma del bucle infutito es ror(;;). El compilador trata tanto while(true) como ror(;;) de la mi sma fonna, por
lo que podemos uti lizar una de las dos fOffilas segn prefiramos.
Ejercicio 7: ( 1) Modifique el Ejercicio 1 para que el programa temne usando la palabra clave break con el valor 99.
Intente utilizar return en su lugar.
La despreciada instruccin "goto"
La palabra clave goto ha estado presente en muchos lenguajes de programacin desde el principio de la Infomltica. De
hecho, goto represent la gnesis de las tcnicas de control de programa en los lenguajes ensambladores: "Si se cumple la
condicin A, salta aqu; en caso contrario, salta all". Si leemos el cdigo ensamblador generado por casi todos los compi-
ladores, podremos ver que el control de programa contiene muchos saltos (el compi lador Java produce su propio "cdigo
ensamblador", pero este cdigo es ejecutado por la mquina virtual Java en lugar de ejecutarse directamente sobre un pro-
cesador hardware).
Una instmccin goto es un salto en el ni vel de cdigo fuente, yeso es lo que hizo que adquiriera una mala reputacin. Si
un programa va a saltar de un punto a otro, no existe alguna fonna de reorganizar el cdigo para que el flujo de control no
tenga que dar saltos? La instruccin goto lleg a ser verdaderamente puesta en cuestin con la publicacin del famoso art-
culo "GOlo considered harmfuf' de Edsger Dijkstra, y desde entonces la caza del goto se ha convertido en un deporte Illuy
popular, forzando a los defensores de esa instruccin a ocultarse cuidadosamente.
Como suele suceder en casos como ste, la verdad est en el punto medio. El problema no est en el uso de goto, sino en
su abuso, en detenninadas situaciones especiales goto representa. de hecho, la mejor fonna de estmcturar el flujo.
Aunque goto es una palabra reservada en Java, no se utiliza en el lenguaje. Java no dispone de ninguna instruccin goto.
Sin embargo, s que dispone de algo que se asemeja a un salto, y que est integrado dentro de las palabras clave break y
continue. No es un salto, sino ms bien una fornla salir de una instruccin de iteracin. La razn por la que a menudo se
asocia este mecanismo con las di scusiones relativas a la instruccin goto es porque utili za la mi sma tcnica: una etiqueta.
Una etiqueta es un identificador segui do de un carcter de dos puntos, como se muestra aqu:
labell ,
El lnico lugar en el que una etiqueta resulta til en Java es justo antes de una instruccin de iteracin. Y queremos decir
exactamente justo antes: no resulta conveniente poner ninguna instruccin entre la etiqueta y la iteracin. Y la nica razn
para colocar una etiqueta en una iteracin es si vamos a anidar otra iteracin o una instmccin switc h (de la que hablare-
mos enseguida) dentro de ella. Esto se debe a que las palabras clave break y continue nOn1lalmente slo intemlmpirn el
bucle actual, pero cuando se las usa como una etiqueta intemllnpen todos los bucles hasta el lugar donde la etiqueta se haya
definido:
labell ,
iteracin-externa
iteracin-interna
/ / ...
break ; / / 111
/ / ...
con tinue ; / / (2 )
/ / ..
continue labell JI (3)
/ / ...
break labell; / / 14 1
4 Control de ejecucin 79
En (1). la instruccin break hace que se sa lga de la iteracin interna y que acabemos en la iteraci n externa. En (2), la
instruccin continue bace que volvamos al principio de la iteracin interna. Pero en (3), la instruccin continue label) hace
que se salga de la iteracin imema y de la iteracin externa, hasta situarse en labell . Entonces, contina de hecho con la ite-
racin, pero comenzando en la iteracin externa. En (4), la instruccin break labell tambin hace que nos salgamos de las
dos iteraciones hasta situamos en labell , pero sin volver a entrar en la iteracin. De hecho, ambas iteraciones habrn fina-
lizado.
He aqu un ejemplo de utilizacin de bucles for :
JI : control/LabeledFor.java
/ / Bucles ter con tlbreak eqtiquetado
n
y "continue etiquetado".
import static net.mindview.util . Print. *
public class LabeledFor {
public static void main(String[] args)
int i = O;
outer: II Aqu no se pueden incluir instrucciones
for ( ; true i ) ( II bucle infinito
inner: II Aqu no se pueden incluir instrucciones
for ( i < 10; i++) {
print ( " i = " + i ) ;
if I i == 2) {
}
print ( "continue " );
continue
if I i == 3 ) (
print ( "break" ) ;
i++; II En caso contrario, i nunca
II se incrementa.
break;
if li == 71
print ("continue oucer");
i++; II En caso contrario, i nunca
II se incrementa.
continue outer;
if l i == SI
print ("break outer" ) ;
break outer;
for (int k = O; k < 5; k++ } {
if l k == 31 {
print ( " continue inner 11 ) ;
continue inner;
80 Piensa en Java
/1 Aqu no se puede ejecutar break o continue para saltar a etiquetas
1* Output:
i = O
continue inner
i = 1
continue inner
i = 2
continue
i = 3
break
i = 4
continue inner
i = 5
continue inner
i = 6
continue inner
i = 7
continue outer
i = 8
break outer
*/1/,-
Observe que break hace que salgamos del bucle for, y que la expresin de incremento no se ejecuta hasta el final de la pasa-
da a travs del bucle fOf o Puesto que break se salta la expresin incremento, el incremento se realiza directamente en el caso
de i = 3. La instruccin continue outer en el caso de i = 7 tambin ll eva al principio del bucle y tambin se salta el incre-
mento, por lo que en este caso tenemos tambin que realizar el incremento directamente.
Si no fuera por la instruccin break outer no habra fonna de salir del bucle ex temo desde dentro de un bucle interno, ya
que break por s mi sma slo permite sal.ir del bucle ms interno (lo mismo cabra decir de continue).
Por supuesto, en aquellos casos en que salir de un bucle impl.ique salir tambin del mtodo, basta con ejecutar return.
He aqu una demostracin de las instrucciones break y continue etiquetadas con bucles while:
ji : control/LabeledWhile.java
/ / Bucles while con "break etiquetado" y "continue etiquetado".
import static net.mindview.util.Print. *
public class LabeledWhile {
public static void main{String[] argsl {
int i = O;
outer:
while(true)
print ( "Outer while loop");
",hile (true ) {
i++
print{lIi = " + i)
if (i == 1) {
)
print ( " continue") ;
continue;
if(i == 3) {
print ( " continue outer")
continue outer
if ( i == 5 )
print ("break 11 l ;
break
if(i == 7)
print ("break outer" );
break outer;
1* Output:
Outer while loop
i = 1
continue
i = 2
i = 3
continue Quter
Outer while loop
i = 4
i = 5
break
Outer while loop
i = 6
i = 7
break outer
*///,-
Las mismas reglas siguen siendo ciertas para while:
4 Control de ejecucin 81
1. Una instruccin continue nonnal hace que saltemos a la parte superior del bucle ms interno y continuemos all
la ejecucin.
2. Una instruccin contin ue etiquelada hace que sa ltemos hasta la etiqueta y que volvamos a ejecutar el bucle si tua-
do justo despus de esa etiqueta.
3. Una instruccin break hace que finalice el bucle.
4. Una instruccin break etiquetada hace que finalicen todos los bucles hasta el que tiene la etiqueta, incluyendo
este ltimo.
Es importante recordar que la nica razn para utilizar etiquetas en Java es si tenemos bucles anidados y queremos ejecu-
tar una instruccin break o continue a travs de ms de un nivel.
En el artculo "Goto considered harmfllf' de Dijkstra. la objecin especfica que l haca era contra la utili zac in de etique-
tas, no de la instruccin goto. Su observacin era que el nmero de errores pareca incrementarse a medida que lo haca el
nmero de etiquetas dentro de un programa, y que las etiquetas en las instrucciones goto hacen que los programas sean ms
dificiles de analizar. Observe que la etiquetas de Java no presentan este problema, ya que estn restringidas en cuanto a su
ubicacin y pueden utilizarse para transferi r el control de fonna arbitraria. Tambin merece la pena observar que ste es uno
de esos casos en los que se hace ms til una detemlinada caracterstica del lenguaje reduciendo la potencia de la corres-
pondiente instmccin.
switch
La palabra clave switch a veces se denomina instruccin de seleccin. La instmccin switch permite seleccionar entre dis-
tintos fragmentos de cdigo basndose en el valor de una expresin entera. Su fonna general es:
switch(selector-entero) {
case valor-entero!
case valor-entero2
case valor-entero3
case valor-entero4
case valor-enteroS
/ / . . .
default: instruccin;
instruccin break;
instruccin break;
instruccin break;
instruccin break;
instruccin break;
82 Piensa en Java
SeleCfOr-entero es una expresin que genera un valor entero. La instruccin switch compara el resultado de selecfOr-enrero
con cada valor-el1fero. Si encuentra una coi ncidencia, ejecuta la correspondiente instruccin (una sola instmccin o mlti-
ples instmcciones: no hace falta usar llaves). Si no hay ninguna coi ncidencia, se ejecuta la instruccin de default.
Observar en la defini cin anterior que cada case finali za con una instmccin break, lo que hace que la ejecucin salte al
final del cuerpo de la instruccin switch. sta es la forma convencional de construir una instruccin switch, pero la instnlc-
cin break es opcional. Si no se incluye, se ejecutar el cdi go de las instmcciones case si tuadas a continuacin hasta que
se encuentre una instmccin break. Aunque normalmente este comportami ento no es el deseado, puede resultar ltil en oca-
siones para los programadores expertos. Observe que la ltima instruccin, situada despus de la clusula default, no tiene
una instruccin break porque la ejecucin contina justo en el lugar donde break hara que continuara. Podemos incluir,
si n que ello represente un probl ema, una instmccin break al final de la clusula default si consideramos que resulta impor-
tante por razones de estilo.
La instmccin s,,'itch es una fonna limpia de implementar selecciones multi va (es decir, sel ecciones donde hay que elegir
entre diversas rutas de ejecucin), pero requiere de un selector que se evale para dar un valor entero, como int o charo Si
se desea emplear, por ejemplo, una cadena de caracteres o un nllmero en coma flotante como selector, no funcionar en una
instmccin switch. Para los tipos no enteros, es preci so emplear una serie de instmcciones ir. Al final del siguiente captu-
lo, veremos que la nueva caracterstica enum de Java SE5 ayuda a suavizar esta restriccin, ya que los valores enum estn
diseiiados para funcionar adecuadamente con la instruccin switch.
He aqu un ejemplo en el que se crean letras de manera aleatoria y se detennjna si son vocales o consonantes:
//: control/VowelsAndConsonants.java
/1 Ilustra la instruccin switch.
import java . util .*;
import static net.mindview. ucil . Print .* ;
public class VowelsAndConsonants {
public static void main(String[] args}
Random rand = new Random(47)
for(int i = O; i < 1 00; i++)
y,
n,
z,
b,
r,
n,
y,
g,
c,
f,
o,
int e = rand.nextInc(26) + 'a';
printnb ( (char) c + ", 11 + C + ": " ) ;
switch(c) {
case 'a':
case 'e':
case 'i':
case ' o ':
case ' u': print("vowel"l i
break;
case 'y':
case 'w': print ("Sometimes a vowel");
break;
defaul t: print (" consonant") ;
/ *
Output :
121, Sometimes a vowel
110 , consonant
122, consonant
98, consonant
114: consonant
110, consonant
121 : Somecimes a vowel
103, consonant
99, consonanc
102, consonant
1110 vowel
w, 119: Sometimes a vowel
z, 122: consonant
, /// ,-
4 Control de ejecucin 83
Puesto que Random.next lnt(26) genera un valor comprendido entre O y 26, basta con sumar ' a' para generar las letras
minsculas. Los caracteres encerrados entre comillas simples en las instmcciones case tambin generan valores enteros que
se emplean para comparacin.
Observe cmo las instrucciones case pueden "apilarsc" unas encima de otras para proporcionar mltiples coincidencias para
un detenllinado fragmento de cdigo. Tenga tambin en cuenta que resulta esencial colocar la instruccin break al final de
una clusula case concreta; en caso contrario, el control no efectuara el salto requerido y continuara simplemente proce-
sando el caso siguiente.
En la instruccin:
int e = rand.nextInt(26) + 'a' i
Random. next l nt( ) genera un valor int aleatorio comprendido entre O y 25, al que se le suma el valor ' a '. Esto quiere decir
que 'a' se convierte automticamente a int para real izar la suma.
Para imprimir c como carcter, es necesario proyectarlo sobre el tipo char; en caso contrario, generana una salida de tipo
entero.
Ejerci cio 8 : (2) Cree una instruccin switch que imprima un mensaje para cada case, y coloque el switch dentro de un
bucle fo r en el que se pruebe cada uno de los valores de case. Incluya una instruccin break despus de
cada case y compruebe los resultados; a conti nuacin, elimine las instrucciones break y vea lo que suce-
de.
Ejercicio 9: (4) Una secllencia de Fibonacci es la secuencia de nmeros 1, 1,2,3,5,8, 13,21,34, etc., donde cada
nmero (a partir del tercero) es la suma de los dos anteriores. Cree un mtodo que tome un entero como
argumento y muestre esa cantidad de nmeros de Fibonacci comenzando por el principio de la secuencia;
por ejemplo, si ejecuta java Fibonacci 5 (donde Fi bonacci es el nombre de la clase) la salida seria: 1, 1.
2,3,5.
Ejerci cio 10: (5) Un nlmero vampiro tiene un nmero par de dgitos y se forma multipli cando una pareja de nmeros
que contengan la mitad del nmero de dgitos del resultado. Los dgitos se toman del nmero original en
cualquier orden. No se permiten utilizar parejas de ceros finales. Ent re los ejemplos tendramos:
Resumen
1260 ~ 21 60
1827 ~ 21 87
2 1 8 7 ~ 2 7 81
Escriba un programa que detennine todos los nmeros vampiro de 4 dgitos (problema sugerido por Dan
Forhan).
Este captulo concluye el estudio de las caractersticas fundamentales que podemos encontrar en la mayora de los lengua-
jes de programacin: clculo, precedencia de operadores, proyeccin de tipos y mecani smos de seleccin e iteracin. Ahora
estamos li stos para dar los siguientes pasos, que nos acercarn al mundo de la programacin orientada a objetos. El siguien-
te captulo cubrir las importantes cuestiones de la inicializacin y limpieza de objetos, a lo que seguir, en el siguiente capi-
rulo, el concepto esencial de ocultacin de la implementacin.
Pueden encontrarse las soluciones a los ejercicios seleccionados en el documento electrnico rhe Thi"ki"g in Java Am/Olllled So/mion Cuide. disponible
para la venta en l\w\I:MindViel\:nel.
Ini cializacin
y li mpieza
A medida que se abre paso la revolucin infonlltica, la programacin "no segura" se ha con-
vertido en uno de los mayores culpables del alto coste que tiene el desarrollo de programas.
Dos de las cuestiones relativas a la seguridad son la inicializacin y la limpieza. Muchos errores en e se deben a que el pro-
gramador se olvida de inicializar una variable. Esto resulta especialmente habitual con las bibliotecas, cuando los usuarios
no saben cmo inicializar un componente en la biblioteca, e incluso ni siquiera son conscientes de que deban hacerlo. La
limpieza tambin constituye un problema especial, porque resulta fcil olvidarse de un elemento una vez que se ha tenni-
nado de utilizar, ya que en ese momento deja de preocuparnos. Al no borrarlo. los recursos utilizados por ese elemento que-
dan retenidos y resulta fcil que los recursos se agoten (especialmente la memoria).
C++ introdujo el concepto de constructor, un mtodo especial que se invoca automticamente cada vez que se crea un ob-
jeto. Java tambin adopt el concepto de constructor y adems di spone de un depurador de memoria que se encarga de libe-
rar automticamente los recursos de memoria cuando ya no se los est utilizando. En este captulo, se examinan las
cuestiones relativas a la inicializacin y la limpieza, as como el soporte que Java proporciona para ambas tareas.
Inicializacin garantizada con el constructor
Podemos imaginar fcilmente que sera sencillo crear un mtodo denominado in iti alize( ) para todas las clases que escri-
biramos. El nombre es una indicacin de que es necesari o invocar el mtodo antes de utilizar el objeto. Lamentablemente,
esto indica que el usuario debe recordar que hay que invocar ese mtodo. En Java, el diseador de una clase puede garanti-
zar la inicializacin de todos los objetos proporcionando un constructor. Si una clase tiene un construclOr, Java invoca auto-
mticamente ese constructor cuando se crea un objeto, antes incluso de que los usuarios puedan llegar a utili zarlo. De este
modo, la ini cializacin queda garantizada.
La siguiente cuestin es cmo debemos nombrar a este mtodo, y existen dos problemas a este respecto. El primero es que
cualquier nombre que usemos podra coli sionar con otro nombre que quisiramos emplear como miembro de la clase. El
segundo problema es que debido a que el compilador es responsable de invocar el constructor. debe siempre conocer qu
mtodo invocar. La solucin en e++ parece la ms fcil y lgica, de modo que tambin se usa en Java: el nombre del cons-
tructor coincide con el nombre de la clase. De este modo, resulta fcil invocar ese mtodo automticamente durant e la ini-
cializacin.
He aqu una clase simple con un constructor:
// : initializationfSimpleConstructor.java
// Ilustracin de un constructor simple.
class Rock {
Rock () { / f ste es el constructor
System.out.print(IIRock 11 ) j
public class SimpleConstructor {
public static void main (String[] args) {
for(int i '" O; i < 10 i++)
86 Piensa en Java
new Rack () i
1* Output:
Rack Rack Rack Rock Rock Rack Rock Rack Rack Rack
* /// ,-
Ahora, cuando se crea un objeto:
new Rock() i
se asigna el correspondiente espacio de almacenamiento y se invoca el constmctor. De este modo, se garantiza que el obje-
to est apropiadamente iniciali zado antes de poder utilizarlo.
Observe que el estilo de codificacin consistente en poner la primera letTa de todos los mtodos en minscula no se aplica
a los constructores, ya que el nombre del constructor debe coincidir exactamente con el nombre de la clase.
Un constmctor que no tome ningn argumcnto se denomina conslrucfOr predeterminado. Nonnalmente, la documentacin
de Java utiliza el tnnino constructor sin argumentos, pero el trmino "constructor predeterminado" se ha estado utilizando
durante muchos ailos antes de que Java apareciera, por lo que prefiero uti lizar este ltin10 tnnino. De todos modos, como
cualquier otro mtodo, el constructor puede tambin tener argumentos que nos penniten especificar cmo hay que crear el
objeto. Podemos modificar fcilmente el ejemplo anterior para que el constructor tome un argumento:
11 : initialization/SimpleConstructor2.java
II Los constructores pueden tener argumentos .
class Rack2 (
Rack2(int i)
System.out . print( "Rack 11 + i + 11 ") i
public class SimpleCanstructor2 {
public static void main (String[] args) {
for (int i = o i < 8 i i++ )
new Rock2 ( i ) i
1* Output :
Rock O Rack 1 Rack 2 Rack 3 Rack 4 Rack S Rack 6 Rock 7
* /// , -
Los argumentos dcl constructor proporcionan una forma de pasar parmetros para la iniciali zacin de un objeto. Por ejem-
pl o, si la clase Tree (rbol) tiene un constmctor que toma como argumento tm nico nmero entero que indica la altura del
rbol, podremos crear un objeto Tree como sigue:
Tree t = new Tree (12) i / I rbal de 12 metras
Si Tree(int) es el nico constmctor del que di sponemos, el compilador no nos pennitir crear un objeto Tree de ninguna
otra forma.
Los constructores eliminan una amplia clase de problemas y hacen que el cdigo sea mas fcil de leer. Por ejemplo, en el
fragmento de cdigo anterior, no vemos ninguna llamada explcita a ningn mtodo initialize() que est conceptualmente
separado del acto de creacin del objeto. En Java, la creacin y la inicializacin son conceptos unificados: no es posibl e
tener la una si n la otra.
El constructor es un tipo de mtodo poco usual porque no ti ene valor de retorno. Existe una clara diferencia entre esta cir-
cunstancia y los mtodos que devuelven un valor de retorno void, en el sentido de que estos ltimos mtodos no devuelven
nada. pero seguimos teniendo la opcin de hacer que devuel van algo. Los constnlctores no devuel ven nada nunca, y no tene-
mos la opcin de que se comporten de otro modo (la expresin De\\' devuel ve una referencia al objeto recin creado, pero
el constructor mi smo no tiene un va lor de retomo). Si hubiera valor de retomo y pudiramos seleccionar cul es, el compi-
lador necesitara saber qu hacer con ese valor de retomo.
Ejercicio 1: (1) Cree una clase que contenga una referencia de tipo String no iniciali zada. Demuestre que esta referen-
cia la inicializa Java con el valor null .
5 Inicializacin y limpieza 87
Ejercicio 2: (2) Cree una clase con un campo Stri ng que se inicialice en el punto donde se defina, y otro campo que-
sea inicializado por el constlUclOr. Cul es la diferencia entre- las dos tcnicas?
Sobrecarga de mtodos
U!1<) de las caractersticas ms importantes en cualquier lenguaje de programacin es el uso de nombres. Cuando se crea un
objeto. se proporciona un nombre a un rea de almacenamiento. Un mtodo, por su paJ1e, es un nombre que sirve para desig-
nar una accin. Utilizamos nombres para referimos a todos los objetos y metodos. Una serie de nombres bien elegida crea-
r un sistema que resultar ms fcil de entender y modificar por otras personas. En cierto modo, este problema se parece
al acto dL' escribir literanlra: el objetivo es comunicarse con los lectores.
Todos los problemas surgen a la hora de aplicar el concepto de matiz del lenguaje humano a los lenguajes de programacin.
A menudo. una misma palabra tiene diferentes significados: es lo que se denomina palabras polismicas, aunque en el campo
de la programacin diramos que estn sobrecargadas. Lo que hacemos normalmente es decir "Lava la camisa", "Lava el
coche" y '"Lava al pelTa": sera absurdo vernos forzados a decir "camisaLava la camisa". "cocheLava el coche" y
"perroLava el pelTa" simplemente para que el oyente no se vea forzado a distinguir cul es la accin que tiene que realizar.
La mayoria de los lenguajes humanos son redundantes, de modo que podemos seguir detenninando el signiticado an cuan-
do nos perdamos algunas de las palabras. No necesitamos identificadores unvocos: podemos deducir el significado a par-
tir del contexto.
La mayora de los lenguajes de programacin (y C en panicular) exigen que dispongamos de un identificador unvoco para
cada metodo (a menudo denominados.fimciones en dichos lenguajes). As que no se pl/ede tener una funcin denominada
print( ) para imprimir entt:'"ros y otra denominada igualmente print( ) para imprimir nmeros c-n coma flotante, cada una de
las funciones necesitar un nombre distintivo.
En Java (yen e+-t-). hay otro factor que obliga a sobrecargar los nombres de los mtodos: cl constructor. Puesto que el nom-
bre del constructor est predetemlinado por el nombre de la clase, slo puede haber un nombre de constmctor. Pero enton-
ces. qu sucede si queremos crear un objeto utilizando varias formas distintas? Por ejemplo, suponga que construimos una
clase cuyos objetos pueden inicializarse de la fonna nomlal o leyendo la infonnacin de un archivo. Harn falta dos cons-
tructores, el constructor predeterminado y otro que tome un objeto String como argumento, a travs del cual suministrare-
mos el nombre del archivo que hay que utilizar para inicializar el objeto. Ambos metodos sern constructores, as que
tendrn el mismo nombre: el nombre de la clasc. Por tanto, la sobrecarga de metodos resulta esencial para poder utilizar el
mismo nombre de metodo con diferentes tipos de argumentos. Y. aunque la sobrecarga de metodos es obligatoria para los
constructores. tambin resulta til de manera general y puede ser empleada con cualquier otro mtodo.
He aqu un ejemplo que muestra tanto constmclores sobrecargados como metodos normales sobrecargados:
/ /: initialization/Overloadi ng. java
// Ilustracin del mecanismo de sobrecarga
/.1 canto de constructores como de mtodos normales.
~ m p o r scatic net.mindview.uti l.Print.*
class Tree {
ine height i
Tree () {
print ( 11 Plant i,-g a seedling")
height =: O i
Tree(int inicialHeight)
height =: initialHeight
print ("Creating new Tree that is " +
height + " feet. tall") i
void info ()
print ("Tree is + height + 11 feet tall")
void info(String s)
print (s + ": Tree is 11 + height + " feet tall")
88 Piensa en Java
public class Overloading {
public static void main {String [ ] args ) {
for (int i = Di i < 5; i++} {
Tree t = new Tree {i ) ;
t. info () ;
t. info ( lIoverloaded methad" ) ;
1/ Constructor sobrecargado:
new Tree () ;
/ * Output:
Creating new Tree that is O feet tall
Tree is O feet tall
overloaded methad: Tree is O feet tall
Creating new Tree that is 1 feet tall
Tree is 1 feet tall
overloaded methad: Tree is 1 feet tall
Creating new Tree that is 2 feet tall
Tree is 2 feet tall
overloaded methad: Tree is 2 feet tall
Creating new Tree that is 3 feet tall
Tree is 3 feet tall
overloaded methad: Tree is 3 feet tall
Creating new Tree that is 4 feet tall
Tree is 4 feet tall
overloaded methad: Tree is 4 feet tall
Planting a seedling
./// >
Con estas definiciones, podemos crear un objeto Tree tanto a partir de una semilla, sin utilizar ningn argumento, como en
fonna de planta criada en vivero, en cuyo caso tendremos que indicar la alnlra que tiene. Para soportar este comportamien-
to, hay un constructor predetenninado y otro que toma como argumento la altura del rbol.
Tambin podemos invocar el mtodo info() de varias formas di stintas. Por ejemplo, si queremos imprimir un mensaje adi-
cional, podemos emplear info(String), mientras que utilizaramos info() cuando no tengamos nada ms que decir. Sera bas-
tante extrao proporcionar dos nombres separados a cosas que se corresponden, obviamente. con un mismo concepto.
Afortunadamente, la sobrecarga de mtodos nos pennite utilizar el mismo mtodo para ambos.
Cmo se distingue entre mtodos sobrecargados
Si los mtodos tienen el mismo nombre. cmo puede saber Java a qu mtodo nos estamos refiriendo? Existe una regla
muy simple: cada mtodo sobrecargado debe tener una lista distintiva de tipos de argumentos.
Si pensamos en esta regla durante un momento, vernos que tiene bastantes sentido. De qu otro modo podra un programa-
dor indicar la diferencia entre dos mtodos que tienen el mismo nombre, si no es utilizando las diferencias entre los tipos
de sus argumentos?
Incluso las diferencias en la ordenacin de los argumentos son suficientes para distinguir dos mtodos entre s, aunque nor-
malmente no conviene emplear esta tcnica, dado que produce cdi go difici l de mantener:
1/ : initialization/OverloadingOrder.java
1/ Sobrecarga basada en el orden de los argumentos.
import static net.mindview.util.Print.*
public class OverloadingOrder
static void f(String S, int i )
print ( " String : " + S + int: u + i )
static void f (int i, String s) {
print (" int: 11 + i + ", String: 11 + s);
public static void main.(String [] args) {
f ("String first", 11) i
f {99, !tlnt first") i
1* Output :
String: String first, int: 11
int: 99, String: Int first
*///,-
5 Inicializacin y limpieza 89
Los dos mtodos f() tienen argumentos idnticos, pero el orden es distinto yeso es lo que los hace diferentes.
Sobrecarga con primitivas
Una primitiva puede ser automticamente convertida desde un tipo de menor tamao a otro de mayor tamaiio, y esto puede
inducir a confusin cuando combinamos este mecanismo con el de sobrecarga. El siguiente ejemplo ilustra lo que sucede
cuando se pasa una primitiva a un mtodo sobrecargado:
11: initialization/PrimitiveOverloading.java
II Promocin de primitivas y sobrecarga.
import static net .mindview.util . Print. *
public class PrimitiveOverloading {
void f1 (char xl { printnb("f 1 (char) 11) i }
void f1 (byte x ) ( printnb("f1(byte) " ); }
void f1 (short x ) { printnb{"f1(short ) 11) i
void f1 (int x ) { printnb ( "f 1(int) ") }
void f1(long x ) ( printnb("fl(long) "); }
void f1 (fl oat x ) { printnb {"f1 (float ) ,, } i
void f1 (double x) { printnb ("fl (double) ");
void f2 (byte x) { printnb ( " f2 (byte ) " );
void f2(short x l { printnb(Uf2(short) " }i
void f2 (int x) ( printnb("f2 (int) " ) ; }
void f2(long x) ( printnb("f2(long) "); }
void f2(float x ) { printnb(Uf2(float) ")i }
void f2(double x) (printnb("f2(double) ");
void f3(short xl { printnb("f3(short) ")i
void f3(int x) { printnb(
lt
f3(int) tI)i }
void f3 (long x) ( printnb("f3 (long) " ); }
void f3 (f loat x l { printnb{
tl
f3 (float ) 11);
void f3{double xl {printnb(lIf3{double) "),
void f4(int x ) { printnb ("f4(int ) ") i }
void f4 (long x) { printnb ( " f4 (long) " );
void f4 (float x l { printnb {
fl
f4 {float ) ");
void f4 (double x ) { printnb("f4 (double ) "),
void fS(long x l { printnb ( lIfS (long) " ) }
void f5 ( float x ) { printnb ( "f5 ( float ) " );
void fS{double x l { printnb (" fS (double l "),
void f6{float x l { printnb ( "f6 (float) 11 ) i }
void f6(double x l { printnb(
lI
f6(double) ") i
void f7 {double x l { printnb("f7(doublel " ) ,
90 Piensa en Java
void testConstVal()
printnb (U 5: n);
f1(S) ;f2(S) ;f3(S) ;f4(S) ;fS(S) ;f6(S) ;f7(S); print();
void testChar () {
char x = 'x' i
printnb ("char: ") i
fl(x) ;f2 (x) ;f3 (x ) ;f4 (x) ;fS(x) ;f6(x) ;f7(x); print();
void testByte () {
byte x = O i
printnb (ltbyte: ");
f1(x) ;f2 (x ) ;f3(x) ;f4 (x) ;fS(x) ;f6(x) ;f7(x); print();
void testShort () {
short x = O;
printnb (" short: n) i
f1 (x ) ; f2 (x ) ; f3 (x ) ; f4 (x ) ; fS (x) ; f6 (x) ; f7 (x ); print () ;
void testlnt (l {
int x = O;
printnb ( " int: ");
f1(x) ;f2(x) ;f3(x) ;f4 (x ) ; fS(x) ;f6(x) ;f7 (x) ; print();
)
void testLong () {
long x = O;
printnb(1I1ong : It);
f1(x) ;f2 (x ) ;f3 (x ) ;f4 (x ) ;fS(x) ;f6(x) ;f7(x); print () ;
void testFloat () {
float x = O;
printnb("float: ");
f1 (x ) ; f2 (x) ; f3 (x ) ; f4 (x ) ; fS (x) ; f6 (x) ; f7 (x); print () ;
void testDouble () {
double x = O;
printnb ("doubl e: ");
f1 (x) ; f2 (x ) ; f3 (x ) ; f4 (x ) ; fS (x ) ; f6 (x) ; f7 (x ); print () ;
public static void main (String [] args) {
PrimitiveOverloading p =
new PrimitiveOverloading();
p.testConstVal() ;
p.testChar() ;
p. testByte () ;
p. testShort () ;
p.testlnt() ;
p. testLong () ;
p. testFloat () i
p. testDouble () ;
/ * Output:
50 f1 (int) f2(int) f3 (int) f4(int) fS ( long ) f6 ( float ) f7 (double )
charo f1 (char ) f2 (int) f3 (int) f4 (int) fS ( long) f6 ( float ) f7 (double)
byte o f1 (byte ) f2 (byte) f3 (short) f4 (i nc ) fS (long) f6 ( float ) f7 (double )
shorto f1(short) f2 (short ) f3(short) f4 (int ) fS(long ) f6 ( float ) f7 (double )
into f1 ( int ) f2(int) f3 ( inc) f4 (int) fS(long) f6 ( float ) f7 (double)
longo f1 (long) f2 ( long) f3 (long) f4 (long) fS (long) f6 (float ) f7 (double)
floato f1(float ) f2(float) f3(float) f4(float) fS(float ) f6 ( float) f7 (double)
doubleo fl(double) f2 (double ) f3(double) f4(double) fS (double) f6(double) f7(double)
*/// 0-
5 Inicializacin y limpieza 91
puede ver que el valor constante 5 se trata como ut, por lo que si hay di sponibl e un mtodo sobrecargado que lOma un obje-
to ot. se utilizara dicho mlOdo. En todos los dems casos, si lo que tenemos es un tipo de datos ms pequeo que el argu-
mento del mtodo, di cho tipo de datos ser promocionado. char produce un efecto li geramente diferente, ya que, si no se
encuentra una correspondencia exacta con cbar, se le promociona a int.
Qu sucede si nuestro argumenlO es mayor que el argumento esperado por el mtodo sobrecargado? Una modificacin del
programa ant eri or nos da la respuesta:
11: initialization/Demotion.java
JI Reduccin de primitivas y sobrecarga.
import static net.mindview.util.Print.*;
public class Demotion {
void fl (char x) { print (Ufl {char} ji) ;
void fl (byte x) { print ("fl (byte)") ;
void fl (short x) { print (" fl (short) ") ;
void fl{int x) { print{ufl{int)") }
void fl (long x) { print ( "fl (long)"); )
void fl(float x) { print("fl(float) " );
void fl (double x ) { print (ti fl (double) ti) ;
void f2 (char x ) { print {U f2 {char} U}; }
void f2 (byte x) { print ( " f2 (byte) " ) ; )
void f2(short xl {print{ Uf2 {shortl " )
void f2(int x) { print("f2(int)"); }
void f2 (long x) { print ( "f2 (long)"); )
void f2(float x l { print("f2{floatl");
void f3 (char x) { print ( "f3 (charl U) ; }
void f3 (byte x) { print ( " O (byte) " ); )
void f3{short xl {print(Uf3{short}")
void f3(int x) {print("O(int) " ) ; }
void f3 (long x) { print ("O (long)");
void f4 (char xl print ( "f4 (char) 11);
void f4 (byte xl print ( "f4 (byte) 11 l ;
void f4(short xl {print ( Uf4 {short)U)
void f4 (int xl { print{lIf4 ( int ) 11); }
void f5(char xl { print{tlf5(char) It}
void f5(byte x) { print("f5(byte) ");
void f5(short x) { print("f5(short)II);
void f6{char xl
void f6 (byte xl
void f7 (char xl
void testDouble()
double x = o;
print ("f6 {charl ");
print {"f6 (byte) 11)
print{l'f7(charl ti)
print {Udouble argument : 11 l i
fl (x) ; f2 ( (float) x) ; f3 ( (long) x) ; f4 ( (int) x ) ;
f5 ( (short) x) ; f6 ( (byte) x ) ; f7 ( (char) x) ;
public static void main(String[] args) {
Demotion p = new Demotion()
p.testDouble() i
1* Output :
double argument:
92 Piensa en Java
f1 (double )
f2 (float )
f3 ( long )
f4 (int )
f5 (short }
f6 (byte }
f7 (char )
* /// ,-
Aqu, los mtodos admiten valores primitivos ms pequeos. Si el argumento es de mayor anchura, entonces ser necesario
efectuar una conversin de estrechamiento mediante una proyeccin. Si no se hace esto, el compilador generar un men-
saje de error.
Sobrecarga de los valores de retorno
Resulta bastante habitual hacerse la pregunta: "Por qu slo tener en cuenta los nombres de clase y las listas de argumen-
tos de los mtodos? Por qu no distinguir entre los mtodos basndonos en sus valores de retorno?" Por ejemplo, los
siguientes dos mtodos, que tienen el mismo nombre y los mismos argumentos, pueden distinguirse fcilmente:
void f () ( )
int f () ( return 1; )
Esto podra funcionar siempre y cuando el compi lador pudiera detenninar inequvocamente el significado a partir del con-
texto, como por ejemplo en int x = f(). Sin embargo, el lenguaje nos permite invocar un mtodo e ignorar el valor de retor-
no, que es una tcnica que a menudo se denomina invocar un mtodo por su efecto colateral, ya que no 110S preocupa el
valor de retorno, sino que queremos que tengan lugar los restantes efectos de la invocacin al mtodo. Luego entonces, si
invocamos el mtodo de esta fonna:
f () ;
Cmo podra Java detenninar qu mtodo f( ) habra que invocar? Y cmo podra determinarlo alguien que estuviera
leyendo el cdigo? Debido a este tipo de problemas, no podemos utilizar los tipos de los valores de retomo para distinguir
los mtodos sobrecargados.
Constructores predeterminados
Como se ha mencionado anteriormente, un constructor predetenninado (tambin denominado "constructor sin argumentos")
es aquel que no tiene argumentos y que se uti liza para crear un "objeto predetenninado". Si creamos una clase que no tenga
constructores, el compilador crear automticamente un constructor predetenninado. Por ejemplo:
11 : i n i t ializat i on / Defaul tCon s tructor. j a va
c l ass Bird ()
public class Defau!tConstructor
public stati c vo id main (Stri ng[ ] args l
Bird b new Bird () ; II Defau l t!
)
/// o-
La expresin
new Bird ()
crea un nuevo objeto y ll ama al constructor predetemlinado, incluso aunque no se baya definido lino de manera explci ta.
Sin ese constructor predeternlinado no dispondramos de ningn mtodo al que invocar para construir el objeto. Sin embar-
go, si definimos algn constructor (con o sin argumentos), el compi lador no sin/enlizar ningn constructor por nosotros:
11 : initialization/ NoSynthesis.java
class Bird2 {
B-d2 {int i ) {}
2ird2 1double d I {}
!."'L:bl ':"c class NoSynthes i s {
public s : atic void main (String [J al-gs ) {
/ / ! Bird2 b = n ew Bird2 () ; / / No h ay p redetermi nado
Bird2 b2 new Bird2(1) ;
Bird2 b3 '" r.e\,o Bird2 ( 1. O) ;
/ I / ,-
Si c:,c ribimos:
:..e.w Bi rd2 ()
5 Inicializacin y limpieza 93
el compilador nos indicara que no puede localizar ninglll constmctOr que se corresponda con la instruccin que hemos
escr ito. Cuando no defi nimos explcitamente ningn constmctor. es como si el compi lador dijera: "Es necesario ut il izar
algn constmcror. as que djame definir uno por ti". Pero, si escribimos al menos constructor. el compilador dice: "Has
escrit o un constructor, as que tu sabrs 10 que ests haciendo: si 110 has incluido uno predeterminado es porque no quie-
res hacerlo".
Ejercicio 3:
Ejercici o 4:
Ejercici o 5:
Ejercicio 6:
Ejercicio 7:
(1) Cree ulla clase con un constructor predeterminado (uno que no tome ningn argumento) que imprima
un mensaje. Cree UIl objeto de esa clase.
( 1) Aiiada un constructor sobrecargado al ejercic io anterior que admita un argumento de tipo String e
imprima la correspondiente cadena de cflracteres junto con el mensaje.
(2) Cree IIna clase denominada Dog con un mtodo sobrecargado bark( ) (mtodo ladrar"). Este mtodo
debe estar sobrecargado basndose en diversos tipos de datos primi tivos y debe imprimir diferel1les tipos
de ladridos. gruidos, etc .. dependiendo de la versin sobrecargada que se invoque. Escriba un mtodo
man( ) que invoque todas las distintas versiones.
( 1) .\!lodilique el ej ercici o 3merior de modo que dos de tos mtodos sobrecargados tengan dos argumen-
tos (de dos tipos di stintos). pero en orden inverso LIno respecto del otro. Verifiquc que estas detniciones
funcionan.
(1) Crec una clase ~ n ningn cnstruc(Qr y luego cree un objeto de esa clase en main( ) para veriticnr que
SI.? sinteti za automaieamente el constructor predetcnninado.
La palabra clave this
Si tenemos dos objetos del mismo tipo llamados a y b. p0c! emos preguntamos cmo es posible iIwocar un metodo pl'C'I( )
p,m-l ambos obj elOs (110fl.l: la palabra illgksa peel significa "pelar ulla fruta ". que en este ejemplo de programacin es una
banana ):
ji : initialization/Bana na Peel . java
class Sa nana { vOld peel l i nt i) (! * * 1 )
public class BananaPe el {
p ublic s:atic void main ( String [J al-gs ) {
Banana a = new Banana() I
b = new Banana() i
a . peel tll i
b . peel (2) ;
)
I/! , -
Si sl o hay un nico mtodo denominado peel( ), cmo puede ese metoJo saber si est siendo llamado para el objcro a o
para el objeto b?
94 Piensa en Java
Para poder escribir el cdigo en una sintaxis cmoda orientada a objetos, en la que podamos "enviar un mensaje a un obje-
to" , el compilador se encarga de reali zar un cierto trabajo entre bastidores por nosotros. Existe un primer argumento secre-
to pasado al mtodo peel(), y ese argumento es la referencia al objeto que se est manipulando. De este modo, las dos
llamadas a mtodos se convierten en algo parecido a:
Banana.peel(a, 1)
Banana.peel(b, 2) i
Esto se realiza internamente y nosotros no podemos escribir estas expresiones y hacer que el compilador las acepte, pero
este ejemplo nos basta para hacernos una idea de lo que sucede en la prctica.
Suponga que nos encontramos dentro de un mtodo y queremos obtener la referencia al objeto aChlal. Puesto que el compi-
lador pasa esa referencia secretamente, no existe ningn identificador para ella. Sin embargo, y para poder acceder a esa
referencia, el lenguaj e incluye una palabra clave especfica: this. La palabra clave this, que slo se puedc emplear en mto-
dos que no sean de tipo static devuelve la referencia al objeto para el cual ha si do invocado el mtodo. Podemos tratar esta
referencia del mi smo modo que cualquier otra referencia a un objeto. Recuerde que, si est invocando un mtodo de la clase
desde dentro de otro mtodo de esa misma clase, no es necesario utilizar this, si no que simplemente basta con invocar el
mtodo. La referencia this actua l ser utilizada automticamente para el otro mtodo. De este modo, podemos escribir:
jj : initializationj Apricot.java
public class Apricot {
void pick l) { / * ... * /
void pit 11 { pick 11 ; / * .. * / }
// / ,-
Dentro de pite ), podramos decir Ihis.pick( ) pero no hay ninguna necesidad de hacerlo. ' El compilador se encarga de
hacerlo automticamente por nosotros. La palabra clave this slo se usa en aquell os casos especiales en los que es necesa-
rio utilizar explcitamente la referencia al objeto aChla l. Por ejemplo, a menudo se usa en instrucciones return cuando se
quiere devolver la referencia al objeto actual:
jj : initialization/Leaf.java
/ / Uso simple de la palabra clave "this".
public class Leaf {
int i = O
Leaf increment()
i++
return this;
void print ()
System.out.println("i = " + i)
public static void main(String[] args)
Leaf x = new Leaf()
x. increment () . increment () . increment () . print () i
j * Output:
i = 3
* ///,-
Puesto que increment() devuelve la referencia al objeto actual a travs de la palabra clave this, pueden realizarse fcilmen-
te l11ultiples operaciones con un mismo objeto.
La palabra clave this tambin resulta ti l para pasar el objeto actual a otro mtodo:
1 Algunas personas escriben obsesivamente this delante de cada llamada a mtodo O referencia a un campo argumentando que eso hace que el cdigo
sea "mas claro y ms explicito". Mi consejo es que no lo haga. Existe una razn por la que utilizamos los lenguajes de alto nivel, y esa razn es que estos
lenguajes se encargan de hacer buena parte del trabajo por nosotros. Si incluimos la palabra clave this cuando no es necesario, las personas que lean el
cdigo se sentiran confundidas, ya que los dems programas que hayan leido en cualquier parte 110 wili=an las palabra clave Ihis de manera continua.
Los programadores esperan que this slo se use all donde sea necesario. El seguir un estilo de codificacin cohercllIe y simple pemlite ahorrar tiempo
y dinero.
in: tializationj Pass:'ngThis . java
class Person {
p'..lblic void eat (Apple apple) {
Apple peeled = apple. get.Peeled () ;
System. out. . prin':.ln ;
class ?eeler {
static Apple peel \Apple apple l
/1 pelar
return apple i / / Pelada
c:ass P-.pple {
.ll..pple getPeeled () { return Peeler . peel ( this ) i }
publi c class PassingThis {
public static void ma in(String[] args)
new Person {) . eat (new Apple () ) ;
/ * OUtput :
Yummy
* /// :-
5 Inicializacin y limpieza 95
El objeto Apple (manzana) necesita invocar Peeler.peel( ), que es un metodo de utilidad ex temo que lleva a cabo una ope-
racin que. por alguna razn, necesita ser ex tema a Apple (quiz ese mlodo extemo pueda aplicarse a muchas clases dis-
tintas y no queremos repeti r el cdigo). Para que el objeto pueda pasarse a si mi smo al mtodo extcmo. es necesari o emplear
Ihis
Ejercicio 8: (1) Cree ulla clase con dos melodos. Dent ro del primer mtodo invoque al segundo mtodo dos veces: la
primera vez sin util izar this y la segunda utilizando dicha palabra clave. Realice este ejemplo simplemen-
te para ver cmo funciona el mecanismo. no debe utilizar esta forma de invocar a los mtodos en la
prctica.
Invocacin de constructores desde otros constructores
CU:lmh) se escriben varios COll Slructor6 para Ulla existen ocasiones en las que conviene im'oear a un constructor desde
dentro de otro para no tener que duplicar el cdigo. Podemos cfcewar este tipo de ll amadas Uli li zand la pa labrtl cla,'c thi s.
Normalmente. cuando this. es en el sentido de "este objeto" o el "objeto m:tual", y esa palabra cla\-e genera, por
si misma. la referencia al objeto actual. Dentro de UIl COll st rlH:tor. la palabra clave this toma un significado distinto cuando
se la proporciona una li sta dt:' argumentos: realiza una ll amada explcita al const ructor que se corresponda con esa li sta de
argumentos. De este modo, di sponemos de una f0n113 sencilla de in\'ocar a NroS constmctores:
1/ : in':'tializat.ion/E='lower . java.
JI Llamada a conSLructores con "chis"
':'mport static net.mindview.util.?rint.*
public class FlOWe r {
int petalCount : O;
String s = " initial value";
Flower ( int petals ) {
petalCount. = petals;
Plint. ( "Construct.or wl int arg only, petaiCount:
+ petalCount ) i
96 Piensa en Java
Flower (String ss ) {
print ( "Constructor w/ String arg only, s
s :: ss;
Flower (String s, int petals )
this (petals ) ;
" + ss ) ;
//1 this(s); JI NO podemos realizar dos invocaciones!
this.s Si ji Otro uso de "this"
print (" String & int args 11) ;
)
Flower () (
this ( "hi", 47 ) ;
print ( "default constructor (no args) " ) ;
void printPetalCount () {
// ! this (11 ) ; JI NO dentro de un no-constructor!
print ( lIpetalCount = " + petalCount + s = "+ s ) ;
public static void main (String{] args )
Flower x = new Flower () ;
x.printPetalCount {) i
/ * Output:
Constructor w/ int arg only, petalCount= 47
String & int args
default constructor (no args)
petalCount = 47 s = hi
*/ // ,-
El constructor Flower(String s, int petals) muestra que, aunque podemos invocar un constructor uti lizando this, no pode-
mos invocar dos. Adems, la llamada al constructor debe ser lo primero que hagamos, porque de lo contrario obtendremos
un mensaje de error de compi lacin.
Este ejemplo tambin muestra otro modo de utilizacin de this. Puesto que el nombre del argumento s y el nombre del
miembro de datos s son iguales, existe una ambi gedad. Podemos resolverl a utilizando this.s, para dejar claro que estamos
refirindonos al miembro de datos. Esta fanna de utilizacin resulta muy habitual en el cdigo Java y se emplea en nume-
rosos lugares del libro.
En printPctalCount( ) podemos ver que el compilador no nos pennite invocar un constructor desde dentro de cualquier
mtodo que no sea un constructor.
Ejercicio 9: (1) Cree una clase con dos constructores (sobrecargados). Utili zando this, invoque el segundo constructor
desde dentro del primero.
El significado de static
Teniendo en mente el significado de la palabra clave this, podemos comprender mejor qu es lo que implica definir un mto-
do como static. Significa que no existir ningn objeto this para ese mtodo concreto. No se pueden invocar mtodos no
static desde dentro de los mtodos static
2
(aunque la inversa s es posible), y se puede invocar un mtodo static para la pro-
pia clase, sin especificar ningn objeto. De hecho, esa es la principal aplicacin de los mtodos static: es como si estuvira-
mos creando el equi valente de un mtodo global. Sin embargo, los mtodos globales no estn pennitidos en Java, y el incluir
el mtodo static dentro de una clase pennite a los objetos de esa clase acceder a mtodos static y a campos de tipo static.
Algunas personas argumentan que los mtodos estticos no son orientados a objetos, ya que ti enen la semntica de un mto-
do global. Un mtodo esttico no enva un mensaje a un objeto, ya que no existe referencia this. Probablemente se trate de
2 El nico caso en que esto puede hacerse es cuando se pasa al mtodo stalic una referencia a un objeto (el mtodo stalic tambin podra crear su propio
objeto). Entonces, a travcs de la referencia (que ahora ser. en la prctica, Ihis), se pueden invocar mtodos no stlltiC y acccder a campos no static. Pero.
normalmente si quercmos hacer algo como esto, lo mejor es que escribamos un mtodo no static nonnal y comente.
5 Inicializacin y limpieza 97
UIl arg,U1l10;;'1l 10 correcto. y si So;;' al guna lIlili 7<lIldo una gran c;:lIltiJad de me IOdos estticos probablemente Cll-
\'enQa 'lUL' \"lH:I\'a a Illeditar sobre I:J estrategia que esta Sin embargo. los melOdos y valores esticos rL'sultan
prcti(os. y hay ocasiol1L's cn las que de \'crdad son necesarios. por lo que la cuestin de si se trat;J. Lie \'erdadera
orien!:ld<:1 a objetos es mejor dejrsela a los tericos.
Limpieza: finalizacin y depuracin de memoria
SOn conscientes de la importancia la inicializacin. pero a se olvidan de que tambin la lim-
pieDl e5 impOl1antc. Despus de todo. (,quin ncc('sit;1 limpiar un \'alor inl ? Pero. con las bibliotecas. limitarse a olvidarsc
de un obj eto despus de haber acabado de utiliLarlo no siempre resulta seguro. Por supuesto . .1a\ a dispone del Jepurador de
para reclamar la memoria ocupada por aquellos objetos que ya no siendo utilizados. Pero en lo
que "u'Jo: en algunos casos poco suponga que el objeto qUe ha de fi.nido asigna memoria "esp(' cial" sin uti lizar
ne\\"o El depurador de memoria slo :-.abe cmo liberar la memoria asignada cun ne\\". por lo que no sabr cmo liberar la
Ill C!llO/"la "especial" del objeto. Par;1 eS{Qs casos. Java proporciona un met odo denominado finalizf'( ) que se puede definir
para 1:'1 clase. He aqui cmo se supuJ/e que funciona ese mtodo: cuando el depurador de memoria li sto p"ra liberar el
alm;ll.'o:namieJ1lo mili7ado por el objeto. invocar primero tinalize( ) y slo reclamara la memoria del objet o en la siguiente
pasJJa Jo:! dt'purauor de memoria. Por t31110. si decidimos utilizar finalizc{ ). tendremos la posibilidad de reali zar tareas de
limpk'7H impol1anl c:-. ell t!1 mOlll elJ!O en '/ IIt' SI! pmdll:ca la depuracil/ de m! lI/uria.
Esta c:-. una posible fuellle de C[Tor de programacin. porque algullos programadores. especialmente los que provienen Jcl
campo 0('1 C+ ....... pueden confundirse inicialmente y considerar que finalize( ) es el des/me/o/" de C+-'-. que es una funcin
ql1C S il1\ oca sicl/lp/"I! cuando se destruye un objeto. Es importante entender la distincin qlle existe entre C.,...+ y .lava a este
porqUL' en C ......+ ./05 Ob/CID.' sif! lIIpre se deSl /"uyell (en un programa libre de errores). mientras que en .lava. el depu-
!"<ldor de Illemoria no siempre procesa los objelOs. Di cho de otro moJo:
l . PI/cde qlle el depurador de memoria no procese los ohjefus.
2. f.u dcpllracilI de 1IIf..'II/Or;o 110 (:'s eClllil'({clI1il a la desfrlfccin del ohjeto.
Si se estos dos prini.:ipios. se podrn evitar muchos problen1as. Lo que quieren decir es que. si existe alguna aCli-
"dad que deba de ser rea lizada allles de que un objeto deje de ser necesario. deberemos realizar dicha aCl i\' idad nosotros
l11[s11105. Ja\'a no di spo!l(, de mtodos ni de ningn otro concepto similar. por lo qlll.! es necesario crear un
do !lomIaI para !levar a cabo esta tarea de limpieza. Por ejemplo, suponga quo:. en el proceso de creacin de un objeto. ese
objeto se dibuja a si mismo en la pantalla. Si no borramos explcitamente su imagen de la pantalla. puede LJuc esa imagen
nunCH 11cguC" a bonarse. Si incluimos un" cierta fUllci onalidad de borrado dentro de tinali ze( ). entonces si un objeto se ve
som\.' lido al proceso de de memoria y se in"oca linali ze( ) (y recordemos que no existe ningull;1 garanta de que
I."SIO :!IlIceda). se eliminara primero la illwgen de IJ panlJII:l: pero si no incluimos explcitamente esa funcionali chld
dI..' horrado. esa ill1agen
POdl..'lllDS t'1ll'olltrarnos con la situacin de que nunca Ikgll C" a liberarse el espacio de almHI..enamiento dc un objeto, debido
a qu\,.' 1..:' 1 nunca se ac('rca a un punto en ('1 que exista un riesgo de quedarse sin espacio de almacenamiento. Si L'I
programa se compl eta el depurador de mcmoria no ll ega el entrar en accin para eliminar el eSXlcio de almacenamiento
asignado ] tos objew:-,. dicho espacio st.'nl de\' uelto al sistema operati \"() el! /llosa en el momento de salir del programa. Esta
C'aral'lerstC':1 bastante comcniellle. porque el proceso de depuracin de memori a implica un cierto gasto 3diciollal
dI.' I"et:ursos tk procesamiento. y si no se lleva a cabo. cl gasto 110 se produce.
Para qu se utiliza finalize()?
Pen..l si 110 debe ulilizarse finalizl' ( ) ("01110 mtodo de limpieza de propsilO general. ,para qu s ilye este mtodo'.)
Un tt:rcer punto qu(' h.\)' que recordar es:
3. La depuraci lI de l1Iemoriu slo Se' preocllpa de /0 lIIemor;a.
E::. (kcir. la nica razn p<lra la exi stencia del depurador de lllo:mora es recupcrar aquella memoria que el programa ya no
61 utilizalldo. Por lanto. cualquier i.lcthidad asociada 1..'011 la depuracin memoria. y en especial el 1lllOdo finalize( ).
dcbo: tambil-Il encargarse llicamell!e de la memoriJ y su desasignacill .
98 Piensa en Java
(,Significa esto qUe. si el objeto contiene otros objetos. finalize( ) debe Iiber3r explcitamente esos otros En renli-
dnd no: el depurador de memoria se ocupa de libernr toda la mcmoria de objetos. independiL'lltL'mente de cmo estos hayan
sido creados, En resumen. finalize( ) slo es necesario en aquellos casos ..:-speciales en los que ..:-1 objeto pueda asignar espa-
cio d..:- almacenamiento utilizando alguna tcnica distinta de la propia creacin de objetos, Algn lector especialmcl1tt:' aten-
to podra argumentar: pero. si roda .laya ..:-s un objeto. (,cmo pu..:-de llegar a producirse esta siulacin?
Parece que finalize( ) se ha incluido en el lenguaje debido a la posibilidad de que el programador realice alguna acti\ 'illad
de estilo C. asignando memoria mediante algn mecanismo distinto del normalmente cmpleado en Java. Esto puede suce-
der. principalmente. a travs de los mtodos Ilari, 'us. que son una f0I111a de in\'ocar desde .laya cdigo esclito en un lengua-
je distinto de .I ay.:! (los mtodos natiyos se estudian en el Apndice B de la segunda edk'ion electrnica de este libro.
disponible en )\"l1'\\',J\lindllt' H'.l1ct), C y C++ son los nicos lenguajes actualmente soportados por los mtodos nati\'os. pero
como desde ellos se pueden inyocar subprogramas en otros lenguajt!s. en la prctica podremos imeocar cualquier cosa que
queramos, Dentro del cdigo no Java, podra invocarse la familia de funciones malloc( ) de C para asignar esp.:!cio de alma-
cenamiento. y a menos que invoquemos free( ). dicho espacio de almacenamiento no ser liberado. provocando una fuga de
memoria. Por supuesto. free( ) es una funcin de ( y e ++. por lo que sera necesario im'ocarla mediant..:- Ull mtodo nativo
dentro de finalize( ),
Despus de estas explicaciones. el lector probablemente estar pensando que no va a tener que utilizar finalize( ) de forma
demasiado frecuente,3 En efecto. es as: dicho mtodo no es el lugar apropiado para realizar las tareas normales de limpie-
za. Pero. entonces dnde lle\"J a cabo esas tareas n0n11ales de limpieza?
Es necesario efectuar las tareas de limpieza
Para limpiar un objcto. el usuario de ese objeto debe invocar un mtodo de li mpieza en el lugar donde desee que sta se rea-
lice. Esto parece bastante sencillo. pero choctl un poco con el concepto C--r+ de des tl1lclor. En C++. todos los objetos se des-
truyen: o. mejor dicho. todos los objetos dl:'berol1 destruirse, Si el objeto (-1-+ se crea como local (es dec ir. en la pila. lo cual
no resulta posible en Java). la destruccin se produce en la llave de cierre del mbito en que el objeto haya sido creado,
Si el objeto se cre utili zando new (como en Java). el destructor invoca cuando el programador llama al operador C++
delete (que no existe en Java), Si el programador de C++ olvida invocar delete. Illmca se llamar al destntctor y se produ-
cir en la prctica una fuga de memoria (adems de que las otras panes del objeto nunca llegarn a limpiarse). Este tipo de
en'or puede ser muy dit1ci l de localizar y es una de las principales razones para pasar de ( + + a Java,
Por contraste. Java no permite crear objetos locales. sino que siempre es necesario utilizar new. Pero en Java, no existe nin-
gn operador "delete" para liberar el objeto. porque el depurador de memoria se encarga de liberar el espacio de almacena-
miento por nosotros. Por (anta. desdc un punto de vista simplista, podramos decir que debido a la depuracin de memoria
Java no dispone de destntctorcs, Sin embargo. a medida que a\'ancemos en el libro veremos que la presencia de un depura-
dor de memoria no elimina ni la necesidad ni la utilidad de los destructores (y recuerde que no se debe ill\'ocar finalize( )
directamente. por lo que dicho mtodo no constituye una solucin). Si queremos que se realice algn tipo de limpieza dis-
tinta de la propia liberacin dd espacio de almacenamiento. sigile siendo nt'l'csario invocar explcitamente un mtodo apro-
piJdo L' n Ja\i1. que ser el equi\ "alente dd destructor de C++ pero sin la comodidad asociada;) ste,
Re.'uerde que no estn garantizadas ni la depuracin de rllemoria ni la finalizacin, Si la mquina \' jrtllal Java (JVMl no est
prxima J quedarse sin memoria. puede qUe no pierda tiempo recuperando espacio mediante el mecanismo de depuracin
de mcmoria,
La condicin de terminacin
En general. no podemos confiar en que finalize( ) invocado y es necesario crear mtodos de "limpieza" separados c invo-
carlos explcitamente, Por tanto. parece 4ue tinalize( ) slo resulta til para oscuras tareas de limpieza de memoria que la
mayora de los programadores nUllca van a tener que utilizar, Sin embargo. existe un caso interesante de uso de tinalize( )
qut.' IW depende de que dicha funcin sea invocada todas las \-eces, Nos referimos a la verificacin de la condicin dI! tel'-
de un objeto,
-' Joshua Blo('h.::.; toda\ a mas tajanll' en Sl"el'i':H1 "EI 'ilt' (J I "l os tlthllizadores son a menudll y getler<llmenl.c
Ellnli rt' JlrdT.tt Pro,'!.lw11llIing LJII,i;lIlg<' Gllide, p. 20 20U I J.
La tcmllllll acur'iauu por Bill Vellners IIt "It I\ .. en Utl sC'minano qUe 1111parttllWs conjull!al1lC'l1te
5 Inicializacin y limpieza 99
En el momento en que ya no estemos interesados en un objeto (cuando est listo para ser borrado) dicho objeto deber
encontrarse en un estado en el que su memoria debe ser liberada sin riesgo. Por ejemplo, si el objeto representa un archivo
abierto, el programador deber cerrar dicho archivo antes de que el objeto se vea sometido al proceso de depuracin de
memoria. Si alguna parte del objeto no se limpia apropiadamente, tendremos un error en el programa que ser muy dificil
de localizar. Podernos utilizar finaHze( ) para descubrir esta condicin, incluso aunque dicho mtodo no sea siempre invo-
cado. Si una de las finali zaciones nos pennite detectar el error, habremos descubierto el problema, que es lo nico que real-
mente nos importa.
He aqu un ej emplo simple de cmo podra emplearse dicho mtodo:
11 : i nitialization/TerminationCondition. java
II USO de finalize() para detectar un objeto
II que no ha sido limpiado apropiadamente.
class Book {
boolean checkedOut = f a l se;
Book (boolean checkOut ) {
checkedOut = checkOut
void check In ()
checkedOut = false
protected void fina li ze()
if (checkedOut)
System. out .println ( "Error: checked out!l) i
II Normalmente, tambin haremos esto :
II super .f inalize(); II Invocar la versin de la clase base
public class TerminationCondition {
public static void main(String[] args)
Book novel = new Book(true);
II Limpieza apropiada :
novel.checkIn() i
II Falta la referencia, nos olvidamos de limpiar:
new Book(true);
II Forzar la depuracin de memoria y la finalizacin:
System. gc () ;
1* Output:
Error: checked out
* /// ,-
La condi cin de terminacin es que se supone que todos los objetos Book (libro) deben ser devueltos (check in) antes de
que los procese el depurador de memoria, pero en main() hay un error de programacin por el que uno de los libros no es
devuelto. Sin finalize() para verifi car la condicin de tenninacin, este error puede ser dificil de locali zar.
Observe que se utiliza System.gc() para forzar la finalizacin. Pero, incluso aunque no usramos ese mtodo, resulta alta-
mente probable que llegramos a descubrir el objeto Book errneo ejecutando repetidamente el programa (suponiendo que
el programa asigne un espacio de almacenamiento suficiente como para provocar la ejecucin del depurador de memoria).
Por regla general, debemos asumir que la versin de finalize() de la clase base tambin estar llevando a cabo alguna tarea
importante, por lo que convendr invocarla utilizando super. CalDO puede verse en Book.finalize(). En este caso, hemos
desacti vado esa llamada mediante comentarios, porque requiere utilizar los mecani smos de tratamiento de excepciones de
los que an no hemos hablado en detalle.
Ejercicio 10: (2) Cree una clase con un mtodo finalize() que imprima un mensaje. En main(), cree un objeto de esa
clase. Explique el comportamiento del programa.
Ejercicio 11: (4) Modifique el ejercicio anterior de modo que siempre se invoque el mtodo finalize().
10C Piensa en Java
Ejercicio 12: (4) Cree una clase denominada Ta nk (tanque) que pueda ser llenado y vaciado, y cuya condicin de ter-
minacin es que el objeto debe estar vaco en el momento de limpiarlo. Escriba un mtodo finalize() que
\ critique esta condicin de tenninacin. En maine ), compruebe los posibles casos que pueden producir-
se al utilizar los objetos Tank.
Cmo funciona un depurador de memoria
Si su experiencia anterior es con lenguajes de programacin en los que asignar espacio de almacenamiento a los objetos en
el cmulo de memoria resulta muy caro en tnninos de recursos de procesamiento, puede que piense que el mecanismo Java
de asignar todo el espacio de almacenamiento (excepto para las primitivas) en el cmulo de memoria es tambin caro. Sin
embargo, resulta que el mecanismo de depuracin de memoria puede contribuir significativamente a acelerar la velocidad
de creacin de los objetos. Esto puede parecer un poco extrailo a primera vista (el que la liberacin del espacio de almace-
namiento afecte a la velocidad de asignacin de dicho espacio) pero sa es la fOffila en que funcionan algunas mquinas
JVM, lo que implica que la asignacin de espacio de almacenamiento en el cmulo de memoria para los objetos Java puede
ser casi tan rpida como crear espacio de almacenamiento en la pila en otros lenguajes.
Por ejemplo. podemos pensar en el cmulo de memoria de C++ como si fuera una parcela de terreno en la que cada objeto
ocupa su propio lote de espacio. Este terreno puede quedar abandonado y debe ser reutilizado. En algunas JVM el cmulo
de memoria Java es bastante distinto: se parece ms a una cinta rransportadora que se desplaza hacia adelante cada vez que
se asigna un nuevo objeto. Esto quiere decir que la asignacin de espacio de almacenamiento a los objetos es notablemen-
te rpida: simplemente se desplaza hacia adelante el "punt ero del cmulo de memoria" para que apunte a un espacio vaco,
por lo que equivale en la prctica a la asignacin de espacio de almacenamiento en la pila en C++ (por supuesto, existe un
cierto gasto adicional de recursos de procesamiento asociado a las tareas de administracin del espacio, pero esos recursos
son mnimos comparados con los necesarios para localizar espacio de almacenamiento).
Algn lector podra argumentar que el cmulo de memoria no puede considerarse como una cinta transportadora, y que si
lo consideramos de esa manera comenzarn a entrar en accin los mecanismos de paginacin de mcmoria, desplazando
informacin hacia y desde el disco. de modo que puede parecer que disponemos de ms memoria de la que realmente exis-
te. Los mecanismos de paginacin afectan significativamente a la velocidad. Adems, despus de crear un nmero grande
de objetos terminar por agotarse la memoria. El tmco radica en que el depurador de memoria, mientras se encarga de libe-
rar el espacio de almacenamiento que ya no es necesario, compacta tambin todos los objetos en el cmulo de memoria, con
lo que el efecto es que el "puntero del cmulo de memoria" queda situado ms cerca del comienza de esa "cinta transpor-
tadora', ms alejado del punto en el que pueda producirse un fallo de pgina. El depurador de memoria se encarga de reor-
denar la informacin y hace posible utilizar ese modelo de cmulo de memoria infinito de alta vclocidad para asignar el
espacio de almacenamiento.
Para entender el proceso de depuracin de mcmoria cn Java, resulta til analizar cmo funcionan los esquemas de depura-
cin de memoria en otros sistemas. Una tcnica muy simple, pero muy lenta, de depuracin de memoria es la que se deno-
mina recuento de referencias. Esto quiere decir que cada objeto contiene un contador de referencias y que, cada vez que se
asocia una referencia a ese objeto, ese contador de referencias se incrementa. De la misma fonna, cada vez que una referen-
cia se sale de mbito o se la asigna el valor l1ulI, se reduce el contador de referencias. Este mecanismos de gestin del nme-
ro de referencias representa un gasto adicional pequeo, pero constante. que tiene lugar mientras dura la ejecucin del
programa. El depurador de memoria recorre la lista complp.ta de objetos, y donde encuentra uno cuyo contador de referen-
cias sea cero, libera el espacio de almacenamiento correspondiente (sin embargo, los mecani smos basados en el recuento de
referencias suelen liberar los objetos tan pronto como el contador pasa a valer cero). La desventaja es que, si hay una serie
de objetos que se refieren circularmente entre s, podemos encontrarnos con nmeros de referencia distintos de cero a pesar
de que los objetos ya no sean necesarios. La localizacin de esos gmpos auto-referenciales exige al depurador de memoria
realizar un trabajo adicional bastante significativo. Este mecanismo de recuento de referencias se suele utilizar de fanna bas-
tante habitual para explicar uno de los posibles mecanismos de depuracin de memoria, pero no parece que se use en nin-
guna implementacin de mquina NM.
En otros esquemas ms rpidos, la depuracin de memoria no est basada en el recuento del nmero de referencia, en su
lugar, se basa en el concepto de que cualquier objeto que no est muerto debe, en ltimo trmino, ser trazable hasta otra
referencia que est localizada en la pila o en almacenamiento estt ico. Esta cadena debe atravesar varios ni ve les de objetos.
De este modo, si comenzamos en la pila y en el rea de almacenamiento esttico y vamos recorriendo todas las referencias,
podremos localizar todos los objetos vivos. Para cada referencia que encontremos, debemos continuar con el proceso de
5 Inicializacin y limpieza 101
traza, entrando en el objeto al que apunta la referencia y siguiendo a continuacin todas las referencias incluidas en ese
too entrando en los objetos a los que esas referencias apuntan, etc., hasta recorrer todo el rbol que se origina en la
cia sinlada en la pila o en almacenamiento esttico. Cada objeto a travs del cual pasemos seguir estando vivo. Observe
que no se presenta el problema de los gmpos auto-referenciales: los objetos de esos grupos no sern recorridos durante este
proceso de construccin del rbol, por lo que se puede deducir automticamente que hay que depurarlos.
En la tcnica que acabamos de describir, la NM utiliza un esquema de depuracin de memoria adaplal;vo, en el que lo que
hace con los objetos vivos que localice depender de la variante del esquema que se est utilizando actualmente. Una de
esas variantes es parar y copiar, lo que quiere decir que (por razones que luego comentaremos) se detiene primero el
grama (es decir, no se trata de un esquema de depuracin de memoria que funcione en segundo plano). Entonces cada obje-
to vivo se copia desde un punto del cmulo de memoria a otro, dejando detrs todos los objetos muertos. Adems, a medida
que se copien los objetos en la nueva zona del cmulo de memoria, se los empaqueta de modo que ocupen un espacio de
almacenamiento mnimo compactando as el rea ocupada (y pennitiendo que se asigne nuevo espacio de almacenamiento
inmediatamente a continuacin del rea recin descrita, como antes hemos comentado).
Por supuesto, cuando se desplaza un objeto de un sitio a otro, es preciso modificar todas las referencias que apuntan al obje-
to. Las referencias que apunten al objeto desde el cmulo de memoria o el rea de almacenamiento esttico pueden modifi-
carse directamente, pero puede que haya otras referencias apuntando a este objeto que sean encontradas posterionnente,
durante el proceso de construccin del rbol. Estas referencias se irn modificando a medida que sean encontradas
ne, por ejemplo, que se utilizara lIna tabla para establecer la correspondencia entre las antiguas direcciones y las nuevas).
Hay dos problemas que hacen que estos denominados "depuradores copiadores" sean poco eficientes. El primero es la nece-
si dad de disponer de dos reas de cmulo de memoria, para poder mover las secciones de memoria entre una y otra, lo que
en la prctica significa que hace falta el doble de memoria de la necesaria. Algunas mquinas NM resuelven este proble-
ma asignando el cmulo de memoria de segmento en segmento, segn sea necesario, y simplemente copiando de un seg-
mento a otro.
El segundo problema es el propio proceso de copia. Una vez que el programa se estabilice, despus de iniciada la ejecucin,
puede que no genere ningn objeto muerto o que genere muy pocos. A pesar de eso, el depurador copiador seguir
do toda la memoria de un sitio a otro, lo que constituye un desperdicio de recursos. Para evitar esto, algunas mquinas JVM
detectan que no se estn generando nuevos objetos muertos y conmutan a un esquema di stinto (sta es la parte "adaptati-
va''). Este otro esquema se denomina marcar y eliminar, y es el que utilizaban de manera continua las anteriores versiones
de la NM de Sun. Para uso general , esta tcnica de marcar y eliminar es demasiado lenta, pero resulta, sin embargo, muy
rpida cuando sabemos de antemano que no se estn generando objetos muertos.
La tcnica de marcar y eliminar sigue la misma lgica de comenzar a partir de la pila y del almacenamiento esttico y tra-
zar todas las referencias para encontrar los objetos vivos. Sin embargo, cada vez que se encuentra un objeto vivo, ste se
marca activando un indicador contenido en el mismo, pero sin aplicarle ningn mecanismos de depuracin. Slo cuando el
proceso de marcado ha terminado se produce la limpieza. Durante esa fase, se libera la memoria asignada a los objetos muer-
tos. Sin embargo, hay que observar que no se produce ningn proceso de copia, por lo que si el depurador de memoria deci-
de compactar un cmulo de memoria fragmentado. tendr que hacerlo moviendo los objetos de un sitio a otro.
El concepto de "parar y copiar" hace referencia a la idea de que este tipo de depuracin de memoria 110 se hace en segundo
plano; en lugar de ello, se detiene el programa mientras tiene lugar la depuracin de memoria. En la documentacin tcni-
ca de Sun podr encontrar muchas referencias al mecanismo de depuracin de memoria donde se dice que se trata de un
proceso de segundo plano de baja prioridad, pero la realidad es que la depuracin de memoria no estaba implcmcntada de
esa fonna en las primeras mquinas NM de Sun. En lugar de ello, el depurador de memoria de Sun detena el programa
cuando detectaba que haba poca memoria libre. La tcnica de marcar y limpiar tambin requiere que se detenga el pro-
grama.
Como hemos mencionado anteriormente, en la mquina NM descrita aqu. la memoria se asigna en bloques de gran tama-
o. Si asignamos un objeto grande, ste obtendr su propio bloque. La tcnica de detencin y copiado estricta requiere que
se copien todos los objetos vivos desde el cmulo de memoria de origen hasta un nuevo cmulo de memoria antes de poder
liberar el primero, lo que implica una gran cantidad de memoria. Utilizando bloques, el mecanismo de depuracin de memo-
ria puede nonnalmente copiar los objetos a los bloques muertos a medida que los va depurando. Cada bloque dispone de un
contador de generacin para ver si est vivo. En el caso nonnal , slo se compactan los bloques creados desde la ltima pasa-
da de depuracin de memoria; para todos los dems bloques se incrementar el contador de gcneracin si han sido
ciados desde algn sitio. Esto pennite gestionar el caso nOffi1al en el que se di spone de un gran nmero de objetos temporales
102 Piensa en Java
de corta duracin. Peri dicamente, se hace una limpi eza completa. en )a que los objetos de gran tamao seguirn sin ser
copiados (simplemente se incrementar su contador de generacin) y los bloques que comcngan objclOs pequeos se copia-
rn y compactarn. La maquina JVM monitori za la eficiencia del depurador de memoria y, si este mecanismo se convierte
en una prdida de tiempo porque todos los objetos son de larga duracin, conmuta al mecanismo de marcado y limpieza. De
f0n113 simi lar, la JVM controla hasta qu punto es efectiva la tcnica de marcado y limpieza, y si el cumulo de memoria
comienza a estar fragmentado, conmuta almccanismo de detencin y copiado. Aqu es donde entra en accin la parte "adap-
tativa" delmccanismo, al que podramos describir de manera rimbombante como: "mecanismo adaptativo generacional de
detencin-copiado y marcado-limpieza".
Existen varias posibles optimizaciones de la velocidad de una mquina NM. Una especialmente importante afecta a la ope-
racin del cargador y es lo que se denomina compilador jusl-in-lime (liT). Un compilador JlT convierte parcial o totalmen-
te un programa a cdigo mquina nativo. de modo que dicho cdigo no necesite ser interpretado por la JVM, con lo que se
ejecutar mucho ms rpido. Cuando debe cargarse una clase (normalmente. la primera vez que queramos crear un objeto
en esa clase) se localiza el archivo .class y se carga en memoria el cdigo intermedio correspondiente a dicha clase. En este
punto, una posible tcnica consiste en compilar simplemente todo el cdigo, para generar un cdigo mquina. pero esto tiene
dos desventajas: necesita algo ms de tiempo, lo cual (si tenemos en cuenta toda la vida del programa) puede representar
una gran cantidad de recursos adicionales; e incrementa el tamao del ejecutable (el cdigo intennedio es bastante ms COI11-
pacto que el cdigo JlT expandido), y esto puede provocar la aparicin del fenmeno de paginacin, lo que ralentiza enor-
memente los programas. Otra tcnica alternativa es la evalllacin lenta, que consiste en que el cdigo intennedio no se
compila para generar cdigo mquina hasta el momento necesario. De este modo, puede que nunca se llegue a compi lar el
cdigo que nunca llegue a ejecutarse. Las tecnologas HotSpot de Java en los kits de desarrollo JDK recientes adoptan una
tcnica simi lar, optimizando de manera incremental un fragmento de cdigo cada vez que se ejecuta, por lo que cuanto ms
veces se ejecut e, ms rpido lo har.
Inicializacin de miembros
Java adopta medidas especiales para garantizar que las variables se inicialicen adecuadamente antes de usarlas. En el caso
de las variables locales de un mtodo, esta garanta se presenta en la forma de errores en tiempo de compilacin. Por tamo,
si escribimos:
void f ( ) (
int i
i++ II Error -- i no inicializada
obtendremos un mensaje de error que dice que i puede no haber sido no inicializada. Por supuesto, el compi lador podra
haber dado a i un valor predetenninado, pero el hecho de que exista una variable local no inicializada es un error del pro-
gramador, y el valor predetenninado estara encubriendo ese error. Forzando al programador a proporcionar un valor de ini-
ciali zacin. es ms probable que se detecte el error.
Sin embargo, si hay un campo de tipo primitivo en una clase, las cosas son algo distintas. Como hemos visto en el Captulo
2, Todo es 1111 objeto. se garantiza que cada campo primitivo de una clase contendr un valor inicial. He aqu un programa
que pennite verificar este hecho y mostrar los valores:
11 : initialization/ lnitialValues.java
II Muestra los valores iniciales predeterminados.
import static net.mindview.util.Print.*
public class InitialValues {
boolean t;
char c;
byte b;
short S;
int i;
long 1;
float f
double d;
InitialValues reference;
void printlnitialValues () {
print ( "Data type Initial value") i
print ("boolean
print ("char
print ( "byte
print (" short
print ( " int
print{"long
print{"float
print ( lIdouble
print(!lreference
" + tl;
[" + e + "] 01 ) ;
+ b ),
+ s);
+ i);
+ 1),
+ f ),
+ d);
+ reference);
public static void main(String[] args) {
InitialValues iv = new InitialValues();
iv.printlnitialValues() i
/ * Tambin podramos escribir:
new InitialValues () .printlnitialValues ();
, /
1*
Output:
Data type Initial value
boolean false
char
byte O
short O
int O
long O
float 0.0
double 0.0
reference null
*/11 ,-
5 Inicializacin y limpieza 103
Puede ver que, an cuando no se han especificado los valores, los campos se inicializan automticameme (el valor corres-
pondiente a char es cero, lo que se imprime como un espacio). De modo que, al menos, no existe ningn riesgo de llegar a
trabajar con variables no inicializadas.
Cuando definimos una referencia a objeto dentro de una clase sin inicializarse como nuevo objeto, dicha referencia se ini-
ciali za con el valor especial null.
Especificacin de la inicializacin
Qu sucede si queremos dar un valor inicial a una variable? Una forma directa de hacerlo consiste, simplement e, en asig-
nar el va lor en el punto en que deftnamos la variable dentro de la clase (observe que no se puede hacer esto en C++, aun-
que los programadores novatos de C++ siempre lo intentan). En el siguiente fragmento de cdigo, se modifican las
definiciones de los campos de la clase lniti alValues para proporcionar los correspondientes valores iniciales:
j/ : initialization/lnitialVal ues2 . java
jj Definicin de valores iniciales explcitos.
public class InitialValues2
boolean bool true;
char ch = IXI;
byte b = 47,
short s = Oxff
int i = 999
long lng = 1;
float f = 3.14f
doub1e d = 3.14159,
11/, -
104 Piensa en Java
Tambin podemos inicializar objetos no primitivos de la misma fomla. Si Depth es una clase, podemos crear una variable
e inicializarla como sigue:
1/: initialization/Measurement.java
class Depth {}
public class Measurement
Depth d = new Depthl);
/ / ...
/// ,-
Si no hubiramos dado a d un valor inicial y tratramos de usarlo de todos modos, obtendramos un error de tiempo de eje-
cucin denominado excepcin (hablaremos de este tema en el Captulo 12, Tratamiento de errores mediante excepciones).
Podemos incluso llamar a un mtodo para proporcionar un valor de inicializacin:
/1 : initialization/Methodlnit.java
public class Methodlnit {
int i = f 1) ;
int f () { return 11; }
/// ,-
Por supuesto, este mtodo puede tener argumentos, pero dichos argumentos no pueden ser otros miembros de la clase que
todava no hayan sido inicializados. Por tanto, podemos hacer esto:
jj : initialization/MethodInit2.java
public class MethodInit2 {
int i = f () ;
int j = 9 I i) ;
int f () { return 11; }
int g(int n) { return n * 10; }
/// ,-
Pero no esto:
JI: initialization/MethodInit3.java
public class MethodInit3 {
JI! int j = g ( i ) i JI Referencia anticipada ilegal
int i = f () ;
int f () { return 11;
int g (i nt n) { return n * 10; }
/// ,-
ste es uno de los ejemplos en los que el compilador se queja, como es lgico, acerca de las referencias anticipadas, ya que
este caso tiene que ver con el orden de inicializacin ms que con la forma en que se compila el programa.
Esta tcnica de inicializacin resulta bastante simple y directa. Tiene la limitacin de que todos los objetos de tipo
I niti alValues contendrn el mismo valor de inicializacin. En ocasiones, esto es, exactamente, lo que queremos, pero en
otros casos hace falla ms flexibi lidad.
Inicializacin mediante constructores
Podemos emplear el constructor para realizar la inicializacin, y esto nos proporciona una mayor flexibi lidad a la hora de
programar, porque podemos invocar mtodos y realizar acciones en tiempo de ejecucin para determinar los valores inicia-
les. Sin embargo, es preciso tener presente una cosa: esto no excluye la inicializacin automtica que tiene lugar antes de
entrar en el constructor. As que, si escribimos por ejemplo:
11 : initialization/Counter . java
public class Counter
int i;
Counter () { i '"' 7;
II .. .
111 >
5 Inicializacin y limpieza 105
entonces i se inicializar primero con el valor O, y luego con el valor 7. Esto es cierto para todos los tipos primitivos y tam-
bin para las referencias a objetos, incluyendo aquellos a los que se inicialice de manera explcita en el punto en el que se
los defina. Por esta razn, el compilador no trata de obligamos a inicializar los elementos dentro del constructor en ningn
sitio concreto o antes de utilizarlos: la inicializacin ya est garantizada.
Orden de inicializacin
Dentro de una clase, el orden de inicializacin se detennina mediante el orden en que se definen las variables en la clase.
Las defllliciones de variables pueden estar dispersas a travs de y entre las definiciones de mtodos, pero las variables se
inicializan antes de que se pueda invocar cualquier mtodo, incluso el constructor. Por ejemplo:
11 : initialization/OrderOfInitialization.java
II Ilustra el orden de inicializacin.
import static net.mindview.util.Print.*;
II Cuando se invoca el constructor para crear un
II objeto Window, aparecer el mensaje:
class Window {
Window(int marker) { print("Window(" + marker + tI ) " ) ; }
class House
Window wl
House () {
new Window(l); II Antes del constructor
II Mostrar que estamos en el constructor:
print ("House () " ) ;
w3 = new Window(33); II Reinicializar w3
Window w2 = new Window(2); II Despus del constructor
void f () { print ( " f () "); )
Window w3 = new Window(3) i II Al final
public class OrderOfInitialization
public static void main(String[] args)
House h = new House() i
h.f( ) i II Muestra que la construccin ha finalizado
1* Output:
Window(l)
Window (2)
Window(3)
House ()
Window(33)
f ()
* //1 ,-
En l-louse, las definiciones de los objetos Window han sido dispersadas intencionadamente, para demostrar que todos ellos
se inicializan antes de entrar en el constructor o de que suceda cualquier otra cosa. Adems. \\'3 se reinicializa dentro del
constructor.
Examinando la salida, podemos ver que la referencia a w3 se inicializa dos veces. Una vez antes y otra durante la llamada
al constructor (el primer objeto ser eliminado, por lo que podr ser procesado por el depurador de memoria ms adelante).
Puede que esto no le parezca eficiente a primera vista, pero garantiza una inicializacin adecuada: qu sucedera si se defi-
niera un constructor sobrecargado que no inicializara w3 y no hubiera una 1icializacin "predeterminada" para w3 en su
definicin?
106 Piensa en Java
Inicializacin de datos estticos
Slo existe una nica rea de almacenamiento para un dato de tipo sta tic, independientemente del nmero de objetos que
se creen. No se puede aplicar la palabra clave statie a las variables locales, as que slo se aplica a los campos. Si un campo
es una primiti va de tipo static y no se inicializa, obtendr el valor inicial estndar correspondiente a su tipo. Si se trata de
una referencia a un objeto, el valor predeterminado de inicializacin ser null .
Si desea colocar la iniciali zacin en el punto de la defmicin, ser similar al caso de las variables no estticas.
Para ver cundo se iniciali za el almacenamiento de tipo stane, ha aqu un ejemplo:
ji : initialization/Staticlnitialization.java
// Especificacin de valores iniciales en una definicin de clase.
import static net.mindview.util.Print.*;
class Bowl {
Bowl (int marker)
print {"Bowl (" + marker + " )") ;
void fl (int marker) {
print("fl(" + marker + ") " ) ;
class Table (
static Bowl bowll = new Bowl (l) ;
Table () {
print{"Table{) " ) ;
bow12.fl(l) ;
void f2(int marker)
print{"f2(" + marker + " ) " );
static Bowl bow12 = new Bowl(2);
class Cupboard {
Bowl bow13 = new Bowl(3);
static Bowl bow14 = new Bowl (4) ;
Cupboard () {
print (11 Cupboard () " ) i
bow14.fl(2) ;
void f3(int marker)
print{"f3 (u + marker + ") U);
sta tic Bowl bow15 = new Bowl(5);
public class Staticlnitialization {
public static void main(String[] args)
print("Creating new Cupboard() in main
u
);
new Cupboard () ;
print ( " Creating new Cupboard () in main " ) ;
new Cupboard () ;
table. f2 (1) ;
cupboard. f3 (1) ;
static Table table = new Table();
static Cupboard cupboard = new Cupboard ();
) / * Output,
Bowl(l)
Bowl (2)
Table ()
f1 (1)
Bowl(4)
Bowl(S)
Bowl(3)
Cupboard ()
f1 (2)
Creat ing new Cupboard() in main
Bowl(3)
Cupboard ()
f1 (2)
creating new Cupboard() in main
Bowl(3)
Cupboard ()
f1 (2)
f2 (1)
f3 ( 1 )
* /// , -
5 Inicializacin y limpieza 107
80\\'1 pennite visuali zar la creacin de una clase, mientras que Table y Cupboard tienen miembros de tipo static de 80\\'1
dispersos por sus correspondientes definiciones de clase. Observe que Cupboard crea un objeto Bowl bowl3 no estti co
antes de las definiciones de tipo static.
Exami nando la salida, podemos ver que la inici alizacin de static slo ti ene lugar en caso necesario. Si no se crea un obje-
to Table y nunca se hace referencia a Table.bowll o Table.bowI2, los objetos Bowl estticos bowll y bowl2 nunca se
crearn. Slo se ini cializarn cuando se cree el primer objeto Table (cuando tenga lugar el primer acceso stati e). Despus
de eso. los objetos static no se reinicializan.
El orden de iniciali zacin es el siguiente: primero se inicializan los objetos estticos, si es que no han sido ya ini cializados
con una previa creacin de objeto, y luego se inicializan los objetos no estticos. Podemos ver que esto es as exami nando
la salida del programa. Para examinar main() (un mtodo static), debe cargarse la clase Static\nitialization, despus de lo
cual se inicializan sus campos estticos table y cupboard, lo que hace que esas clases se carguen y, como ambas conti enen
objetos Bowl estticos, eso bace que se cargue la clase Bowl . Por tanto, todas las clases de este programa concreto se car-
gan antes de que d comienzo main(). ste no es el caso usual , porque en los programas tpicos no tendremos todo vincu-
lado entre s a travs de valores estticos, como sucede en este ejemplo.
Para resumir el proceso de creacin de un objeto, considere una clase Dog:
1. Aunque no utili ce explcitamente la palabra clave static, el constructor es, en la prctica, un mtodo sta tic. Por
tanto, la primera vez que se crea un objeto de tipo Dog, o la primera vez que se accede a un mtodo esttico o a
un campo estti co de la clase Dog, el intrprete de Java debe localizar Dog.class, para lo cual analiza la ruta de
clases que en ese momento haya definido (c1asspath).
2. A medida que se carga Dog.c1ass (creando un objeto Class, acerca del cual bablaremos posteriormente) se ejecu-
tan todos sus inicializadores de tipo static. De este modo, la inicializacin de tipo static slo tiene lugar una vez,
cuando se carga por primera vez el objeto Class.
3. Cuando se crea un nuevo objeto con new Dog( ), el proceso de construccin del objeto Dog asigna primero el
suficiente espacio de almacenamiento para el objeto Dog en el cmulo de memoria.
4. Este espacio de almacenamiento se rellena con ceros, lo que asigna automticamente sus valores predetermi na-
dos a todas las primiti vas del objeto Dog (cero a los nmeros y el equivalente para booloan y cbar); asimismo.
este proceso hace que las referencias queden con el valor null .
5. Se ejecutan las iniciali zaciones especificadas en el lugar en el que se definan los campos.
6. Se ejecutan los constructores. Como podremos ver en el Captulo 7, Reutilizacin de las clases, esto puede impli -
car un gran nmero de actividades, especialmente cuando estn impli cados los mecanismos de berencia.
108 Piensa en Java
Inicializacin static explcita
Java pemlile agrupar Olras inicializaciones estticas dentro de una "clusul a" sta tic especial (en ocasiones denominada blo-
que esttico) en una clase. El aspecto de esta clusula es el siguiente:
// : initialization/ Spoon. j ava
public class Spoon {
static int i;
sta tic {
i :::: 47;
}
111 ,-
Parece ser un mtodo. pero se trata slo de la palabra clave sta tic seguida de un bloque de cdigo, Este cdigo, al igual
que otras inicializaciones estticas slo se ejecuta una vez: la primera vez que se crea un objclO de esa clase o la primera
vez que se accede a un mjembro de tipo static de esa clase (incluso aunque nUllca se cree un objeto de dicha clase). Por
ejemplo:
/1 : initialization/ ExplicitStatic . java
/ / Inicializacin static explcita con la clusula "static".
import static net.mindview.util.Print.*
class Cup {
Cup (int marker )
print("Cup ( 1I + marker + 11 ) " ) ;
void f (int marker) {
print ( "f ( 1I + marker + 11) " ) ;
class Cups {
static Cup cupl
static Cup cup2
static {
cupl
cup2
new
new
Cup ( l) ;
Cup ( 2 ) ;
Cups()
print ( IICups () n);
public class ExplicitStatic {
public static void main(String [] args) {
print {"Inside main() " ) ;
Cups , cupl.f(99) ; II ( 1 )
}
II static cups cupsl
II static cups cups2
1* Output:
Inside main ()
Cup ( l)
Cup(2)
f (99)
*111,-
new Cups ( )
new Cups () ;
II ( 2 )
II ( 2 )
Los iniciali zadores static para Cups se ejecutan cuando tiene lugar el acceso del objeto esttico cup] en la lnea marcada
con (1), o si se desactiva mediante un comentario la lnea (1) y se quitan los comentarios que desactivan las lneas marca-
das (2). Si se desactivan mediante comentarios tanto (1) como (2), la iniciali zacin sta tic de Cups nunca tiene lugar. como
5 Inicializacin y limpieza 109
puede verse a la salida. Asimismo, da igual si se el iminan las marcas de comentario que estn desactivando a una y otra de
las lineas marcadas (2) o si se eliminan las marcas de ambas lneas; la inicializacin esttica tiene lugar una sola vez.
Ej erci c io 13: (l) Verifique las afinnaciones contenidas en el prrafo anterior.
Ejercic io 14: ( 1) Cree una clase con un campo esttico String que sea inicializado en el punto de definicin, y otro
campo que se inicialice mediante el bloque sta tic. Aada un mtodo st atic que imprima ambos campos y
demuestre que ambos se inicializan antes de usarlos.
Inicializacin de instancias no estticas
Java proporciona una si ntaxis simi lar, denominada inicializacin de instancia, para inicializar las variables estticas de cada
objeto. He aqu un ejemplo:
11 : initialization/ Mugs.java
II "Inicializacin de instancia" en Java.
import static net.mindview.util.Print.*;
cl ass Mug {
Mug ( int marker )
print {IIMug(1I + marker + 11 ) 11 ) ;
void f ( int marker ) {
print (11 f ( " + marker + " ) 11 ) ;
public class Mugs {
Mug mug1;
Mug mug2;
(
mug1 = new Mug(l )
mug2 = new Mug ( 2 )
print (lImug1 & mug2 initialized" )
Mugs ()
print("Mugs () !I ) ;
Mugs (int i )
print ( IIMugs (int ) 11 ) ;
publi c s t a t i c voi d main (String [ ] args l {
print ( " Inside main () It ) ;
new Mugs () ;
print ( IInew Mugs () c ompleted" ) i
new Mugs (1 )
print ( IInew Mugs (1 ) c ompleted" )
1* Output:
Inside main ( )
Mug(l )
Mug(2)
mug1 & mug2 initialized
Mugs ()
new Mugs ( ) completed
Mug ( l)
Mug(2 )
mug1 & mug2 initialized
Mugs ( int )
new Mugs ( l ) completed
* // /,-
110 Piensa en Java
Podemos ver que la clusula de iniciali zacin de instancia:
mugl : new Mug {l ) ;
mug2 = new Mug(2 ) ;
print ( "mugl & mug2 initialized" ) ;
parece exactamente como la clusula de ini ciali zacin esttica, salvo porque falta la palabra clave statie. Esta sintaxi s es
necesaria para soportar la inicializacin de clases internas annimas (vase el Captulo 10, Clases illlernas), pero tambin
nos permite garantizar que ciertas operaciones tendrn lugar independientemente de qu constmctor explcito se invoque.
Examinando la salida, podemos ver que la clusula de iniciali zacin de instancia se ejecuta antes de los dos constructores.
Ejercici o 15: ( 1) Cree una clase con un campo String que se inicialice mediante una clusula de inicializacin de ins-
tancia.
Inicializacin de matrices
Una matri z es, simplemente, una secuencia de objetos o primitivas que son todos del mismo tipo y que se empaquetan jun-
tos, utilizando un nico nombre identificador. Las matrices se definen y usan mediante el operador de indexacin I }. Para
definir una referencia de una matri z, basta con incluir unos corchetes vacos detrs del nombre del tipo:
int[] al;
Tambin puede colocar los corchetes despus del identificador para obtener exactamente el mi smo resultado:
int al [] ;
Esto concuerda con las expectati vas de los programadores de e y C++. Sin embargo, el primero de los dos estilos es una
sintaxis ms adecuada, ya que comunica mejor que el tipo que estamos defmiendo es una "matriz de variables de tipo int ".
Este estilo es el que emplearemos en el libro.
El compilador no pernlite especificar el tamao de la matriz. Esto nos retrotrae al problema de las "referencias" anterior-
mente comentado. Todo lo que tenemos en este punto es una referencia a una matriz (habiendo asignado el suficiente espa-
cio de almacenamiento para esa referencia), sin que se haya asignado ningn espacio para el propio objeto matriz. Para crear
espacio de almacenamiento para la matriz, es necesario escribir una expresin de inicializacin. Para las matrices, la inicia-
li zacin puede hacerse en cualquier lugar del cdigo, pero tambin podemos utilizar una clase especial de expresin de ini-
ciali zacin que slo puede emplearse en el punto donde se cree la matriz. Esta inicializacin especial es un conjunto de
valores encerrados entre llaves. En este caso, el compilador se ocupa de la asignacin de espacio (el equivalente de utilizar
new) Por ejemplo:
int 1] al : ( 1, 2, 3, 4, 5 );
Pero entonces, por qu bamos a definir una referencia a una matriz sin definir la propia matri z?
int[l a2;
Bueno, la razn para definir una referencia sin definir la matri z asociada es que en Java es posible asignar una matri z a otra,
por lo que podramos escribir:
a2 = al;
Lo que estamos haciendo con esto es, en realidad, copi ar una referencia, como se ilustra a continuacin:
JJ : initializationJArraysOfPrimitives .java
import static net.mindview. ut il.Print.*
public class ArraysOfPrimitives {
public static void main (St ring( ] argsl
int [] al : ( 1, 2, 3, 4, 5 );
int [] a2;
a2 = al i
f or(int i O; i e a2.1ength; i++l
a2[] = a2[] + 1;
for {int i = O; i < al.length; i++)
print (n al [" + i + "1 = " + al [i] ) i
} / , Output,
al [O] 2
al [1] 3
al [2] 4
al [3] 5
al [4] 6
, /// ,-
5 Inicializacin y limpieza 111
Como puede ver, a al se le da un valor de inicializacin, pero a a2 no; a a2 se le asigna posterionnente un valor que en este
caso es la referencia a otra matriz. Puesto que a2 y a 1 apuntan ambas a la misma matriz, los cambios que se realicen a tra-
vs de a2 podrn verse en al .
Todas las matrices tienen un miembro intrnseco (independientemente de si son matrices de objetos o matrices de primiti-
vas) que puede consultarse (aunque no modificarse) para determinar cuntos miembros hay en la matri z. Este miembro es
length. Puesto que las matrices en Java, al igual que en C y C++, comienzan a contar a partir del elemento cero, el elemen-
to mximo que se puede indexar es length - 1. Si nos salimos de los lmites, e y C++ lo aceptarn en silencio y penniti-
rn que bagamos lo que queramos en la memoria, lo cual es el origen de muchos errores graves. Sin embargo, Java nos
protege de tales problemas provocando un error de tiempo de ejecucin (una excepcin) si nos salimos de los Imites.
5
Qu sucede si no sabemos cuntos elementos vamos a necesitar en la matriz en el momento de escribir el programa?
Simplemente, bastar con utilizar new para crear los elementos de la matriz. Aqu, new funciona incluso aunque se est
creando una matriz de primitivas (sin embargo, new no pennite crear una primitiva simple que no fonne parte de una
matri z):
11 : initiali za ti on/ArrayNew. java
11 Creacin de matrices con new.
import java.util. *;
import static net.mindview.util.Print.*;
public class ArrayNew {
public static void main{String[] args)
int [] a;
Random rand = new Random(47);
a = new int [rand.nextlnt {20)] ;
prin t ( n length of a = 11 + a . length) ;
print(Arrays.toString(a}} i
/ * Output :
length of a = 18
[O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O, O]
, ///,-
El tamao de la matriz se selecciona aleatoriamente utilizando el mtodo Random.nextlnt(), que genera un valor entre cero
y el que se le pase como argumento. Debido a la aleatoriedad, est claro que la creacin de la matri z ti ene lugar en tiempo
de ejecucin. Adems, como la salida del programa muestra que los elementos de matriz de tipos primitivos se inicializan
automticamente con valores "vacos" (para las variables numricas y char, se inicializan con cero mientras que para varia-
bles boolean se inicializan con false).
El mtodo Arrays.toString( ), que fonna parte de la biblioteca estndar java.util, genera una versin imprimible de una
matriz unidimensional.
5 Por supuesto, comprobar cada acceso de una matriz cuesta tiempo y cdigo, y no hay manera de desactivar esas comprobaciones, lo que quiere decir que
los accesos a matrices pueden ser una fuente de ineficiencia en los programas, si se producen en alguna seccin critica. En aras de la seguridad en ltemct
y de la productividad de los programadores, los diseadores en Java pensaron que resultaba convenienle pagar este precio para evitar los errores asocia-
dos con las matrices. Aunque el programador pueda sentirse lenlado de escribir cdigo para tTal ar de hacer que Jos accesos a las matrices sean ms eficien-
tes, esto es una prdida de ti empo, porque las optimizaciones automticas en tiempo de compilacin y en tiempo de ejecucin se encargan de acelerar los
accesos a las matrices.
112 Piensa en Java
Por supuesto, en este caso la matri z tambin poda haber sido definida e inicializada en la misma instruccin:
int[] a = new int[rand.nextlnt(20}];
sta es la forma preferible de hacerlo. siempre que se pueda.
Si se crea una matriz que no es de tipo primitivo, lo que se crea es una matri z de referencias. Considere el tipo envoltorio
Integer, que es una clase y no una primiti va:
JI : initialization/ArrayClassObj.java
JI Creacin de una matriz de objetos no primitivos.
import java.util.*;
import static net . mindview.util.Print .*
public class ArrayClassObj {
public static void main(String(] args )
Random rand = new Random(47);
Integer[] a = new Integer[rand.next lnt ( 20 }) i
print ( " length of a = " + a .length) ;
for(inc i = O; i < a . length i++}
a[i] = rand.nextlnt(500) i II Conversin automtica
print(Arrays . toString(al 1;
1* Output: (Sample )
length of a = 18
[55,193,361 ,461,429,368,200,22,207,288,128,51,89, 309, 278, 498, 361, 20]
*/// ,-
Aqu, incluso despus de invocar new para crear la matriz:
Integer[] a = new Integer(rand.nextInt(201] i
es slo una matriz de referencias y la ini cializacin no se completa basta que se inicialice la propia referencia creando un
nuevo objeto Integer (mediante el mecanismo de conversin automtica, en este caso):
a[i] = rand . nextInt(500) i
Sin embargo, si nos olvidamos de crear un objeto, obtendremos una excepcin en ti empo de ejecucin cuando tratemos de
utilizar esa posicin vaca de la matriz.
Tambi n es posible inicializar matrices de objetos mediante una lista encerrada entre llaves. He aqu dos formas de hacerlo:
11 : initialization/ArrayInit.java
II Inicializacin de la matriz.
import java.util. *;
public class ArrayInit {
public static void main(String[] args) {
Integer [] a = (
new Integer (1) ,
new Integer(2) ,
3, II Conversin automtica
} ;
Integer[] b = new Integer[] {
new Integer (1) ,
new Integer (2) ,
3, II Conversin automtica
};
System.out.println(Arrays.toString(a i
System.out.println(Arrays . toString(b ;
1* Output:
11, 2, 3]
[1, 2, 3]
*///,-
5 Inicializacin y limpieza 113
En ambos casos, la coma final de la li sta de inicializadores es opcional (esta caracterstica pennite un manteni miento ms
fcil de las li stas de gran tamao).
Aunque la primera forma es til , es ms limitada, porque slo puede emplearse en el punto donde se deflne la matri z.
podemos uti lizar las fonnas segunda y tercera en cualqui er lugar, incluso dentro de una ll amada a un mtodo. Por ejemplo,
podramos crear una matriz de objetos String para pasarl a a otro mtodo main(), con el fin de proporcionar argumentos de
lnea de comandos altemativos a ese mtodo main() :
1/ : initialization/DynamicArray.java
1/ Inicializacin de la matriz .
public class DynamicArray {
public static void main (String [] args ) {
Other .main (new String[] { "fiddle", "de", Itdum" }) i
class Other {
public static void main (String [) args) {
for (String s : args)
System.out.print(s + " ") i
1* Output:
fiddle de dum
*j jj ,-
La matriz creada para el argumento de Other.main() se crea en el punto correspondi ente a la llamada al mtodo, as que
podemos incluso proporcionar argumentos alternativos en el momento de la llamada.
Ejercicio 16: ( 1) Cree una matri z de objetos String y asigne un objeto String a cada elemento. Imprima la matriz utili-
zando un bucle for .
Ejercicio 17: (2) Cree una clase con un constructor que tome un argumento String. Durante la construccin, imprima
el argumento. Cree una matriz de referencias a objetos de esta clase, pero sin crear ningn obj eto para asig-
narl o a la matriz. Cuando ejecute el programa, observe si se imprimen los mensajes de ini ciali zacin
correspondi entes a las llamadas al constructor.
Ejercicio 18: ( 1) Compl ete el ejercicio anterior creando objetos que asociar a la matriz de referencias.
Listas variables de argumentos
La segunda forma proporciona una sintaxis cmoda para crear e invocar mtodos que pueden producir un efecto similar a
las lisras variables de arglllne11los de C (conocidas con el nombre de "varargs" en C). Esto puede incluir un nmero desco-
nocido de argumentos que a su vez pueden ser de tipos desconocidos. Puesto que todas las clases se heredan en ltima ins-
tancia de la clase raz comn Object (tema del que hablaremos ms adelante en el libro), podemos crear un mtodo que
admite una matriz de Object e invocarlo del sigui ente modo:
/1: initialization/VarArgs.java
II USO de la sintaxis de matriz para crear listas variables de argumentos .
class A {}
public class VarArgs {
static void printArray(Object[) args)
for(Object obj : args)
System. out.print(obj + " tl) i
System.out.println() i
public static void main(String(] args) {
printArray(new Object[] {
114 Piensa en Java
new Integer ( 47 ) , new Float(3.14 ) , new Double (11.11 )
}) ,
printArray (new Object[] {"ane", "two
ll
, "three" }) i
printArray ( new Object[] (new A () , new A () , new A () } ) ;
/ * Output: ( Sample )
47 3.14 11.11
ene tWQ three
A@la46e3 0 A@3e25a5 A@19821f
* /// ,-
Podernos ver que print() admi te una matriz de tipo Object, y recorre la matri z ut ilizando la sintaxisforeach imprimiendo
cada objeto. Las clases de la bibli oteca estndar de Java generan una salida ms comprensibl e, pero los obj etos de las cIa-
ses que hemos creado aqu imprimen el nombre de la clase, seguido de un signo de '@' y de una serie de dgitos hexadeci-
males. Por tanto, el comportami ento predeternl inado (si no se define un mtodo toSt ring() para la clase, como veremos
posterionnente en el libro) consiste en imprimir el nombre de la clase y la direccin del obj eto.
Es posible que se encuentre con cdi go anteri or a Java SES escrito como el anteri or para generar li stas vari ables de argu-
mentos. Sin embargo, en Java SE5, esta caracterstica largo tiempo demandada ha si do finalmente aadida, por lo que ahora
podemos empl ear puntos suspensivos para definir una lista variabl e de argumentos, como puede ver en pr intArray() :
11: initializationjNewVarArgs.java
I I USO de la sintaxis de matrices para crear listas variables de argumentos.
public class NewVarArgs {
static void printArray(Object.
for (Object obj : args)
System.out.print (obj + " " ) ;
System.out.println( ) ;
args) {
public static void main (String(] args ) {
II Admite elementos individuales:
printArray(new Integer (47 ) I new Float (3.14) I
new Double(11.11 )) ;
printArray (47, 3.14F, 11.11 ) ;
printArray ( "one", "two", "three" ) ;
printArray (new A( ) , new A(), new A())
II O una matriz:
printArray l IObject [] ) new Integer[l{ 1,2,3,4 } ) ,
printArray () jI Se admite una lista vaca
1* Output: ( 75% match )
47 3.14 11.11
47 3.14 11.11
one two three
A@1babSOa A@c3c749 A@150bd4d
1 2 3 4
* /// , -
Con varargs, ya no es necesari o escribir explci tamente la sintaxis de la matriz: el compi lador se encargar de completarla
automticamente cuando especi fi quemos varargs. Seguimos obteni endo una matriz, lo cual es la razn de que pr int() siga
pudiendo uti lizar la sintaxisforeach para iterar a travs de la matriz. Sin embargo, se trata de al go ms que una simple con-
versin aut omtica entre una li sta de elementos y una matriz. Observe la penltima lnea del programa, en la que una matriz
de elementos Integer (creados con la caractersti ca de conversin automtica) se proyecta sobre una matriz Obj ect (para
evitar que el compi lador genere una advertencia) y se pasa a printArray(). Obviamente, el compi lador determina que esto
es ya una matri z, por lo que no realiza ninguna conversin con ella. De modo que, si tenemos un grupo de elementos, pode-
mos pasarlos como una li sta, y si ya tenemos una matriz, se aceptar esa matriz C0l110 li sta variable de argument os.
La ltima lnea del programa muestra que es posible pasar cero argumentos a una lista varmg. Esto resulta ti l cuando exis-
ten argumentos fina les opcionales:
JI : initialization/OptionalTrailingArguments.java
public class OptionalTrailingArguments {
static void f (int required, String ... trailing)
System.out,print( ll r equired: ti + required + I! n);
for(String s : trailing)
System. out .print(s + " " );
System.out.println() ;
public static void main(String[] args) {
f (l , "one") i
f (2, "two
U
, "three" );
f (O) ;
/ * Output :
required: 1 ane
required : 2 two three
requi red: o
*/// , -
5 Inicializacin y limpieza 115
Esto muestra tambin cmo se pueden utili zar varargs con un tipo especificado di stinto de Object. Aqu, todos los varargs
deben ser objetos String. Se puede utilizar cualquier tipo de argumentos en las listas varargs, incluyendo tipos primiti vos.
El siguiente ejemplo tambin muestra que la li sta vararg se transforma en una matri z y que, si no hay nada en la lista, se
tratar como una matri z de tamao cero.
JJ : initiaIizationJVarargType . java
public cIass VarargType {
static void f (Character ... args ) {
System. out.print{args .getClass()) i
System.out.println( " length " + args . length);
static void 9 (int ... args ) {
System.out.print{args .getClass()) i
System. out . println ( " length " + args.Iength)
public static void main (String [] args) {
f ( 'a' ) ;
fl);
g (1);
g() ;
System.out.println("int[) : " + new int(O) .getClass{))
J* Output:
class (Lj ava. lang . Character i Iength 1
class [Lj ava . Iang. Character i length O
class [I Iength 1
class [I Iength O
int [): class [I
* ///, -
El mtodo getClass( ) es parte de Objeet, y lo anali zaremos en detalle en el Captulo 14, Informacin de lipos. Devuelve
la clase de un objeto, y cuando se imprime esa clase, se ve una representacin del tipo de la clase en forma de cadena de
caracteres codificada. El carcter inicial ' 1' indica que se trata de una matriz del tipo situado a continuacin. La ' 1' indica
una primiti va nt; para comprobarlo, hemos creado una matri z de iut en la ltima lnea y hemos impreso su tipo. Esto per-
mi te comprobar que la utili zacin de varargs no depende de la caracterstica de conversin automtica, sino que utiliza en
la prctica los tipos primitivos.
Sin embargo, las li stas vararg funcionan perfectamente con la caracterstica de conversin automtica. Por ejemplo:
116 Piensa en Java
1/: initialization/AutoboxingVarargs.java
public class AutoboxingVarargs {
public static void f(Integer . . args) {
for(Integer i : args)
System.out.print{i + " U);
System.out.println{) ;
public static void main(String[] args)
f(new Integer(l), new Integer(2));
f(4, 5, 6, 7, 8, 9);
f(lO, new Integer{ll) I 12);
/* Output:
1 2
456789
10 11 12
* /// , -
Observe que se pueden mezclar los tipos en una misma lista de argumentos, y que la caracterstica de conversin automti-
ca promociona selecti vamente los argumentos nt a Integer.
Las li stas vararg compli can el proceso de sobrecarga, aunque ste parezca sufici entemente seguro a primera vista:
// : initialization/OverloadingVarargs.java
public class OverloadingVarargs {
statie void f(Charaeter . .. args)
System.out.print("first") ;
for(Character e : args)
System.out.print(" 11 + e) i
System.out.println () ;
statie void f(Integer.. args)
System.out.print{"second") ;
for(Integer i : args)
System.out.print(II 11 + i);
System.out.println() ;
static void f{Long ... args)
System.out.println{IIthird"l;
publie statie void main (String [J args) {
f ( ' a' I 'b' , 'e' l ;
f (1) ;
f (2, 1);
f (O) ;
f (aL) ;
II! f{) i 11 No se compilar -- ambigo
1* Output :
first a b e
seeond 1
seeond 2 1
seeond O
third
* /// ,-
En cada caso, el compil ador est utili zando la caracter stica de conversin automtica para determinar qu mtodo sobre-
cargado hay que utili zar, e invocar el mtodo que se ajuste de la fonna ms especfica.
5 Inicializacin y limpieza 117
Pero cuando se invoca f() sin argumentos, el compilador no tiene fanna de saber qu mtodo debe llamar. Aunque este error
es comprensible, probablemente sorprenda al programador de programas cliente.
podemos tratar de resolver el problema aadiendo un argumento no vararg a uno de los mtodos:
JI : initialization/OverloadingVarargs2.java
/ / {CompileTimeError} (Won' t compile)
public class OverloadingVarargs2 (
static void f(float i, Character ... args) {
System. out. printIn ( n f irst" ) i
static void f(Character ... argsl
System.out.print("second" ) ;
public static void main (String [] argsl {
f (1, 'a');
f ('a ', 'b') i
}
/// ,-
El marcador de comentario {CompileTimeError} excluye este archivo del proceso de constmccin Ant del libro. Si lo com-
pila a mano podr ver el mensaje de error:
relerenee /0 I is ambiguous, bo/h me/hod l(floG/Java.lang. Charae/"" .. ) in Overloading Varargs2 and
111et!Jod fOava./ang.Characte: . .) in Ove/oadingVarargs2 match
Si proporciona a ambos mtodos un argumento no-varO/'g, funcionar perfectamente:
11 : initialization/OverloadingVarargs3.java
public class OverloadingVarargs3 {
static void f(float i, Character ... args) {
System.out.println(Ufirst
U
) ;
static void f (char e, Character ... args) {
System.out.println(Usecond
U
) ;
public static void main(String[] args) {
f (1, la t) ;
f (t al, Ib
l
) i
1* Output:
first
second
' /// ,-
Generalmente, slo debe utilizarse una lista variable de argumentos en una nica versin de un mtodo sobrecargado. O
bien, considere el no utilizar la lista variable de argumentos en absoluto.
Ejercicio 19: (2) Escriba un mtodo que admita una matriz vararg de tipo Str ing. Verifique que puede pasar una lista
separada por comas de objetos Stri ng o una matriz String[] a este mtodo.
Ejerci cio 20: (1) Cree un mtodo main() que utilice varargs en lugar de la sintaxis main() normal. Imprima todos los
elementos de la matriz args resultante. Pruebe el mtodo con diversos conjuntos de argumentos de lnea
de comandos.
Tipos enumerados
Una adicin aparentemente poco importante en Java SES es la palabra clave enUDl , que nos facilita mucho las cosas cuan-
do necesitamos agrupar y utilizar un conjunto de tipos enumerados. En el pasado, nos veamos forzados a crear un conjun-
118 Piensa en Java
to de valores enteros constantes, pero estos conjuntos de valores no suelen casar muy bien con los conjuntos que se necesi-
tan definir y son, por tanto, ms arriesgados y dificiles de utilizar. Los tipos enumerados representan una necesidad tan
comn que C. e++ y diversos otros lenguajes siempre los han tenido. Antes de Java SES, los programadores de Java esta-
ban obligados a conocer muchos detalles y a tener mucho cuidado si queran emular apropiadamente el efecto de eoum.
Ahora. Java dispone tambin de enum, y lo ha implementado de una manera mucho ms completa que la que podemos
encontrar en C/C++. He aqu un ejemplo simple:
11 : initialization/ Spiciness.java
public enum Spiciness {
NOT, MILO, MEDIUM, HOT, FLAMING
} /1/ ,-
Esto crea un tipo enumerado denominado Spiciness con cinco valores nominados. Puesto que las instancias de los tipos enu-
merados son constantes, se suelen escribir en maysculas por convenio (si hay mltiples palabras en un nombre, se separan
mediante guiones bajos).
Para utilizar un tipo enum, creamos una referencia de ese tipo y la asignamos una instancia:
11 : initialization/ SimpleEnumUse.java
public class SimpleEnumUse {
public static void main{String[] args )
Spiciness howHot = Spiciness.MEDIUM
System.out.println{howHot) i
1* Output:
MEDIUM
* /// ,-
El compilador aade automticamente una seri e de caractersticas tiles cuando creamos un tipo eoum. Por ejemplo, crea
un mtodo toString() para que podamos visuali zar fcilmente el nombre de una instancia enum, y sa es precisamente la
forma en que la instruccin de impresin anterior nos ha penni tido generar la salida del programa. El compilador tambin
crea un mtodo ordinal( ) para indicar el orden de declaracin de una constante enum concreta, y un mtodo static values( )
que genera una matriz de valores con las constantes enum en el orden en que fueron declaradas:
11 : initialization/EnumOrder.java
public class EnumOrder {
public sta tic void main (String [] args ) {
for {Spiciness s : Spiciness.values{))
System.out.println(s + ", ordinal 11 + s.ordinal());
1* Output:
NOT, ordinal O
MILD, ordinal 1
MEDIUM, ordinal 2
HOT, ordinal 3
FLAMING, ordinal 4
* /// ,-
Aunque los tipos enumerados enum parecen ser un nuevo tipo de datos, esta palabra clave slo provoca que el compi lador
realice una serie de activi dades mientras genera una clase para el tipo enum, por lo que un enum puede tratarse en muchos
sentidos como si fuera una clase de cualquier otro tipo. De hecho, los tipos enum son clases y tienen sus propios mtodos.
Una caracterstica especialmente atractiva es la forma en que pueden usarse los tipos enum dentro de las instrucciones
switch:
11 : initialization/Burrito.java
public class Burrito {
Spiciness degree
public Burrito (Spiciness degree ) { this. degree
public void describe () {
System. out. print ( "This burrito is " )
degree; }
switch(degree)
case NOT: System.out.println(lInot spicy at all . tI) i
break
case MILD:
case MEDIUM: System.out.println(lt a little hot.");
break;
case HOT:
case FLAMING:
defaul t: System. out. println ( tlmaybe too hot. 11 ) ;
public static void main(String[] argsl
/ *
This
This
This
Burrito
plain = new Burrito (Spiciness.NOT).
greenChile = new Burrito (Spiciness.MEDIUM) ,
jalapeno = new Burrito (Spiciness.HOT) ;
plain . describe () ;
greenChile.describe() ;
jalapeno.describe () ;
Output:
burrito
burrito
burrito
is
is
is
not spicy at all.
a little hoto
maybe too hot.
*/// ,-
5 Inicializacin y limpieza 119
Puesto que una instruccin switch se emplea para seleccionar dentro de un conjunto limitado de posibilidades, se comple-
menta perfectamente con un tipo enum. Observe cmo los nombres enum indican de una manera mucho ms clara qu es
lo que pretende hacer el programa.
En general , podemos utilizar un tipo enum como si fuera otra fOn1la de crear un tipo de datos, y limitamos luego a utilizar
los resultados. En realidad, eso es lo importante, que no es necesario prestar demasiada atencin a su uso, porque resulta
bastante simple. Antes de la introduccin de enum en Java SE5, era necesario realizar un gran esfuerzo para construi r un
tipo enumerado equivalente que se pudiera emplear de fOn1la segura.
Este breve anlisi s es suficiente para poder comprender y utilizar los tipos enumerados bsicos, pero examinaremos estos
tipos enumerados ms profundamente en el Captulo 19, Tipos enumerados.
Ejerci cio 21 : ( 1) Cree un tipo enum con los seis lipos de billetes de euro de menor valor. Recorra en bucle los valores
utilizando values() e imprima cada va lor y su orden correspondiente con ordinal().
Ejercici o 22: (2) Escriba una instruccin switch para el tipo enum del ejercicio anterior. En cada case, imprima una des-
cripci n de ese billete concreto.
Resumen
Este aparentemente elaborado mecani smo de ini cial izacin, el constructor, nos indica la importancia crtica que las tareas
de inicializacin tienen dentro del lenguaje. Cuando Sjame Stroustrup, el inventor de C++, estaba di seando ese lenguaje,
una de las primeras cosas en las que se fij al analizar la productividad en C fue que la inicializacin inadecuada de las varia-
bles es responsable de una parte significativa de los problemas de programacin. Este tipo de errores son dificiles de loca-
lizar, y lo mismo cabra decir de las tareas de limpi eza inapropiadas. Puesto que los constructores nos pem'ten garantizar
una iniciali zacin y limpieza adecuadas (el compi lador no pennitir crear un objeto si n las apropiadas llamadas a un cons-
tructor), la seguridad y el control estn garantizados.
En C++. la destruccin tambin es muy importante, porque los objetos creados con new deben ser destruidos explcitamen-
te. En Java, el depurador de memoria libera automticamente la memoria de los objetos que no son necesarios, por lo que
el mtodo de limpieza equivalente en Java no es necesario en muchas ocasiones (pero cuando lo es, es preciso implemen-
tarlo explcitamente). En aquellos casos donde no se necesite un comportamiento simi lar al de los destructores, el mecanis-
mo de depuracin de memoria de Java simplifica enOn1lemente la programacin y mejora tambi n en gran medida la
120 Piensa en Java
seguridad de la gestin de memori a. Algunos depuradores de memoria pueden incluso limpi ar otros recursos, como los
recursos grficos y los descriptores de archivos. Sin embargo, el depurador de memori a hace que se incremente el coste de
ejecucin, resultando dificil evaluar adecuadamente ese coste, debido a la lentitud que hi stricamente han tenido los intr-
pretes de Java. Aunque a lo largo del tiempo se ha mejorado significati vamente la velocidad de Java, un probl ema de la velo-
cidad ha supuesto un obstculo a la hora de adoptar este lenguaje en ciertos tipos de problemas de programacin.
Debido a que est garanti zado que todos los objetos se construyan, los constructores son ms complejos de lo que aqu
hemos mencionado. En particular, cuando se crean nuevas clases utilizando los mecani smos de composicin o de herencia,
lambin se mantiene la garanta de constnlccin, siendo necesaria una cierta sintaxi s adicional para soportar este mecanis-
mo. Hablaremos de la composicin, de la herencia y del efecto que ambos mecani smos tienen en los constnlctores en pr-
ximos captulos.
Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico Tlle Thinking in Java AnnOfafed Sofllfion Guide. que esta dispo-
nible para la venta en Inm:MindView.llel.
Control de acceso
El control de acceso (u ocultacin de la implementacin) trata acerca de
"que no salgan las cosas a la primera".
Todos los buenos escritores, incluyendo aquellos que escriben software, saben que un cierto trabajo no est tem1inado hasta
despus de haber sido reescrito. a menudo muchas veces. Si dejamos un fragmento de cdigo encima de la mesa durante un
tiempo y luego volvemos a l, lo ms probable es que veamos una [anna mucho mejor de escribirlo. sta es una de las prin-
cipales motivaciones para el trabajo de rediseo, que consiste en reescribir cdigo que ya funciona con el fin de hacerlo ms
legible, comprensible y, por tanto, mantenible.'
Sin embargo, existe una cierta tensin en este deseo de modificar y mejorar el cdigo. A menudo, existen consumidores (pro-
gramadores de cliente) que dependen de que ciertos aspectos de nuestro cdigo cont inen siendo iguales. Por tanto.
nosotros queremos modificar el cdigo, pero ellos quieren que siga siendo igual. Es por eso que una de las principales con-
sideraciones en el diseo orientado a objetos es la de "separar las cosas que cambian de las cosas que pennanecen"'.
Esto es particulannente importante para las bibliotecas. Los consumidores de una biblioteca deben poder confiar en el ele-
mento que estn utilizando, y saber que no necesitarn reescribir el cdigo si se publica una nueva versin de la biblioteca.
Por otro lado, el creador de la biblioteca debe tener la libertad de realizar modificaciones y mejoras, con la confianza de que
el cdigo del cliente no se ver afectado por esos cambios.
Estos objetivos pueden conseguirse adoptando el convenio adecuado. Por ejemplo, el programador de la biblioteca debe
aceptar no eliminar los mtodos existentes a la hora de modificar una clase de la biblioteca, ya que eso hara que dejara de
funcionar el cdigo del programador de clientes. Sin embargo, la sintacin inversa es un poco ms compleja de resolver. En
el caso de un campo, cmo puede saber el creador de la biblioteca a qu campos han accedido los programadores de clien-
tes? Lo mismo cabe decir de los mtodos que slo forman parte de la implementacin de una clase y que no estn para ser
usados directamente por el programador de clientes. Qu pasa si el creador de la biblioteca quiere deshacerse de una imple-
mentacin anterior y sustituirla por una nueva? Si se modifica alguno de esos miembros, podra dejar de funcionar el cdi-
go de algn programa cliente. Por tanto, el creador de la biblioteca tiene las manos atadas y no puede modificar nada.
Para resolver este problema. Java proporciona especificadores de aCceso que penniten al creador de la biblioteca decir qu
cosas estn disponibles para el programa cliente y qu cosas no lo estn. Los niveles de control de acceso, ordenados de
mayor a menor acceso, son public, protected, acceso de paquete (que no ti enen una palabra clave asociada) y private.
Leyendo el prrafo anterior, podramos pensar que, como diseadores de bibliotecas, conviene mantener todas las cosas 10
ms "privadas" posible y exponer slo aquellos mtodos que queramos que el programa cliente utilice. Esto es CIerto, aun-
que a menudo resulta antinatural para aquellas personas acostumbradas a programar en otros lenguajes (especialmente C) y
que estn acostumbradas a acceder a todo sin ninguna restriccin. Cuando lleguen al final del captulo, estas personas esta-
rn convencidas de la utilidad de los controles de acceso en Java.
Sin embargo, el concepto de biblioteca de componentes y el control acerca de quin puede acceder a los componentes de
esa biblioteca no es completo. Sigue quedando pendiente la cuestin de cmo empaquetar los componentes para [onnar
1 Consulte RelaclOril/g: Improlling ,he Design 01 Exisling Code, de Martin Fowlcr, el al. (AddisonWesley, 1999). Ocasionalmente. algunas personas argu
mentarn en contra de las lareas de rediseo, sugiriendo que tUl cdigo que ya funciona es perfectamente adecuado, por lo que resulta una prdida dc ticm
po tratar de redisearlo. El problema con esta fomla de pensar es que la parte del len en lo que se refiere al tiempo y al dinero consumidos por un proyecto
no est en la escritura inicial del cdigo. sino en su mantenimiento. Hacer el cdigo ms fcil de entender pernlite ahorrar una gran cantidad de dinero.
122 Piensa en Java
una unidad de biblioteca cohesionada. Este aspecto se controla mediante la palabra clave package en Java. y los especifi-
cadores de acceso se vern afectados por el hecho de que una clase se encuentra en el mi smo paquete o en otro paquete di s-
tinto. Por tanto, para comenzar este captulo, veamos primero cmo se incluyen componentes de biblioteca en los paquetes.
Con eso, seremos capaces de entender compl etamente el significado de los especificadores de acceso.
package: la unidad de biblioteca
Un paquete contiene un grupo de clases, organizadas conjuntamente dentro de un mi smo espacio de nombres.
Por ejemplo, existe una biblioteca de utilidad que fomla parte de la di stribucin estndar de Java, organizada bajo el espa-
cio de nombres java.util. Una de las clases de java.util se denomina ArrayList. Una fonua de utilizar un objeto Arr.yList
consiste en especi ficar elllombre completo java.util.ArrayList.
JI : access / FullQualification.java
public class FullQualification {
public static void main (String[] args ) {
java.util.ArrayList list = new java.util.ArrayList( ) ;
)
/1 / ,-
Sin embargo, este procedimiento se vuel ve rpidamente tedioso, por lo que suele ser ms cmodo utilizar en su lugar la pala-
bra clave import. Si queremos importar una nica clase, podemos indicar esa clase en la instruccin import:
JI : access / Singlelmport.java
import java.util.ArrayList
public c!ass Singlelmport {
public static void main (String [] args) {
ArrayList list = new java.util . ArrayList ( ) i
)
111 , -
Ahora podemos usar ArrayList sin ningn cualificador. Sin embargo, no tendremos a nuestra disposicin ninguna de las
otras clases de java.util. Para importar todas, basta con utili zar '*' tal como hemos visto en los ejemplos del libro.
import java.util.*;
La razn para efectuar estas importaciones es proporcionar un mecani smo para gestionar los espacios de nombres. Los nom-
bres de todos los miembros de las clases estn aislados de las clases restantes. Un mtodo f( ) de la clase A no coincidir
con un mtodo f() que tenga la misma signatura en la clase B. Pero qu sucede con los nombres de las clases? Suponga
que creamos una clase Stack en una mquina que ya disponga de otra clase Stack escrita por alguna otra persona. Esta posi-
bilidad de colisin de los nombres es la que hace que sea tan importante disponer de un control completo de los espacios de
nombres en Java, para poder crear una combinacin de identificadores unvoca para cada clase.
La mayora de los ejemplos que hemos visto hasta ahora en el libro se almacenaban en un nico archivo y haban sido di se-
nados para uso local, por lo que no nos hemos preocupado de los nombres de paquete. Lo cierto es que estos ejemplos s
estaban incluidos en paquetes: el paquete predeterminado o "innominado". Ciertamente, sta es una opcin viable y trata-
remos de utilizarla siempre que sea posible en el resto del libro, en aras de la simplicidad. Sin embargo, si lo que pretende-
mos es crear bibliotecas o programas que puedan cooperar con otros programas Java que estn en la misma mquina,
deberemos tener en cuenta que hay que evitar las posibles colisiones entre nombres de clases.
Cuando se crea un archivo de cdigo fuente para Java, normalmente se le denomina unidad de compilacin (y tambin, en
ocasiones, unidad de traduccin). Cada lmidad de compil acin debe tener un nombre que termine en .java, y dentro de la
unidad de compilacin puede haber UDa clase public que debe tener el mi smo nombre del archivo (i ncluyendo el uso de
maysculas y minsculas, pero excluyendo la extensin .java del nombre del archivo). Slo puede haber una clase public
en cada unidad de compi lacin; en caso contrario, el compilador se quejar. Si existen clases adicionales en esa unidad de
compil acin, estarn ocultas para el mundo exterior al paquete, porque no son public, y simplemente se tratar de clases
"soporte" para la clase public principal.
6 Control de acceso 123
Organizacin del cdigo
Cuando se compila un archivo .j ava. se obtiene un archivo de salida para cada clase del archivo .java. Cada archivo de
salida tiene el nombre de una de las clases del archi vo .java, pero con la extensin .class. De este modo, podemos ll egar a
obtener un gran nmero de archivos .class a partir de un nmero pequeo de archivos .java. Si el lector ha programado ante-
riom1ente en algn lenguaje compilado, estar acostumbrado al hecho de que el compilador genere algn fonnato intenne-
dio (nannalmente un archi vo "obj") que luego se empaqueta con otros del mi smo tipo utilizando un montador (para crear
un archivo ejecutable) o un gestor de biblioteca (para crear una biblioteca). sta no es la fonna de funciona r de Java. Un
programa funcional es un conjunto de archivos .class, que se puede empaquetar y comprimir en un archivo JAR (Java
ARrchi ve), utilizando el archivador jar de Java. El intrprete de Java es responsable de localizar, cargar e interpretar2 estos
archivos.
Una bibl ioteca es un gmpo de estos archivos de clase. Cada archi vo fuente suele tener una clase public y un nmero arbi-
trario de clases no pblicas, por lo que no slo existe un componente public para cada archivo fuente. Si queremos especi-
ficar que todos estos componentes (cada uno con sus propios archi vos .java y .c1ass separados) deben agruparse, podemos
utilizar la palabra clave package.
Si usamos una instmccin package, debe aparecer como la primera lnea no de comentario en el archivo. Cuando escribi -
mos:
package access;
estamos indicando que esta unidad de compilacin es parte de una biblioteca denominada access. Dicho de otro modo, esta-
mos especificando que el nombre de clase pblica situado dentro de esta unidad de compilacin debe int egrarse bajo el
"paraguas" correspondient e al nombre access, de modo que cualquiera que quiera usar ese nombre deber especi fi carlo por
completo o utili zar la palabra clave import en combinacin con access, utili zando las opciones que ya hemos mencionado
anteriormente (observe que el convenio que se empl ea para los nombres de paquetes Java consiste en emplear letras mins-
culas, incl uso para las palabras intermedias).
Por ejemplo, suponga que el nombre de un archi vo es MyClass.java. Esto quiere decir que slo puede haber una clase
public en dicho archivo y que el nombre de esa clase debe ser MyClass (respetando el uso de maysculas y minscu-
las):
11 : access/mypackage/MyClass.java
package access.mypackage;
public class MyClass {
//
} /// ,-
Ahora. si alguien quiere utilizar MyClass o cualquiera olra de las clases pblicas de access, deber emplear la palabra clave
import para que estn disponibles esos nombres defi nidos en el paquete access. La alternativa consiste en especificar el
nombre completamente cualificado:
11: access/QualifiedMyClass.java
public class QualifiedMyClass {
public static void main(String[] args)
access.mypackage.MyClass m =
new access.mypackage.MyClass();
La palabra clave import permi te que este ejemplo tenga un aspecto mucho ms simpl e:
11 : access/lmportedMyClass.java
import access.mypackage.*
2 No hay ninguna caracterstica de Java que nos obligue a utilizar un interprete. Existen compiladores Java de cdigo nativo que generan un nico archi*
vo ejecutable.
124 Piensa en Java
public class ImportedMyClass {
public static void main (String[] args ) {
MyClass m = new MyClass () ;
}
/// , -
Merece la pena tener presente que lo que las palabras clave package e import nos penniten hacer. como diseiiadores de
bibliotecas, es dividir el espacio de nombres global nico, para que los nombres no colisionen, independientemente de cun-
tas personas se conecten a Internet y comiencen a escribir clases en Java.
Creacin de nombres de paquete unvocos
El lector se habr percatado de que, dado que un paquete nunca estar realmente "empaquetado" en un solo archivo, podr
estar compuesto por muchos archivos .class, por lo que el sistema de archivos puede llegar a estar un tanto abarrotado. Para
evitar el desorden, una medida lgica que podemos tomar seria colocar todos los archivos .class correspondientes a un
paquete concreto dentro de un mismo directorio; es decir, aprovechar la estmctura de archivos jerrquica del sistema ope-
rativo. sta es una de las formas mediante las que Java trata de evitar el problema de la excesiva acumulacin de archivos;
veremos esto de otra fOn1la cuando ms adelante presentemos la utilidad jaro
Recopilar los archivos de un paquete dentro de un mismo subdirectorio resuelve tambin otros dos problemas: la creacin
de nombres de paquete un vocos y la localizacin de aquellas clases que puedan estar perdidas en algn lugar de la estruc-
tura de directorios. Esto se consigue codificando la ruta correspondiente a la ubicacin del archivo .class dentro del nombre
del paquete. Por convenio, la primera parte del nombre del paquete es el nombre de dominio Internet invertido del creador
de la clase. Dado que est garantizado que los nombres de dominio Internet sean unvocos, si seguimos este convenio nues-
tro nombre de paquete ser unvoco y nunca se producir una col isin de nombres (es decir, salvo que perdamos el derecho
a utilizar el nombre de dominio y la persona que 10 comience a utilizar se dedique a escribir cdigo Java con los mismos
nombres de ruta que usted utili z). Por supuesto, si no disponemos de nuestro propio nombre de dominio, deberemos con-
cebir una combinacin que resulte lo suficientemente improbable (como por ejemplo la combinacin de nuestro nombre y
apellidos) para crear nombres de paquete unvocos. Si ha decidido comenzar a publicar cdigo Java, merece la pena que
haga un requefio esfuerzo para obtener un nombre de dominio.
La segunda parte de esta soluci n consiste en establecer la correspondencia entre los nombres de paquete y los directorios
de la mquina, de modo que cuando el programa Java se ejecute y necesita cargar el archivo .class, pueda localizar el direc-
torio en el que ese archivo .class resida.
El intrprete Java acta de la fomla siguiente. Primero, localiza la variable de entamo CLASSPATH
3
(que se tija a travs
del sistema operativo y en ocasiones es definida por el programa de instalacin que instala Java con una herramienta basa-
da en Java en la mquina). CLASSPATH contiene uno o ms directorios que se utilizan como races para buscar los archi-
vos .class. Comenzando por esa raz, el intrprete toma el nombre de paquete y sustituye cada pWltO por una barra inclinada
para generar un nombre de ruta a partir de la raiz CLASSPATH (por lo que el paquete package roo.bar.baz se convertiria
en foo\bar\baz o foo/bar/baz o, posiblemente, en alguna otra cosa, dependiendo del sistema operativo). Esto se concatena
a continuacin con las diversas entradas que se encuentren en la variable CLASSPATH. Ser en ese subdirectorio donde el
intrprete busque el archi vo .class que tenga un nombre que se corresponda con la clase que se est intentando crear (tam-
bin busca en algunos directorios estndar relativos al lugar en el que reside el intrprete Java).
Para comprender esto, considere por ejemplo mi nombre de dominio, que es MindView.net. Invirtiendo ste y pasndolo a
minsculas, net.mindview establece mi nombre global un voco para mis clases (antiguamente, las extensiones com, edu,
org, etc., estaban en maysculas en los paquetes Java, pero esto se modific en Java 2 para que todo el nombre del paque-
te estuviera en minsculas). Puedo subdi vi dir este espacio de nombres todava ms creando, por ejemplo, una biblioteca
denominada simple, por lo que tendr un nombre de paquete que ser:
package net.mindview.simple
Ahora, este nombre de paquete puede uti lizarse como espacio de nombres paraguas para los siguientes dos archivos:
3 Cuando nos refiramos a la variable de entamo, utilizaremos letras maysculas (CLASSPATH).
JI : net / mindview/ simple / Vector . java
/1 Creacin de un paquete .
package net . rnindview . s i mpl e;
public class Vector {
public Vector () (
Syst.em. out . println ( Unet.mindview. simpl e . Vector
Ol
) i
)
111 >
6 Control de acceso 125
Como hemos mencionado antes, la instruccin package debe ser la primera lnea de no comentario dentro del cdigo del
archi vo. El segundo archivo tiene un aspecto parecido:
JI : net / mindview/ simple/ List . java
/1 Creacin de un paquete .
package net . mindview . simple
public class List {
public List 11 {
Sys t em.out . print ln( "net . mindview. simpl e . Lis t" } ;
Ambos archivos se ubicarn en el siguiente subdirectorio de mi sistema:
C: \ DOC\ JavaT\ net \ mindview\si mple
Observe que la primera lnea de comentario en cada archivo del libro indica la ubicacin del directorio donde se encuentra
ese archi vo dentro del rbol del cdigo fuente; esto se usa para la herramienta automtica de extraccin de cdigo que he
empleado con el libro.
Si examinamos esta ruta, podemos ver el nombre del paquete net.mindview.simple, pero qu pasa con la primera pane de
la ruta? De esa parte se encarga la vari able de entorno CLASSPATH, que en mi mquina es:
CLASSPATH= . ;O, \ JAVA\LIB;C, \ OOC\ JavaT
Podemos ver que la variable de en tomo CLASSPATH puede contener W13 serie de rutas de bsqueda allemativas.
Sin embargo, existe una variacin cuando se usan archivos JAR. Es necesario poner el nombre real del archi vo lAR en la
vari abl e de mta, y no simpl emente la ruta donde est ubi cado. As, para un archi vo lAR denominado grape.jar, la variable
de mta incluira:
CLASSPATH= . ;O, \ JAVA\ LIB;C, \ flavors \ grape.jar
Una vez que la variable de ruta de bsqueda se ha confi gurado apropiadamente, el siguiente archivo puede ubi carse en cual-
quier directorio:
11 : access / LibTest. j ava
II Utiliza la biblioteca.
i mport net.mindview.simple . *
public class LibTest {
public stat ic void main (String [J args ) {
Vector v : new Vect or( ) ;
List 1 : new List {) ;
1* Output:
net.mindview. simple . Vector
net.mindview. simple . List
' /11 ,-
Cuando el compilador se encuentra con la instruccin import correspondiente a la biblioteca simple, comienza a explorar
todos los directorios especifi cados por CLASSPATH, en busca del subdirectorio net/ mindview/simple, y luego busca los
archi vos compilados con los nombres apropiados (Vector.c1ass para Vector y List.c1ass para List). Observe que tanto las
dos clases C0 l110 los mtodos deseados de Vector y List tienen que ser de tipo public.
126 Piensa en Java
La configuracin de CLASSPATH resullaba tan enigmtica para los usuarios de Java inexpertos (al menos lo era para mi
cuando comenc con el lenguaje) que Sun ha hecho que en el kit JDK de las vcrsiones ms recientes de Java se compOrte
de fomla algo ms inteligente. Se encontrar, cuando lo instale. que aunque no configure la variable CLASSPATH. podr
compilar y ejecutar programas Java bsicos. Sin embargo. para compilar y ejecutar el paquete de cdigo fuente de este libro
(disponible en 1I'1I'1I'.MindVie1l'. ne). necesi tar anadir a la variable CLASSPATH el directorio base del rbol de cdigo.
Ejercicio 1: (1) Cree una clase dent ro de un paquete. Cree una instancia de esa clase fuera de dicho paquete.
Colisiones
Qu sucede si se importan dos bibliotecas mediante '*' y ambas incluyen los mi smos nombres? Por ejemplo, suponga que
un programa hace esto:
impore net.mindview.simple.*;
import java.util.*;
Puesto que java.util.* tambin contiene una clase Vector. esto provocara una potencial colisin. Sin embargo, mientras que
no lleguemos a escribir el cdigo que provoque en efecto la coli sin, no pasa nada. Resulta bastante conveniente que esto
sea as, ya que de otro modo nos veramos forzados a escri bir un montn de cosas para evitar coli siones que realmente nunca
iban a suceder.
La colisin s que se producir si ahora intentamos constmir un Vector:
Vector v = new Vector {) ;
A qu clase Vector se refiere esta lnea? El compilador no puede saberl o, como tampoco puede saberlo el lector. As que
el compil ador generar un error y nos obligar a ser ms explci tos. Si queremos utili zar el Vector Java estndar, por ejem-
pl o, deberemos escribir:
java.util.Vector v = new java.util.Vector {) ;
Puesto que esto Uunto con la variable CLASSPATH) especifica completamente la ubicacin de la clase Vector deseada, no
existir en realidad ninguna necesidad de emplear la instruccin import java.util.*, a menos que vayamos a utilizar algu-
na Olra clase definida en java.util .
Alternativamente, podemos utili zar la instmccin de importacin de una nica clase para prevenir las colisiones, siempre y
cuando no empleemos los dos nombres que entran en coli sin dentro de un mi smo programa (en cuyo caso, no tendremos
ms remedi o que especificar completamente los nombres).
Ejercicio 2: ( 1) Tome los fragmentos de cdigo de esta seccin y transfnnelos en un programa para verificar que se
producen las colisiones que hemos mencionado.
Una biblioteca personalizada de herramientas
Armados con este conocimiento, ahora podemos crear nuestras propias bibliotecas de herrami erllas, para reducir o eliminar
la escritura de cdi go duplicado. Considere, por ejemplo, el alias que hemos estado utili zando para Syslem.out.println().
con el fin de reducir la cantidad de infonnacin tecleada. Esto puede ser part e de una clase denorninada Priot, de modo que
dispondramos de una instruccin esttica de impresin bastante ms legible:
ji : net / mindview/ util / Print.java
JI Mtodos de impresin que pueden usarse sin
JI cualificadores, empleando importaciones estticas de Java SE5:
package net.mindview. util;
import java.io.*;
public class Print
JI Imprimir con una nueva lnea:
public static void print(Object obj)
System.out .println{objl;
JI Imprimir una nueva lnea sola:
public static void print()
System.out.println(} ;
JI Imprimir sin salto de lnea:
public static void printnb (Object obj) {
System.out.print(obj) ;
/ / El nuevo printf () de Java SES (de el :
public static PrintStream
printf (String format, Obj ect. .. args) {
return System. out.printf(format, args );
6 Controt de acceso 127
Podemos utilizar estas abreviaturas de impresin para imprimir cualquier cosa, bien con la insercin de una nueva lnea
(print( o sin una nueva lnea (printnb( .
Como habr adivinado, este archivo deber estar ubi cado en un directorio que comience en una de las ubicaciones defini-
das en CLASSPATH y que luego contine con net/ mindview. Despus de compilar, los mtodos sta tic print() y printnb()
pueden emplearse en cualqui er lugar del sistema utilizando una instruccin import sta tic:
11: access/PrintTest.java
II Usa los mtodos estticos de impresin de Print.java.
import static net.mindview. util.Print.*
public class PrintTest {
public static void main(String[] args)
print ( !tAvailable frem now on! It)
print ( 100);
print (100L);
print(3.14159) ;
1* Output:
Available frem now on!
100
100
3.14159
* /// , -
Un segundo componente de esta biblioteca pueden ser los mtodos range(), que hemos presentado en el Captulo 4. Con/rol
de la ejecucin, y que penniten el uso de la sintaxisforeach para secuencias simples de enteros:
11 : net/mindview/util/Range .java
II Mtodos de creacin de matrices que se pueden usar sin
II cualificadores, con importaciones estticas Java SES:
package net.mindview.util
public class Range {
II Generar una secuencia [O . . n)
public static int[] range(int n)
int [] result "" new int [n]
for (i nt i = O i < n i++)
result[i] = i
return resul t
II Generar una secuencia [start .. end)
public static int[] range (int start, int end) {
int sz = end - start
int [] result = new int [sz] ;
for(int i = O; i < sz; i++)
result[i] start + i;
128 Piensa en Java
return result;
/ 1 Generar una secuencia [start .. end) con incremento igual a step
public static int [] range {int start, int end, int step) {
int sz = (end - start l/ step;
int[] result = new int[sz);
for (int i = O; i < 5Z; i++ l
result[i) = start + (i * step) ;
return result;
A partir de ahora, cuando desarrolle cualquier nueva utilidad que le parezca interesante la podr aadir a su propia biblio-
teca. A lo largo del libro podr ver que cmo aadiremos ms componentes a la biblioteca net.mindview.util
Utilizacin de importaciones para modificar el comportamiento
Una caracterstica que se echa en falta en Java es la compilacin condicional que existe en e y que pemlite cambiar una
variable indicadora y obtener un comportamiento diferente sin variar ninguna otra parte del cdigo. La razn por la que
dicha caracterstica no se ha incorporado a Java es, probablemente, porque la mayor parte de las veces se utiliza en C para
resolver los problemas interplataforma: dependiendo de la platafomla de destino se compilan diferentes partes del cdigo.
Puesto que Java est pensado para ser automticamente un lenguaje interplatafornla no debera ser necesaria.
Sin embargo, existen otras aplicaciones interesantes de la compilacin condicional. Un uso bastante comn es durante la
depuracin del cdigo. Las caractersticas de depuracin se activan durante el desarrollo y se desactivan en el momento de
lanzar el producto. Podemos conseguir el mismo efecto modificando el paquete que se importe dentro de nuestro programa,
con el fin de conmutar entre el cdigo utilizado en la versin de depuracin y el empleado en la versin de produccin. Esta
misma tcnica puede utilizarse para cualquier cdigo de tipo condicional.
Ejercicio 3: (2) Cree dos paquetes: debug y debugoff, que contengan una clase idntica con un mtodo debug(). La
primera versin debe mostrar su argumento String en la consola, mientras que la segunda no debe hacer
nada. Utilice una lnea sta tic import para importar la clase en un programa de prueba y demuestre el efec-
to de la compilacin condicional.
Un consejo sobre los nombres de paquete
Merece la pena recordar que cada vez que creamos un paquete, estamos especificando implcitamente una estructura de
directorio en el momento de dar al paquete un nombre. El paquete debe estar en el directorio indicado por su nombre, que
deber ser un directorio alcanzable a panir de la mta indicada en CLASSPATH. Experimentar con la palabra clave packa-
ge puede ser algo frustrante al principio, porque a menos que respetemos la regla que establece la correspondencia entre
nombres de paquete y rutas de directorio. obtendremos un montn de misteriosos mensajes en tiempo de ejecucin que nos
dicen que el sistema no puede encontrar una clase concreta, incluso aunque esa clase est ah en el mismo directorio. Si
obtiene un mensaje como ste, desactive mediante un comentario la instruccin package y compruebe si el programa fun-
ciona, si lo hace, ya sabe dnde est el problema.
Observe que el cdigo compi lado se coloca a menudo en un directorio distinto de aquel en el que reside el cdigo fuent e.
pero la ruta al cdigo compilado deber seguir siendo localizable por la JVM utilizando la variable CLASSPATH.
Especificadores de acceso Java
Los especificadores de acceso Java public, protected y private se colocan delante de cada definicin de cada miembro de
la clase, ya sea ste un campo O un mtodo. Cada especificador de acceso slo controla el acceso para esa definicin con-
creta.
Si no proporciona un especificador de acceso, querr decir que ese miembro tiene "acceso de paquete". Por tanto, de una
fonna u otra. todo tiene asociado algn tipo de control de acceso. En las secciones siguientes. vamos a analizar los diversos
tipos de acceso.
6 Control de acceso 129
Acceso de paquete
En los ejemplos de los captulos anteriores no hemos utilizado especificadores de acceso. El acceso predetenninado no tiene
asociada ninguna palabra clave, pero comnmente se hace referencia a l como acceso ele paquete (y tambin, en ocasio-
nes, "acceso amigable"). Este tipo de acceso significa que todas las dems clases del paquete actual tendrn acceso a ese
miembro, pero para las clases situadas fuera del paquete ese miembro aparecer como priva te. Puesto que cada unidad de
compilacin (cada archivo) slo puede pertenecer a un mismo paquete, todas las clases dentro de una misma unidad de com-
pilacin estarn automticamente disponibles para las otras mediante el acceso de paquete.
El acceso de paquete nos permite agrupar en un mismo paquete una serie de clases relacionadas para que puedan interac-
tuar fcilmente entre s. Cuando se colocan las clases juntas en un paquete, garantizando as el acceso mutuo a sus miem-
bros definidos con acceso de paquete, estamos en cierto modo garantizando que el cdigo de ese paquete sea "propiedad"
nuestra. Resulta bastante lgico que slo el cdigo que sea de nuestra propiedad disponga de acceso de paquete al resto del
cdigo que nos pertenezca. En cierto modo. podramos decir que el acceso de paquete hace que tenga sentido el agrupar las
clases dentro de un paquete. En muchos lenguajes. la forma en que se hagan las definiciones en los archivos puede ser arbi-
traria. pero en Java nos vemos impelidos a organizarlas de una fomla lgica. Adems, podemos aprovechar la definicin del
paquete para excluir aquellas clases que no deban tener acceso a las clases que se definan en el paquete actual.
Cada clase se encarga de controlar qu cdigo tiene acceso a sus miembros. El cdigo de los restantes paquetes no puede
presentarse sin ms y esperar que le muestren los miembros protected, los miembros con acceso de paquete y los miem-
bros private de una detemlinada clase. La nica fomla de conceder acceso a un miembro consiste en:
l. Hacer dicho miembro public. Entonces, todo el mundo podr acceder a l.
2. Hacer que ese miembro tenga acceso de paquete, por el procedimiento de no incluir ningn especificador de acce-
so, y colocar las otras clases que deban acceder a l dentro del mismo paquete. Entonces, las restantes clases del
paquete podrn acceder a ese miembro.
3. Como veremos en el Captulo 7, Rculili::acin de clases, cuando se introduce la herencia. una clase heredada puede
acceder tanto a los miembros protectcd como a los miembros pubLic (pero no a los miembros priva te). Esa clase
podr acceder a los miembros con acceso de paquete slo si las dos clases se encuentran en el mismo paquete.
Pero, por el momento, vamos a olvidamos de los temas de herencia y del especificador de acceso protected.
4. Proporcionar mtodos "de acceso/mutadores" (tambin denominados mtodos "get/set") que pennitan leer y
cambiar el valor. ste es el enfoque ms civilizado en tnninos de programacin orientada a objetos, y resulta
fundamental en JavaBeans, como podr ver en el Captulo 22, blle/faces grficas de usuario.
public: acceso de interfaz
Cuando se utiliza la palabra clave public, sta quiere decir que la declaracin de miembros situada inmediatamente a con-
tinuacin suya est disponible para todo el mundo, y en particular para el programa cliente que utilice la biblioteca. Suponga
que definimos un paquete desscrt que contiene la siguiente unidad de compilacin:
//: access/dessert/Cookie.java
// Crea una biblioteca.
package access.dessert;
public class Cookie {
public Cookie () (
System.out .println(IICookie constructor");
}
void bite() { System.out.println("bite"); }
/// ,-
Recuerde que el archivo de clase producido por Cookie.java debe residir en un subdirectorio denominado dessert, dentro
de un directorio access (que hace referencia al Captulo 6, Control de acceso de este libro) que a su vez deber estar bajo
uno de los directorios CLASSPATH. No cometa el error de pensar que Java siempre examinar el directorio actual como
uno de los puntos de partida de su bsqueda. Si no ha incluido un ... como una de las rutas dentro de CLASSPATH, Java
no examinar ese directorio.
130 Piensa en Java
Si ahora creamos un programa que usa Cookie:
11 : access/Dinner.java
II Usa la biblioteca.
import access.dessert. * ;
public class Dinner {
public static void main{String[] args)
Cookie x = new Cookie();
II! x.bite(); II No puede acceder
1* Output:
Cookie constructor
* jjj ,-
podemos crear un objeto Cookie, dado que su constructor es public y que la clase tambin es public (ms adelante, profun-
dizaremos ms en el concepto de clase pubHc). Sin embargo, el miembro bite() es inaccesible desde de Dinner.java ya que
bite() slo proporciona acceso dentro del paquete dessert, as que el compilador nos impedir utilizarlo.
El paquete predeterminado
Puede que le sorprenda descubrir que el siguiente cdigo s que puede compilarse, a pesar de que parece que no cumple con
las reglas:
11: access/Cake.java
II Accede a una clase en una unidad de compilacin separada.
class Cake {
public static void main (String [] args) {
Pie x = new Pie {) i
x. f () ;
1* Output:
Pie. f ()
*j jj,-
En un segundo archivo del mismo directorio tenemos:
11: access/Pie.java
II La otra clase.
class Pie {
void f {) { System.out.println{"Pie.f{) 11 ) i }
} jjj,-
Inicialmente, cabra pensar que estos dos archivos no tienen nada que ver entre s, a pesar de lo cual Cake es capaz de crear
un objeto Pie y de invocar su mtodo ro (observe que debe tener ' .' en su variable CLASSPATH para que los archivos se
compi len). Lo que parecera lgico es que Pie y f() tengan acceso de paquete y no estn, por tanto, disponibles para Cake.
Es verdad que tienen acceso de paquete, esa parte de la suposicin es correcta. Pero la razn por la que estn disponibles en
Cake.java es porque se encuentran en el mismo directorio y no tienen ningn nombre explcito de paquete. Java trata los
archivos de este tipo como si fueran implcitamente parte del "paquete predetem1inado" de ese directorio, y por tanto pro
porciona acceso de paquete a todos los restantes archivos situados en ese directorio.
private: ino lo toque!
La palabra clave private significa que nadie puede acceder a ese miembro salvo la propia clase que lo contiene, utili zando
para el acceso los propios mtodos de la clase. El resto de las clases del mismo paquete no puede acceder a los miembros
privados, as que el efecto resultante es como si estuviramos protegiendo a la clase contra nosotros mismos. Por otro lado.
resulta bastante comn que un paquete sea creado por varias personas que colaboran entre s, por lo que private pennite
modificar libremente ese miembro sin preocuparse de si afectar a otras clases del mismo paquete.
6 Control de acceso 131
El acceso de paquete predetenninado proporciona a menudo un nivel adecuado de ocultacin; recuerde que un miembro con
acceso de paquete resulta inaccesible para todos los programas cliente que utilicen esa clase. Esto resulta bastante conve-
niente, ya que el acceso predetenninado es el que nomlalmente se utiliza (y el que se obtiene si nos olvidamos de aadir
especificadores de control de acceso). Por tanto, 10 que normalmente haremos ser pensar qu miembros queremos definir
explcitamente como pblicos para que los utilicen los programas cliente; como resultado, uno tendera a pensar que la pala-
bra clave private no se utiliza muy a menudo, ya que se pueden realizar los diseos si n ella. Sin embargo, la realidad es que
el uso coherente de priva te tiene una gran importancia, especialmente en el caso de la programacin multihebra (como vere-
moS en el Captulo 21, Concurrencia).
He aqu un ejemplo del uso de priva te:
11 : access/IceCream.java
II Ilustra la palabra clave "private".
class Sundae {
private Sundae() {}
static Sundae makeASundae()
return new Sundae{)
public class IceCream {
public static void main (String[) args )
II ! Sundae x = new Sundae()
Sundae x = Sundae.makeASundae();
Este ejemplo nos pennite ver un caso en el que private resulta muy til: queremos tener control sobre el modo en que se
crea un objeto y evitar que nadie pueda acceder directamente a un constructor concreto (o a todos eUos). En el ejemplo ante-
rior, no podemos crear un objeto Sundae a travs de su constructor; en lugar de ello, tenemos que invocar el mtodo
makeASundae( ) para que se encargue de hacerlo por nosotros
4
Cualquier mtodo del que estemos seguros de que slo acta como mtodo "auxiliar" de la clase puede ser defmido como
privado para garantizar que no lo utilicemos accidentalmente en ningn otro lugar del paquete, lo que nos impedira modifi-
car o elimi nar el mtodo. Definir un mtodo como privado nos garantiza que podamos modificarlo libremente en el futuro.
Lo mismo sucede para los campos privados definidos dentro de una clase. A menos que tengamos que exponer la imple-
mentacin subyacente (lo cual es bastante menos habinlal de lo que podra pensarse), conviene definir todos los campos
como privados. Sin embargo, el hecho de que una referencia a un objeto sea de tipo private en una clase no quiere decir
que algn otro objeto no pueda tener una referencia de tipo publie al mismo objeto (consuhe los suplementos en lnea del
libro para conocer ms detalles acerca de los problemas de los alias).
protected: acceso de herencia
Para poder comprender el especificador de acceso protected, es necesario que demos un salto hacia adelante. En primer
lugar, debemos tener presente que no es necesario comprender esta seccin para poder continuar leyendo el libro hasta lle-
gar al captulo dedicado a la herencia (el Captulo 7, Relllilizacin de clases). Pero para ser exhaustivos, hemos incluido aqu
una breve descripcin y un ejemplo de uso de proleeted.
La palabra clave protected trata con un concepto denominado herencia, que toma una clase existente (a la que denomina-
remos clase base) y aade nuevos miembros a esa clase sin tocar la clase existente. Tambin se puede modificar el compor-
tamiento de los miembros existentes en la clase. Para heredar de una clase, tenemos que especificar que nuestra nueva clase
ampla (extends) una clase existente, como en la siguiente lnea:
class Foo extends Bar {
4 Existe otro efecto en este caso: puesto que el nico constructor definido es el predetenninado y ste se ha definido como pl"ivate, se impedini que nadie
herede esta clase (lo cual es un tema del que hablaremos ms adelante).
132 Piensa en Java
El resto de la definicin de la clase tiene el aspecto habitual.
Si creamos un nuevo paquete y heredamos de una clase situada en otro paquete. los nicos miembros a los que tendremos
acceso son los miembros pblicos del paquete original (por supuesto, si la herencia se realiza dentro del mismo paquete. se
podrn manipular todos los miembros que tengan acceso de paquete). En ocasiones, el creador de la clase base puede tomar
un miembro concreto y garantizar el acceso a las clases derivadas. pero no al mundo en generaL Eso es precisamente lo que
hace la palabra clave protected. Esta palabra clave tambin proporciona acceso de paquete, es decir, las restantes clases del
mismo paquet e podrn acceder a los elementos protegidos.
Si volvemos al archivo Cookic.java. la siguiente clase no puede invocar el miembro bite( ) que tiene acceso de paquete:
// : access/ChocolateChip.java
/1 No se puede usar un miembro con acceso de paquete desde otro paquete.
import access.dessert.*;
public class ChocolateChip extends Cookie (
public ChocolateChip () {
System. out .println ( "ChocolateChip constructor");
}
public void chomp()
lIt bite() 1/ No se puede acceder a bite
public static void main(String[] args )
ChocolateChip x = new ChocolateChip{);
x. chomp ()
1* Output:
Cookie constructor
ChocolateChip constructor
* /// ,-
Uno de los aspectos interesantes de la herencia es que, si existe un mtodo bite( ) en la clase Cookie, tambin existir en
todas las clases que hereden de Cooki e. Pero como bite() tiene acceso de paquete y est situado en un paquete di stinto, no
estar disponible para nosotros en el nuevo paquete. Por supuesto, podramos hacer que ese mtodo fuera pblico, pero
entonces todo el mundo tendra acceso y puede que no sea eso lo que queramos. Si modificamos la clase Cookie de la forma
siguiente:
1/ : access/cookie2/Cookie.java
package access.cookie2
public class Cookie {
public Cookie () {
System.out.println("Cookie const r uctor " )
protected void bi te () {
System.out.println{"bite") ;
ahora bite( ) estar disponible para toda aquell a clase que herede de Cookic:
11: access /ChocolateChip2.java
import access.cookie2.*;
public class ChocolateChip2 extends Cookie {
public ChocolateChip2 () {
System. out. println ( "ChocolateChip2 constructor")
}
public void chomp () { bite (); } // Mtodo protegido
public static void main(String[] args) {
ChocolateChip2 x = new ChocolateChip2() i
x.chomp() i
/ * Output :
cookie constructor
ChocolateChip2 constructor
bite
' /11 ,-
Observe que, aunque bite( ) tambin tiene acceso de paquete, no es de tipo publico
6 Control de acceso 133
Ejercici o 4:
Ejercici o 5:
(2) Demuestre que los mtodos protegidos (protected) tienen acceso de paquete pero no son pblicos.
(2) Cree una clase con campos y mtodos de tipo publi c, private, protected y con acceso de paquete. Cree
un objeto de esa clase y vea los tipos de mensajes de compi lacin que se obtienen cuando se intenta acce-
der a todos los miembros de la clase. Tenga en cuenta que las clases que se encuentran en el mismo direc-
torio faonan parte del paquete "predetenrunado",
Ejercici o 6: (1) Cree una clase con datos protegidos. Cree una segunda clase en el mismo archivo con un mtodo que
manipule los datos protegidos de la primera clase.
Interfaz e implementacin
El mecanismo de control de acceso se denomina a menudo ocultacin de la implementacin. El envolver los datos y los
mtodos dentro de la clase, en combinacin con el mecanismo de ocultacin de la implementacin se denomina a menudo
encapsulacin.
5
El resultado es un tipo de datos con una serie de caractersticas y comportamientos.
El mecanismo de control de acceso levanta una serie de fronteras dentro de un tipo de datos por dos razones importantes.
La primera es establecer qu es lo que los programas cliente pueden usar o no. Podemos entonces disear como queramos
los mecanismos internos dentro de la clase, sin preocuparnos de que los programas cliente utilicen accidentalmente esos
mecanismos internos como parte de la interfaz que deberan estar empleando.
Esto nos lleva directamente a la segunda de las razones, que consiste en separar la interfaz de la implementacin. Si esa
clase se utiliza dentro de un conjunto de programas, pero los programas cliente tan slo pueden enviar mensajes a la inter-
faz pblica, tendremos libertad para modificar cualquier cosa que no sea pblica (es decir, los miembros con acceso de
paquete, protegidos o privados) sin miedo de que el cdigo cliente deje de funcionar.
Para que las cosas sean ms claras, resulta conveniente a la hora de crear las clases, siul8r los miembros pblicos al princi-
pio, seguidos de los miembros protegidos, los miembros con acceso de paquete y los miembros privados. La ventaja es que
el usuario de la clase puede comenzar a leer desde el principio y ver en primer lugar lo que para l es lo ms importante (los
miembros de tipo public, que son aquellos a los que podr accederse desde fuera del archivo), y dejar de leer en cuanto
encuentre los miembros no pblicos, que f0n11an parte de la implementacin interna.
11 : access/OrganizedByAccess.java
public class OrganizedByAccess {
public void publ () { / * * /
public void pub2 () { / . .. 1
public void pub3 () { l ' ... 1
private void privl () { / * * /
private void priv2 () { 1* * /
private void priv3 () { /* */
private int i
/1
/! / ,-
5 Sin embargo, mucha gente utiliza la palabra encapsulacin para referirse en exclusiva a la ocultacin de la implementacin.
134 Piensa en Java
Esto slo facilita parcialmente la lectura, porque la interfaz y la implementacin siguen estando mezcladas. En otras pa-
labras, el lector seguir pudiendo ver el cdigo fuente (la implementacin), porque est incluido en la clase. Adems, el sis-
tema de documentacin basado en comentarios soportado por Javadoc no concede demasiada importancia a la legibilidad
del cdigo por parte de los programadores de clientes. El mostrar la interfaz al consumidor de una clase es, en realidad, tra-
bajo del explorador de clases, que es una herramienta que se encarga de exarnjnar todas las clases disponibles y mostramos
lo que se puede hacer con ellas (es decir, qu miembros estn disponibles) en una forma apropiada. En Java, visualizar la
documentacin del IDK con un explorador web nos proporciona el mismo resultado que si utilizramos un explorador de
clases.
Acceso de clase
En Java, los especificadores de acceso tambin pueden emplearse para detennjnar qu clases de una biblioteca estarn dis-
ponibles para los usuarios de esa biblioteca. Si queremos que una cierta clase est disponible para un programa cliente, ten-
dremos que utilizar la palabra clave publi c en la definicin de la propia clase. Con esto se controla si los programas cliente
pueden siquiera crear un objeto de esa clase.
Para controlar el acceso a una clase, el especificador debe situarse delante de la palabra clave class, Podemos escribir:
pUblic class Widget {
Ahora, si el nombre de la biblioteca es access, cualquier programa cl iente podr acceder a Widget mediante la instruccin
import access.Widget
o
import access.*
Sin embargo, existen una serie de restricciones adicionales:
l . Slo puede haber una clase public por cada unidad de compilacin (archivo). La idea es que cada unidad de com-
pilacin tiene una nica interfaz pblica representada por esa clase pblica. Adems de esa clase, puede tener tan-
tas clases de soporte con acceso de paquete como deseemos. Si tenemos ms de una clase pblica dentro de una
unidad de compilacin, el compilador generar un mensaje de error.
2. El nombre de la clase publi c debe corresponderse exactamente con el nombre del archivo que contiene dicha uni-
dad de compilacin, incluyendo el uso de maysculas y minsculas. De modo que para Widget, el nombre del
archivo deber ser Widget.java y no widget.j ava ni WIDGET.java. De nuevo, obtendremos un error en tiem-
po de compi lacin si los nombres no concuerdan.
3. Resulta posible, aunque no muy normal , tener una unidad de compi lacin sin ninguna clase publi co En este caso,
podemos dar al archivo el nombre que queramos (aunque si lo denominamos de forma arbitraria confundiremos
a las personas que tengan que leer y mantener el cdigo).
Qu sucede si tenemos una clase dentro de access que slo estamos empleando para llevar a cabo las tareas realizadas por
Wi dget o alguna otra clase de tipo public de access? Puede que no queramos molestamos en crear la documentacin para
el programador de clientes y que pensemos que algo ms adelante quiz queramos modificar las cosas completamente, eli-
minando esa clase y sustituyndola por otra. Para poder disponer de esta flexibilidad, necesitamos garantizar que ningn
programa cliente dependa de nuestros detalles concretos de implementacin que estn ocultos dentro de access. Para con-
seguir esto, basta con no incluir la palabra clave public en la clase, en cuyo caso tendr acceso de paquete (dicha clase slo
podr ser usada dentro de ese paquete).
Ejercicio 7: (1) Cree una biblioteca usando los fragmentos de cdigo con los que hemos descrito access y Widget.
Cree un objeto Widget dentro de una clase que no forme parte del paquete access.
Cuando creamos una clase con acceso de paquete, sigue siendo conveniente definir los campos de esa clase como private
(siempre deben hacerse los campos lo ms privados posible), pero generalmente resulta razonable dar a los mtodos el
mismo tipo de acceso que a la clase (acceso de paquete). Puesto que una clase con acceso de paquete slo se utiliza normal-
mente dentro del paquete, slo har falta defi nir como pblicos los mtodos de esa clase si nos vemos obligados a ell o; ade-
ms, en esos casos, el compi lador ya se encargar de informamos.
6 Control de acceso 135
Observe que una clase no puede ser private (ya que eso haria que fuera inaccesibl e para todo el mundo salvo para la pro-
pia clase) ni protected.
6
As que slo tenemos dos opciones para especificar el acceso a una clase: acceso de paquete o
public. Si no queremos que nadi e tenga acceso a la clase. podemos definir todos los constructores como privados, impidien-
do que nadie cree un objeto de dicha clase salvo nosotros, que podremos hacerlo dentro de un miembro de tipo static de esa
clase. He aqu un ejemplo:
ji : access/Lunch.java
/1 Ilustra los especificadores de acceso a clases. Define una
/1 clase como privada con constructores privados:
class Soupl {
pri vate Soupl () ()
JI (1) Permite la creacin a travs de un mtodo esttico:
public static Soupl makeSoup()
return new Soupl();
class Soup2 {
private Soup2() ()
/1 (2) Crea un objeto esttico y devuelve una referencia
JI cuando se solicita. (El patrn "Singleton" ) :
private static Soup2 psi = new Soup2{) i
public static Soup2 access {} {
return psl
public void f () {}
1/ Slo se permite una clase pblica por archivo:
public class Lunch {
void testPrivate () {
II No se puede hacer! Constructor privado:
II! Soupl soup = new Soupl() ;
void testStatic()
Soupl soup = Soupl.makeSoup();
void testSingleton{)
Soup2. access () . f () ;
}
lit >
Hasta ahora, la mayora de los mtodos devolvan void o un tipo primiti vo, por lo que la definicin:
public static Soupl makeSoup()
return new Soupl{) ;
puede parecer algo confusa a primera vista. La palabra Soupl antes del nombre del mtodo (makeSoup) dice lo que el
mtodo devuelve. Hasta ahora en el libro, esta palabra normalmente era void, lo que significa que no devuel ve nada. Pero
tambin podemos devolver una referencia a un objeto, que es lo que estamos haciendo aqu. Este mtodo devuelve una refe-
rencia a un objeto de la clase Soupl.
Las clases Soupl y Soup2 muestran cmo impedir la creacin directa de objetos de una clase definiendo todos los cons-
tructores como privados. Recuerde que, si no creamos explcitamente al menos un constructor, se crear automticamente
el constructor predeterminado (un constructor sin argwnentos). Escribiendo el constructor predetenninado, garantizamos
6 En realidad, una e/ase nlerna puede ser privada o protegida, pero se trata de un caso especial. Hablaremos de ello en el Captulo 10, Clases internas.
136 Piensa en Java
que no sea escrito automticamente. Definindolo como privado nadie podr crear un objeto de esa clase. Pero entonces,
cmo podr alguien lIsar esta clase? En el ejemplo anterior se muestran dos opciones. En Soup 1. se crea un mtodo static
que crea un nuevo objeto SoupJ y devuelve una referencia al mismo. Esto puede ser ti l si queremos realizar algunas ope-
raciones adicionales con el objeto Soupt antes de devol verlo, o si queremos ll evar la cuenta de cuntos objetos Soupl se
han creado (por ejemplo. para restringir el nmero total de objetos).
Soup2 utiliza lo que se denomina un pmrn de disei)o, de lo que se habla en Thinking in Pallerns (with Java) en
wl1'H'.Minc/View.nef. Este patrn concreto se denomina Solitario (s; ngleron) , porque slo pennite crear Wl nico objeto. El
objeto de la clase Soup2 se crea como un miembro static prvate de Soup2, por lo que existe un objeto y slo uno, y no se
puede acceder a l salvo a rravs del mrodo pblico access( ).
Como hemos mencionado anterionnente, si no utili zamos un especificador de acceso para una clase, sta tendr acceso de
paquete. Esto significa que cualquier otra clase del paquete podr crear un objeto de esa clase, pero no se podrn crear obje-
tos de esa clase desde fuera del paquete (recuerde que todos los archivos de un mismo di rectori o que no tengan declaracio-
nes package expl citas fonnan parte, implcitamente, del paquete predetenni nado de dicho directorio). Sin embargo, si un
miembro esttico de esa clase es de tipo publie, el programa cliente podr seguir accediendo a ese miembro esttico, an
cuando no podr crear un objeto de esa clase.
Ejercicio 8:
Ejercicio 9:
Resumen
(4) Siguiendo la fonna del ejempl o Lunch.java. cree una clase denominada ConnectionManager que
gestione una matriz fija de objetos Conncetion. El programa cli ente no debe poder crear explcitamente
objetos Connceton, sino que slo debe poder obtenerlos a travs de un mtodo estt ico de
ConnectionManager. Cuando ConnectionManager se quede sin objetos, devolver una referencia oul!.
Pmebe las clases con un programa main() .
(2) Cree el sigui ente archivo en el directorio aeccss/loeal (denrro de su mta CLASSPATH):
11 access/local / PackagedClass.java
package access.local
class PackagedClass {
public PackagedClass()
System.out.println("Creating a packaged class") i
A continuacin cree el siguiente archivo en un directorio distinto de aceess/local :
11 access/foreign/Foreign.java
package access.foreign
import access.local.*
public class Foreign {
public static void main(String[] args) {
PackagedClass pe = new PackagedClass()
Explique por qu el compilador crea un error. Se resolveria el error si la clase Forcign fuera parte del
paquete access. local?
En cualquier relacin, resulta importante definir una serie de fronteras que sean respetadas por todos los participantes.
Cuando se crea una biblioteca. se establece una relacin con el usuario de esa biblioteca (el programador de cli entes), que
es un programador como nosotros, aunque lo que hace es utili zar la biblioteca para construir una aplicacin u otra bibliote-
ca de mayor tamai'io.
Sin una serie de reglas, los programas cliente podran hacer lo que quisieran con lodos los miembros de una clase, an cuan
do nosotros prefiriramos que no manipularan algunos de los miembros. Todo estara expuesto a ojos de todo el mundo.
6 Conlrol de acceso 137
En este captulo hemos visto cmo se construyen bibliotecas de clases: en primer lugar, la fonna en que se puede empaque-
tar un grupo de clases en una biblioteca y. en segundo lugar, la fanna en que las clases pueden controlar el acceso a sus
miembros.
Las esti maciones indican que los proyectos de programacin en e empiezan a fallar en algn punto entre las 50.000 y
100.000 lneas de cdigo, porque e tiene un nico espacio de nombres yesos nombres empiezan a colisionar, obligando a
realizar un esfuerzo adicional de gestin. En Java. la palabra clave package, el esquema de denominacin de paquetes y la
palabra clave import nos proporcionan un control completo sobre los nombres, por lo que puede evitarse fcilmente el pro-
blema de la colisin de nombres.
Existen dos razones para controlar el acceso a los miembros. La primera consiste en evirar que los usuarios puedan hus-
mear en aquellas partes del cdigo que no deben lOcar. Esas partes son necesarias para la operacin interna de la clase, pero
no fonnan parte de la interfaz que el programa cliente necesita. Por tanto, definir los mtodos y los campos como privados
constituye un servicio para los programadores de clientes, porque as stos pueden ver fcilmente qu cosas son impoI1an-
tes para ellos y qu cosas pueden ignorar. Simplifica su comprensin de la clase.
La segunda razn, todava ms importante. para definir mecanismos de control de acceso consiste en pennitir al diseador
de bibliotecas modificar el funcionamiento interno de una clase sin preocuparse de si ello puede afectar a los programas
cliente. Por ejemplo. podemos disear al principio ulla clase de una cierta manera y luego descubrir que una cierta rees-
tructuracin del cdigo podra aumentar enomlemente la velocidad. Si la interfaz y la implementacin estn claramente pro-
tegidas, podemos realizar las modificaciones sin forzar a los programadores de clientes a reescribir su cdigo. Los mecanis-
1110S de control de acceso garantizan que ningn programa cliente dependa de la implementacin subyacente de una clase.
Cuando di sponemos de la capacidad de modificar la implementacin subyacente, no slo tenemos la libertad de mejorar
nuestro diselio, sino tambin la libertad de cometer errores. lndependicntemente del cuidado que pongamos en la planifica-
cin y el diseo, los errores son inevitables. Saber que resulta relativamente seguro cometer esos errores significa que ten-
dremos menos miedo a experimentar, que aprenderemos ms rpidamente y que finalizaremos nuestro proyecto con mucha
ms antelacin.
La interfaz pblica de una clase es lo que el usuario puede ver, as que constituye la parte de la clase que ms importancia
tiene que definamos "correctamente" durante la fase de anlisis del diseo. Pero incluso aqu existen posibilidades de modi-
ficacin. Si no definimos correctamente la interfaz a la primera podemos aadir ms mlOdos siempre y cuando no elimi-
nemos ningn mtodo que los programas cliente ya hayan utilizado en su cdigo.
Observe que el mecanismo de control de acceso se centra en una relacin (yen un tipo de comunicacin) entre un creador
de bibliotecas y los clientes externos de esas bibliotecas. Existen muchas situaciones en las que esta relacin no est presen-
te. Por ejemplo, imagine que todo el cdigo que escribimos es para nosotros mismos o que estamos trabajando en estrecha
relacin con un pequeo grupo de personas y que todo lo que se escriba se va a incluir en un mismo paquete. En esas si-
tuaciones, el lipa de comunicacin es diferente, y la rgida adhesin a una serie de reglas de acceso puede que no sea la solu-
cin ptima. En esos casos, el acceso predetenninado (de paquete) puede que sea perfectamente adecuado.
Las soluciones a los ejercicios seleccionados pueden encontrarse en el documento electrnico The Thillkillg ill JUnI AI/I/Ulafed SO{I/fioll CI/ide. disponible
para la \"cnta en \\'\\'II'./LIil/dl'iell."ef.
Reutilizacin
de clases
Una de las caractersticas ms atractivas de Java es la posibilidad de reutili zar el cdigo.
Pero, para ser verdaderamente revolucionario es necesario ser capaz de hacer mucho
ms que simplemente copiar el cdigo y modificarlo.
sta es la tcnica que se ha estado utilizando en lenguajes procedimental es como e, y no ha funcionado muy bien. Como
todo lo dems en Java, la solucin que este lenguaje proporciona gira alrededor del concepto de clase. Reutilizando el cdi-
go creamos nuevas clases, pero en lugar de crearlo partiendo de cero, usamos las clases existentes que alguien haya cons-
truido y depurado anteri omlcnte.
El truco estri ba en utilizar las clases sin modificar el cdigo existente. En este captul o vamos ver dos fonnas de llevar esto
a cabo. La primera de ellas es bastante simpl e. Nos limitamos a crear objetos de una clase ya existente dentro de una nueva
clase. Esto se denomina composicin, porque la nueva clase est compuesta de objetos de otras clases existentes. Con esto,
simplemente estamos reutili zando la funcionalidad del cdi go, no su fonna.
La segunda tcnica es ms sutil. Se basa en crear una nueva clase como un tipo de una clase existente. Literalmente 10 que
hacemos es tomar la fonna de la clase existente y aadirl a cdigo sin modificarla. Esta tcnica se denomina herencia, y el
compi lador se encarga de realizar la mayor parte del trabajo. La herencia es una de las piedras angulares de la programa-
cin orientada a objetos y tiene una serie de implicaciones adicionales que analizaremos en el Captulo 8, Poliformismo.
Resulta que buena parte de la sintaxi s y el comportamiento son similares tanto en la composicin como en la herencia (lo
que tiene bastante sentido, ya que ambas son fonnas de construir nuevos tipos a partir de otros tipos existentes). En este
captulo, vamos a presentar ambos mecani smos de reutilizacin de cdigo.
Sintaxis de la composicin
Hemos utilizado el mecanismo de composicin de fonna bastante frecuente en el libro hasta este momento. Simplemente,
basta con colocar referencias a objetos dentro de las clases. Por ejemplo, suponga que queremos construir un objeto que
almacene varios objetos String, un par de primitivas y otro objeto de otra clase. Para los objetos no primitivos, lo que hace-
mos es colocar referencias dentro de la nueva clase, pero sin embargo las primiti vas se definen directamente:
11 : reusing/SprinklerSystem.java
II Composicin para la reutilizacin de cdigo.
class WaterSource {
private String S;
WaterSource () {
System. out .println ("WaterSource () ,, ) ;
s = "Constructed";
public String toString () { return s; }
public class SprinklerSystem {
private String valvel, valve2, valve3, valve4
140 Piensa en Java
private WaterSource source
pri vate int i i
private float fi
public String toString ()
return
new WaterSource ()
IIvalvel
II valve2
"val ve3
"valve4
+ valvel + +
+ valve2 + +
+ valve3 + +
+ valve4 + " \ n" +
"i = " + i + " " + "f = " + f + " " +
11 source = 11 + source
public static void main (String[] args ) {
SprinklerSystem sprinklers = new SprinklerSystem()
System.out.println ( sprinklers )
1* Output:
WaterSource ( )
valvel = null valve2
i = O f = 0.0 source
* / //,-
null valve3 null valve4 null
Constructed
Uno de los mtodos definidos en ambas clases es especial : toString(). Todo objeto no primitivo tiene un mtodo toString()
que se invoca en aquellas situaciones especiales en las que el compilador quiere una cadena de caracteres String pero lo que
tiene es un objeto. As, en la expresin contenida en SprinklerSystem.toString():
11 source = 11 + source
el compilador ve que estamos intentando aadir un obj eto String ("source = 11) a un objeto WaterSource. Puesto que slo
podemos ' "aadir" un obj eto String a otro objeto String, el compilador dice "voy a convertir source en un objeto String
in vocando toString() !". Despus de hacer esto, puede combinar las dos cadenas de caracteres y pasar el objeto String resul-
tante a System.out.println() (o, de fonna equi valente, a los mtodos estticos print() y printnb() que hemos definido en
este libro). Siempre que queramos permitir este tipo de comportami ento dentro de una clase que creemos, nos bastar con
escribir un mtodo toString( ).
Las primiti vas que son campos de una clase se ini ciali zan automt icamente con el valor cero, como hemos indi cado en el
Captulo 2, Todo es un objeto. Pero las referencias a objetos se inicializan con el valor null , y si tratamos de invocar mto-
dos para cualquiera de ellas obtendremos una excepcin (un eITor en tiempo de ejecucin). Afortunadamente, podemos
imprimir una referencia null si n que se genere una excepcin.
Tiene bastante sentido que el compilador no cree un obj eto predetenninado para todas las referencias, porque eso obligara
a un gasto adicional de recursos completamente innecesarios en muchos casos. Si queremos inicializar las referencias, pode-
mos hacerlo de las sigui entes fonnas:
J. En el lugar en el que los objetos se definan. Esto significa que estarn siempre inici ali zados antes de que se invo-
que el constructor.
2. En el constructor correspondi ente a esa cl ase.
3. Justo antes de donde se necesite utili zar el objeto. Esto se suele denominar inicializacin diferida. Puede reducir
el gasto adicional de procesami ento en aquellas situaci ones en las que la creacin de los objetos resulte muy cara
y no sea necesario crear el objeto todas las veces.
4. Utili zando la tcnica de iniciaUzacin de instancia.
El siguiente ejemplo ilustra las cuatro tcnicas:
11: reusing/ Bath.java
I I Inicializacin mediante constructor con composicin.
import static net.mindview.util . Print .*
class Soap {
private String Si
Soap () (
print ("Soap()") i
s = "Constructed";
public String toString () { return s; }
public class Bath {
private String /1 Inicializacin en el punto de definicin:
51 :: I'Happy" ,
s2 = 11 Happy 11 ,
53, 54;
private Soap castille;
private int ii
private float toy;
public 8ath 1) {
print (U Inside Bath () " ) i
53 = "Joy";
toy = 3 .14f;
castille = new Soap() i
// Inicializacin de instancia:
{i= 47;}
public String toString () {
if (54 == nulll / / Inicializacin diferida:
54 = IIJoy";
return
"sl + 51 + "\n" +
"52 + 52 + "\n" +
IIs3
+ 53 + "\n" +
"54 + 54 +
"\nn
+
"i = 11 + i + "\n" +
"toy = " + toy + " \n" +
"castille = " + castille;
public static void main(String[] argsl {
Bath b = new Bath();
print lb) ;
/* Output:
Inside Bath ()
Soap ()
51 Happy
s2 Happy
53 Joy
54 Joy
i :::: 47
toy = 3.14
castille
,///,-
Constructed
7 Reutilizacin de clases 141
Observe que en el constructor 8ath, se ejecuta una instruccin antes de que tenga lugar ninguna de las inicializaciones.
Cuando no se reali za la iniciaLizacin en el punto de defi nicin, sigue si n haber ninguna garanta de que se vaya a reali zar
la inicializacin antes de enviar un mensaje a una referencia al objeto, salvo la inevi table excepcin en tiempo de ejecucin.
Cuando se invoca toString(), asigna un valor a s4 de modo que todos los campos estarn apropiadamente inicializados en
el momento de usarlos.
Ejercicio 1: (2) Cree una clase simple. Dentro de una segunda clase, defina una referencia a un objeto de la segunda
clase. Utili ce el mecanismo de ini cializacin diferida para instanciar este objeto.
142 Piensa en Java
Sintaxis de la herencia
La herencia es una parte esencial de Java (y lOdos los lenguajes orientados a objetos). En la prctica. siempre estamos uti-
li zando la herencia cada vez que creamos UDa clase, porque a menos que heredemos explcitamente de alguna otra clase.
estaremos heredando implcitamente de la clase raz estndar Object de Java.
La si ntaxi s de composicin es obvia, pero la herencia utiliza una sintaxis especial. Cuando heredamos, lo que hacemos es
decir "esta nueva clase es similar a esa antigua clase". Especificamos esto en el cdigo situado antes de la llave de abertu-
ra del cuerpo de la clase. utilizando la palabra clave extends seguida del nombre de la clase base. Cuando hacemos esto,
automticamente obtenemos todos los campos y mtodos de la clase base. He aqu una clase:
JI: reusing/Detergent.java
JI Sintaxis y propiedades de la herencia.
impore static net.mindview.util.Print.*
class Cleanser {
private String s = "Cleanser";
public void append(String al { s += a }
public void dilute{) ( append(U dilute() ");
public void apply () ( append (" apply () " ); }
public void scrub () ( append (" scrub () " ) ; }
public String toString () { return Si}
public static void main(String[] args)
Cleanser x = new Cleanser();
x . dilute(); x.apply(); x.scrub();
print (x l;
public class Detergent extends Cleanser {
// Cambio de un mtodo :
public void scrub () {
append(" Detergent.scrub()");
super.scrub() // Invocar version de la clase base
// Aadir mtodos a la interfaz:
public void foam() ( appendl" foam() ");
// Probar la nueva clase:
public static void main (String [] args) {
Detergent x = new Detergent();
x.dilute() ;
x.apply() ;
x.scrub(l;
x.foam();
print (x l;
print ("Testing base class:!I l ;
Cleanser.main(argsl;
/ * Output:
Cleanser dilute () apply () Detergent . scrub () scrub () f oam()
Testing base class :
Cleanser dilute () apply () scrub()
* /// ,-
Esto nos pem1ite ilustrar una serie de caractersticas. En primer lugar, en el mtodo Cleanser append(). se concatenan cade-
nas de caracteres con S utilizando el operador +=. que es uno de los operadores, junto con '+" que los diseadores de Java
"han sobrecargado" para que funcionen con cadenas de caracteres.
En segundo lugar. tanto eleanser como Detergent contienen un mtodo maine ). Podemos crear un mtodo maine ) para
cada una de nuestras clases: esta tcnica de colocar un mtodo main() en cada clase pcm1ite probar fcilmente cada una de
7 Reutilizacin de clases 143
ellas. V no es necesario el iminar el mtodo maine ) cuando hayamos tenninado: podemos dejarlo para las pruebas poste-
riores.
An cuando tengamos muchas clases en un programa, slo se invocar el mtodo main() de la clase especificada en la lnea
de comandos. Por tanto, en este caso, cuando escribimos java Detergent, se invocar Detergent.main( ). Pero tambin
podemos escribi r java Cleanser para invocar a Cleanser.main( ), an cuando Cleanser no sea una clase pblica. Incluso
aunque una clase tenga acceso de paquete. si el mtodo main() es pblico, tambin se podr acceder a l.
Aqu. podemos ver que Detergcnt.main() llama a Cleanser.main( ) explcitamente, pasndole los mismos argumentos de
la lnea de comandos (sin embargo. podramos pasarle cualquier matriz de objetos String).
Es importante que todos los mtodos de Cleanser sean pblicos. Recuerde que, si no se indica ningn especificador de acce-
so. los miembros adoptarn de f0n11a predetenninada el acceso de paquete, lo que slo pennite el acceso a los otros miem-
bros del paquete. Por tanto. dentro de este paquete. cualquiera podra usar esos mtodos si no hubiera ningn especificador
de acceso. Detergent. por ejemplo. no tendra ningn problema. Sin embargo. si alguna clase de algn otro paquete fuera a
heredar de Cleanser, slo podra acceder a los miembros de tipo public. As que, para permitir la herencia, como regla gene-
ral deberemos definir todos los campos eomo private y todos los mtodos como public (los miembros de tipo protecled
tambin pemliten el acceso por parte de las clases derivadas; analizaremos este tema ms adelante). Por supuesto. en algu-
nos casos particulares ser necesario hacer excepciones. pero esta directriz suele resultar bastante til.
Cleanser dispone de una serie de mtodos en su interraz: append( ). dilute( ). apply( ). serub( ) y toString( ). Puesto que
Detergent deril'a de Cleanser (gracias a la palabra clave extends). automticamente obtendr todos estos mtodos como
parte de su interfaz, an cuando no los veamos explcitamente definidos en Detergent. Por tanto. podemos considerar la
herencia como un modo de reutilizar la clase.
Como podemos ver en scrub( ), es posible tomar un mtodo que haya sido definido en la clase base y modificarlo. En este
caso, puede tambin que queramos invocar el mtodo de la clase base desde dentro de la nueva versin. Pero dentro de
scrub( ), no podemos simplemente invocar a scrub( ). ya que eso producira una llamada recursiva. que no es exactamen-
te 10 que queremos. Para resolver este problema, la palabra clave super de Java hace referencia a la "supcrclase" de la que
ha heredado la clase actual. Por tanto. la expresin supcr.scrub( ) invoca a la versin de la clase base del mtodo scrub( ).
Cuando se hereda, no estamos limirados a utilizar los mtodos de la clase base. Tambin podemos aadir nuevos mtodos
a la clase derivada, de la misma forma que los aadiramos a otra clase: definindolos. El mtodo foam( ) es un ejemplo de
esto.
En Detergent.main( ) podemos ver que. para un objeto Detergent, podemos invocar todos los mtodos disponibles en
CIeanseO' osi C0l110 en Detergent (es decir, foam()).
Ejemplo 2: (2) Cree una nueva clase que herede de la clase Detergent. Sustinlya el mtodo scrub() y aada un nuevo
mtodo denominado stcrilize( ).
Inicializacin de la clase base
Puesto que ahora tenemos dos clases (la clase base y la clase derivada) en lugar de una, puede resultar un poco confuso tra-
tar de imaginar cul es el objeto resultante generado por una clase derivada. Desde fuera, parece como si la nueva clase
tuviera la misma interfaz que la clase base y. quiz algunos mtodos y campos adicionales. Pero el mecanismo de herencia
no se limita a copiar la interfaz de la clase base. Cuando se crea un objeto de la clase derivada, ste contiene en su interior
un subobjeto de la clase base. Este subobjeto es idntico a 10 que tendramos si hubiramos creado directamente un objeto
de la clase base. Lo que sucede, simplemente. es que. visto desde el exterior, el subobjeto de la clase base est envuelto por
el objeto de la clase derivada.
Por supuesto. es esencial que el subobjeto de la clase base se inicialice correctamente, y slo hay una forma de garantizar
esto: realizar la inicializacin en el constructor invocando al constructor de la clase base, que es quien tiene todos los cono-
cimientos y todos los privilegios para llevar a cabo adecuadamente la inicializacin de la clase base. Java inserta automti -
camente llamadas al constructor de la clase base dentro del constructor de la clase derivada. El siguiente ejempl o muestra
este mecanismo en accin con tres niveles de herencia:
11 : reusing/ Cartoon.java
11 Llamadas a constructores durante la herencia.
144 Piensa en Java
import static net.mindview.util.Print.*
class Art
Art () { print ( "Art constructor" ) ; }
class Drawing extends Art {
Drawing () { print ( "Drawing constructor ti ) i }
public class Cartoon extends Drawing {
public Cartoon () { print ( "Cartoon constructor" ) i
public static void main (String[] args ) {
Cartoon x = new Cartoon () i
1* Output:
Art constructor
Drawing constructor
Cartoon constructor
*/1/,-
Como puede ver, la construccin tiene lugar desde la base hacia "afuera", por lo que la clase base se inicializa antes de que
los constructores de la clase derivada puedan acceder a ella. Incluso aunque no creramos un constructor para Cartoon() ,
el compilador sintetizara un constructor predeternlinado que invocara al constructor de la clase base.
Ejercicio 3:
Ejercicio 4:
Ejercicio 5:
(2) Demuestre la afinnacin anterior.
(2) Demuestre que los constructores de la clase base (a) se invocan siempre y (b) se invocan antes que los
constructores de la clase deri vada.
(1) Cree dos clases, A y B, con constructores predeterminados (listas de argument os vacias) que impriman
un mensaje infornlando de la construccin de cada objeto. Cree una nueva clase llamada e que herede de
A, y cree un miembro de la clase B dentro de C. No cree un constructor para C. Cree un obj eto de la clase
e y observe los result ados.
Constructores con argumentos
El ejemplo anterior ti ene constructores predetenninados; es decir, que no tienen argumentos. Resulta fci l para el compila-
dor invocar estos constructores, porque no existe ninguna duda acerca de qu argumentos hay que pasar. Si no existe un
constructor predetenninado en la clase base. o si se quiere invocar un constructor de la clase base que tenga argumentos,
ser necesario escri bir explcitamente una llamada al constmctor de la clase base uti li zando la palabra clave super y la lista
de argumentos apropiada:
11: reusing/Chess.java
II Herencia, constructores y argumentos.
import static net.mindview.util.Print.*;
class Game {
Game {int i)
print ( "Game constructor" ) i
class BoardGame extends Game {
BoardGame (int i) {
super (i ) ;
print ( "BoardGame constructor" ) i
public class Chess extends BoardGame {
Chess (1 {
super(ll) ;
print ( "Chess constructor
lt
);
public stacic void main(String(] args) {
Chess x = new Chess()
/ * Output :
Game constructor
BoardGame constructor
Chess constructor
* /// >
7 Reutilizacin de clases 145
Si no se invoca el constructor de la clase base en BoardGame( ), el compilador se quejar de que no puede localizar un
constructor de la fonna Ga me( ). Adems. la llamada al constructor de la clase base debe ser la primera cosa que se haga
en el constructor de la clase derivada (el compilador se encargar de recordrselo si se olvida de ello).
Ejerci cio 6: (1) Utilizando Chess.j ava. demuestre las afimlaciones del prrafo anterior.
Ejercici o 7: (1) Modifique el Ejercicio 5 de modo que A y B tengan constructores con argumentos en lugar de cons-
tTuctores predetenninados. Escriba un constructor para e que realice toda la inicializacin dentro del cons-
tructor de C.
Ejercic io 8: (1) Cree una clase base que slo tenga un constructor no predeterminado y una clase derivada que tenga
un constructor predetenninado (sin argumentos) y otro no predetemlinado, En los constmctores de la clase
derivada, invoque al constructor de la clase base,
Ejerci c io 9: (2) Cree Wla clase denominada Root que contenga una instancia de cada una de las siguientes clases (que
tambin deber crear): Componentl, Component2 y Component3. Derive una clase Stem de Root que
tambin contenga uoa instancia de cada "componente", Todas las clases deben tener constructores prede-
terminados que impriman un mensaje relativo a la clase,
Ejerci c io 10: (1) Modifique el ejercicio anterior de modo que cada clase slo tenga constructores no predetenninados.
Delegacin
Una tercera relacin, que no est directamente soportada en Java, se denomina delegacin. Se eocuentra a caballo entre la
herencia y la composicin, por que lo que hacemos es incluir un objeto miembro en la clase que estemos construyendo
(como en la composicin), pero al mismo tiempo exponemos todos los mtodos del objeto miembro en nuestra nueva clase
(como en la herencia). Por ejemplo, una nave espacial (spaceship) necesita un mdulo de control:
11: reusing/SpaceShipControls . java
public class SpaceShipControls
void up(int ve1ocity) ()
void down (int velocity) {}
void 1eft (int ve10cityl {)
void right (int velocity) {}
void forward (int velocity) {}
void back (int veloci ty) {}
void turboBoost () {}
/// 0-
Una forma de conslruir la nave espacial consistira en emplear el mecani smo de herencia:
/1 : reusing/SpaceShip .java
public class SpaceShip extends SpaceShipControls {
private String name;
public SpaceShip(String name ) ( this.name = name;
public String toString{) { return name; }
146 Piensa en Java
pUblic static void main(String [] args) {
SpaceShip protector = new SpaceShip ("NSEA Protector"
proteceor.forward(lOO) ;
Sin embargo. un objeto SpaceShip no es realmcnle "un tipo de" objeto SpaccShipControls. an cuando podamos, por
ejemplo. "deeirle" al objeto SpaceShip que avance hacia adelante (forward( )). Result a ms preciso decir que la nave espa-
cial (el objeto SpaceShip) cOl/liel/e un mdulo de control (objeto SpaceShipControls), y que, al mismo tiempo. todos los
mtodos de SpaceShipControls deben quedar expuestos en el objeto SpaceShip. El mecani smo de delegacin pemlite
resolver este dilema:
jj : reusingjSpaceShipDelegation.java
public class SpaceShipDelegation {
private Sering name;
private SpaceShipConerols controls
new SpaceShipControls () ;
public SpaceShipDelegation (String name)
this.name = name;
jj Mtodos delegados:
public void back (int velocity)
controls .bac k (velocity ) ;
public void down(int velocity)
controls.down(velocity) ;
public void forward (int velocity)
controls .forward (ve l ocity) ;
public void left ( int velocity)
controls.left(velocity) ;
public void right(int velocity)
controls.right (velocity) ;
public void turboBoost()
controls .turboBoost () ;
public void up(int velocity)
controls.up(velocity) ;
public static void main (String (] args ) {
SpaceShipDelegation protector =
new SpaceShipDelegation ( "NSEA Protector" ) ;
protector.forward(lOO) ;
Como puede ver los mtodos se redirigen hacia el mtodo controls subyacente, y la interfaz es, por tanto, la mi sma que con
la herencia. Sin embargo, tenemos ms control con la delegaci n, ya que podemos decidir proporcionar ni camente un sub-
conjunto de los mtodos contenidos en el objeto mi embro.
Aunque el lenguaj e Java no soporta directamente la delegacin, las herramientas de desarrollo s que suelen hacerl o.
Por ejemplo, el ejemplo anterior se ha generado automti camente utili zando el entorno integrado de desarrollo JetBrains
Idea.
Ejercicio 11: (3) Modifique Detergent.java de modo que utilice el mecanismo de delegacin.
7 Reutilizacin de clases 147
Combinacin de la composicin y de la herencia
Resulta bastante comn utilizar los mecanismos de composicin y de herencia conjuntamente. El siguiente ejemplo mues-
tra la creacin de una clase ms compleja utilizando tanto la herencia como la composic in, junto con la necesaria inicial i-
zacin mediante los constructores:
/1: reusing!PlaceSetting.java
I Combinacin de la composicin y la herencia.
import static net.mindview.util.Print.*
class Plate {
Plate (int i)
print ("Plate constructor") i
class DinnerPlate extends Plate {
DinnerPlate (int i) {
super(i) ;
print ("DinnerPlate constructor");
class Utensil (
Utensil(int i)
print ("Utensil constructor");
class Spoon extends Utens il {
Spoon{int i) {
super (i)
print ("Spoon constructor " )
class Fork extends Utensil {
Fork lint il {
super(i)
print ( " Fork constructor " ) i
class Knife extends Utensil {
Knife (int i) {
super(i)
print ("Knife constructor" ) i
// Una forma cultural de hacer algo:
class Custom {
Custom ( int i) {
print ( "Custom constructor!! ) i
publ ic class PlaceSetting extends Custom {
private Spoon SPi
148 Piensa en Java
private Fork frk
private Knife kn
private DinnerPlate pI;
public PlaceSetting(int i)
super (i + 1);
sp = new Spoon(i + 2);
frk = new Fork(i + 3};
kn = new Knife(i + 4);
pI = new DinnerPlate{i + 5);
print (" PlaceSetting constructor");
public static void main(String[] args)
PlaceSetting x = new PlaceSetting(9);
/ * Output :
Custom cons tructor
Utensil constructor
Spoon constructor
Utensil constructor
Fork constructor
Utensil constructor
Knife constructor
PI ate constructor
DinnerPlate constructor
PlaceSetting constructor
* /// , -
Aunque el compi lador nos obliga a inicializar la clase base y requiere que lo hagamos al principio del constructor. no se ase-
gura de que inicialicemos los objetos miembro, as que es preciso prestar atencin a este detall e.
Resulta asombrosa la fonna tan limpia en que las clases quedan separadas. No es necesario siquiera disponer del cdigo
fuente de los mtodos para poder reut ilizar ese cdigo. Como mucho, nos basta con limitamos a importar un paquete (esto
es cierto tanto para la herencia como para la composicin).
Cmo garantizar una limpieza apropiada
Java no tiene el concepto C++ de destructor, que es un mtodo que se invoca automticamente cuando se destruye un obje-
to. La razn es, probablemente, que en Java la prctica habitual es olvidarse de los objetos en lugar de destruirlos, penni-
tiendo al depurador de memoria reclamar la memoria cuando sea necesario.
A menudo, esto resulta suficiente, pero hay veces en que la clase puede realizar determinadas actividades durante su tiem-
po de vida que obligan a realizar la limpieza. Como hemos mencionado en el Captulo 5, Inicializacin y limpieza, no pode-
mos saber cundo va a ser invocado el depurador de memoria, o ni siquiera si va a ser invocado. Por tanto, si queremos
limpiar algo concreto relacionado con una clase, deberemos escribir explicitamente un mtodo especial para hacerlo yase-
guramos de que el programador de clientes sepa que debe invocar dicho mtodo. Adems, como se descri be en el Captulo
12, Tratamiento de errores con excepciones, debemos protegemos frente a la aceleracin de posibles excepciones incorpo-
rando dicha actividad de limpieza en una clusula finall y.
Considere un ejemplo de un sistema de diseo asistido por computadora que dibuje imgenes en la pantalla:
JJ : reusingJCADSystem.java
JI Cmo garantizar una limpieza adecuada.
package reusing
import static net.mindview.util.Print.*
class Shape {
Shape{int i)
void dispose ()
print (IIShape constructor")
{ print ("Shape dispose
H
) i }
class Circle extends Shape {
Circle (int i) {
super (i) ;
print{UDrawing Circle") i
}
void dispose () {
print ("Erasing Circle");
super.dispose(} ;
class Triangle extends Shape {
Trianglelint il (
super (i) ;
print ( "Drawing Triangle 11) i
}
void dispose () {
print (" Erasing Triangle");
super.dispose() ;
class Line extends Shape {
private int start, end;
Line(int start, int end}
super (start) j
this.6tart = start
this.end = end
print ("Drawing Line: 11 + start +
void dispose () {
print ("Erasing tine: " + start +
super.dispose() i
public class CADSystem extends Shape {
private Circle C
private Triangle ti
private Line [] lines = new Line [3) ;
public CADSystem(int i) {
super (i + 1);
" + end);
" + end);
for{int j = O; j < lines.length; j++)
lines [j] = new Line{j I j*j) i
e = new Circle(1);
t = new Triangle{1);
print ("Combined constructor");
public void dispose () {
print ("CADSystem. dispose () " } ;
II El orden de limpieza es el inverso
II al orden de inicializacin:
t.dispose() ;
c.dispose() ;
for(int i = lines.length - 1; i >= O; i--)
lines[i] .dispose( );
super.dispose() ;
public static void main (String [] args) {
7 Reutilizacin de clases 149
1 SO Piensa en Java
CADSystem x new CADSystem(47);
try {
/1 Cdigo y tratamiento de excepciones.
finally {
x.dispose{) ;
/* Output:
Shape constructor
Shape constructor
Drawing Line: 0, O
Shape constructor
Drawing Line: 1, 1
Shape constructor
Drawing Line: 2, 4
Shape constructor
Drawing Circle
Shape constructor
Drawing Triangle
Combined constructor
CADSystem.dispose()
Erasing Triangle
Shape dispose
Erasing Circle
Shape dispose
Erasing Line: 2, 4
Shape dispose
Erasing Line: 1, 1
Shape dispose
Erasing Line: O, O
Shape dispose
Shape dispose
*///,-
Todo en este sistema es algn tipo de objeto Shape (que a su vez es un tipo de Object, puesto que hereda implcitamente
de la clase raz). Cada clase sustit uye el mtodo dispose( ) de Shape, adems de invocar la versin de dicho mtodo de la
clase base utilizando super. Las clases Shape especficas, Circle, Triangle y Une, tienen constructores que "dibujan" las
formas geomtricas correspondientes, aunque cualquier mtodo que se invoque durante la vida del objeto puede llegar a ser
responsable de hacer algo que luego requiera una cierta tarea de limpieza. Cada clase tiene su propio mtodo dispose() para
restaurar todas esas cosas que no estn relacionadas con la memoria y dejarlas en el estado en que estaban antes de que el
objeto se creara.
En maine ), hay dos palabras clave que no habamos visto antes y que no van a explicarse en delalle hasta el Capitulo le.
Traramiento de errores mediante excepciones: try y finally. La palabra clave try indica que el bloque situado a continua-
cin suyo (delimitado por llaves) es una regin protegida, lo que quiere decir que se la otorga un tratamiento especial. Uno
de estos tratamientos especiales consiste en que el cdigo de la clusula finally sihlada a continuacin de esta regin prote-
gida siempre se ejecuta, independientemente de cmo se salga de bloque try (con el tratamiento de excepciones, es posible
salir de un bloque try de di versas formas di st intas de la normal). Aqu, la clusula finally dice: "Llama siempre a dispose()
para x, independientemente de lo que suceda".
En el mtodo de limpieza, (dispose(), en este caso), tambi n hay que prestar atencin al orden de Llamada de los mtodos
de limpieza de la clase base y de los objetos miembro, en caso de que un subobjelo dependa de otro. En general, hay que
seguir la misma forma que imponen los compiladores de C++ para los destructores: primero hay que realizar toda la tarea
de limpieza especfica de la clase, en orden inverso a su creacin (en generaL esto requiere que los elementos de la clase
base sigan siendo viables). A continuacin, se invoca el mtodo de limpieza de la clase base, C0l110 se ilustra en el ejemplo.
Hay muchos casos en los que el tema de la limpieza no constituye un problema, bastando con dejar que el depurador de
memoria realice su tarea. Pero cuando hay que llevar a cabo una limpieza explcita se requieren grandes dosis de diligencia
y atencin, porque el depurador de memori a no sirve de gran ayuda en este aspecto. El depurador de memoria puede que no
7 Reutilizacin de ctases 151
llegue nunca a ser invocado y, en caso de que lo sea, podra reclamar los objetos en el orden que quisiera. No podemos con-
fiar en la depuracin de memoria para nada que no sea reclamar las zonas de memoria no utilizadas. Si queremos que las
tareas de limpieza se lleven a cabo. es necesario definir nuestros propios mtodos de limpieza y no emplear finalize() .
Ejercicio 12: (3) Aliada una jerarqua adecuada de mtodos dispose() a todas las clases del Ejercicio 9.
Ocultacin de nombres
Si una clave base de Java tiene un nombre de mtodo varias veces sobrecargado, redefinir dicho nombre de mtodo en la
cl ase derivada no ocultar ninguna de las versiones de la clase base (a diferencia de lo que sucede en e++). Por tanto, el
mecanismo de sobrecarga funci ona independientemente de si el mtodo ha sido definido en este nivelo en tina clase base:
// : reusing/ Hide . java
// La sobrecarga de un nombre de mtodo de la clase base en una
II clase derivada no oculta las versiones de la clase base.
i mport static net.mindview. util.Print .* ;
c lass Homer {
char doh (char c) {
print ("doh (char) ") ;
return 'd';
float doh l float f l (
print ( "doh (float ) " ) ;
return 1. Of;
class Milhouse {}
class Bart extends Homer {
void doh (Milhouse m) {
print ( "doh (Milhouse ) " ) ;
public class Hi de {
public stat ic void main (Stri ng [) args) {
Bart b = new Bart {) i
b . dohlll;
b.doh( ' x ');
b . doh l l.Of l ;
b . doh (new Milhouse ()) i
1* Output :
dohlfloa tl
dOh(char)
doh Ifloa t I
doh( Milhouse)
*/11 , -
Podemos ver que todos los mtodos sobrecargados de Homer estn disponibles en Bart. aunque Bart introduce un nuevo
mtodo sobrecargado (si se hiciera esto en C++ se ocultaran los mtodos de la clase base). Como veremos en el siguiente
capitulo, lo ms comn es sobrecargar los mtodos del mismo nombre, utili zando exactamente la mi sma signatura y el
mi smo tipo de retomo de la clase de retomo. En caso contrario, el cdigo podra resultar confuso (lo cual es la razn por la
que C-++ oculta todos los mtodos de la clase base. para que no cometamos lo que muy probablemente se trata de un error).
Java SES ha aadido al lenguaje la anotacin @Override, que no es una palabra clave pero puede usarse como si lo fuera.
Cuando queremos sustituir un mtodo, podemos aadir esta anotacin y el compilador generar un mensaje de error si sobre-
cargamos accidentalmente el mtodo en lugar de sustiruirlo:
152 Piensa en Java
JI : reusing/ Lisa . java
/ / {CompileTimeError} (Won' t compile )
class Lisa extends Homer {
@Override void doh (Milhouse m) {
System.out.println("doh (Milhouse J ");
}
///,-
El marcador {CompileTimeError} excluye el archivo del proceso de construccin con Ant de este libro, pero si lo compi-
la a mano podr ver el mensaje de error:
methad does not override a methad fraID its superclass
La anotacin @Override evitar, as , que sobrecarguemos accidentalmente un mtodo cuando no es eso lo que queremos
hacer.
Ejercici o 13: (2) Cree una clase con un mtodo que est sobrecargado tres veces. Defina una nueva clase que herede de
la anterior y aada una nueva versin sobrecargada del mtodo. Muestre que los cuatro mtodos estn dis-
ponibles en la clase derivada.
Cmo elegir entre la composicin y la herencia
Tanto la composicin como la herencia nos permiten incluir subobjetos dentro de una nueva clase (la composicin lo hace
de forma explcita, mi entras que en el caso de la herencia esto se hace de fonna implcita). Puede que se est preguntando
cul es la diferencia enrTe ambos mecanismos y cundo convi ene elegir entre uno y otro.
Generalmente, la composicin se usa cuando se desea incorporar la funcionalidad de la clase existente dentro de la nueya
clase pero no su interfaz. En otras palabras, lo que hacemos es integrar un objeto para poderlo utilizar con el fm de poder
implementar ciertas caractersticas en la nueva clase, pero el usuario de la nueva clase ver la interfaz que hayamos defini -
do para la nueva clase en lugar de la interfaz del objeto incrustado. Para conseguir este efecto, lo que hacemos es integrar
objetos private de clases existentes dentro de la nueva clase.
Algunas veces, tiene sentido permitir que el usuario de la clase acceda directamente a la composicin de esa nueva clase.
es decir. hacer que los objetos miembros sean pblicos. Los objetos miembros utilizan la tcnica de la ocultacin de la
implementacin por s mismos, as que no existe ningn riesgo. Cuando el usuario sabe que estamos ensamblando un con-
junto de elementos. normalmente, puede comprender mejor la interfaz que hayamos definido. Un ejemplo seria un objeto
car (cocbe):
JI: reusingfCar.java
II Composicin con objetos pblicos.
class Engine {
public void start () {}
public void rey 11 {)
public void stop 11 {)
class Wheel {
public void inflate (int psi ) {}
class Window {
public void rollup 11 {)
public void rolldown ( ) {)
class Door {
public Window window = new Window()
public void open 11 {)
public void elose (1 {)
public class Car {
public Engine engine
public Wheel[) wheel
public Door
Ieft = new Door {) ,
new
new
Engine () ;
Wheel [4) ;
right = new Door(); // 2-door
public Car () {
for (int i = Oi i < 4; i++ )
wheel [i] = new Wheel () ;
public static void main{String[] args ) {
Car car = new Car () ;
car.left.window.rollup() ;
car.wheel [O) .inflate (72) ;
7 Reutilizacin de clases 153
Puesto que en este caso la composicin de un coche fonna parte del anli sis del problema (y no simpl emente del diseo sub-
yacente), hacer los miembros pblicos ayuda al programador de clientes a entender cmo utilizar la clase, y disminuye tam-
bi n la complejidad del cdigo que el creador de la clase tiene que desarrollar. Sin embargo, tenga en cuenta que ste es un
caso especial y que, en general, los campos deberan ser privados.
Cuando recurrimos almcc3nismos de herencia, lo que hacemos es tomar una clase existente y definir una versin especial
de la misma. En general, esto quiere decir que estaremos tomando una clase de propsito general y especial izndola para
una necesi dad concreta. Si reflexionamos un poco acerca de ello, podremos ver que no tendra ningn sentido componer un
coche utilizando un objeto vehculo. ya que un coche no contiene vehculo, sino que es un vehculo. La relacin eS-1II1 se
expresa mediante la herencia, mientras que la relacin tiene-un se expresa mediante la composicin.
Ejercicio 14: (1) En Carojava aada un mtodo service() a Engine e invoque este mtodo desde main().
protected
Ahora que ya hemos tomado contacto con la herencia. vemos que la palabra clave protected adquiere su pleno significado.
En un mundo ideal, la palabra clave private resuhara suficiente, pero en los proyectos reales, hay veces en las que quere-
mos ocultar algo a ojos del mundo pero pennitir que accedan a ese algo los miembros de las clases derivadas.
La palabra clave protected es homenaje al pragmatismo, lo que dice es: "Este elemento es privado en lo que respecta al
usuario de la clase, pero est disponible para cualqui era que herede de esta clase y para todo lo dems que se encuentre en
el mismo paquete (protccted tambin proporciona acceso de paquete)"o.
Aunque es posible crear campos de tipo protected (protegidos), lo mejor es definir los campos como privados; esto nos per-
mitir conservar siempre el derecho a modificar la implementacin subyacente. Entonces, podemos permitir que los here-
deros de nuestra clase dispongan de un mtodo controlado utilizando mtodos protected:
JI: reusingfOrc.java
JI La palabra clave protected.
import static net.mindview.util.Print.*
class Villain
private String name;
protected void set (String nm) { name = nm; }
public Villain(String name ) { this.name = name;
public String toString () {
return "I'm a Villain and my name is " + name;
154 Piensa en Java
public class Ore extends Villain {
private int orcNumber;
public Ore (String name, int orcNumber)
super (name) ;
this.orcNumber = orcNumber;
public void change (String name, int orcNumber) {
set(name); /1 Disponible porque est protegido.
this.orcNumber = orcNumber;
public String toString () {
return "Ore " + orcNumber + ": " + super. toString () ;
public static void main(String[] args)
Ore ore,: new Orc{"Limburger", 12) i
print (ore) i
ore . change ("Bob", 19);
print (ore) ;
1* Out put :
Or e 12: l' m a Vi l lain and my name i s Li mburger
Or e 19 : l ' m a Vi l lai n a nd my na me i 5 Bob
* /// , -
Puede ver que ehange() tiene a cceso a sel() porque es de tipo proleeled. Observe tambin la forma en que se ha defini-
do el mtodo loSlring() de Ore en trminos de loSlring() de la clase base.
Ejercicio 15: (2) Cree una clase denlro de un paquete. Esa clase debe estar dentro de un paquete. Esa clase debe conte-
ner un mtodo protected. Fuera del paquete, trate de invocar el mtodo protected y expli que los resulta-
dos. Ahora defina otra clase que herede de la anterior e invoque el mtodo prolected desde un mtodo de
la clase derivada.
Upcasting (generalizacin)
El aspecto ms importante de la herencia no es que proporciona mtodos para la nueva clase, sino la relacin expresada en-
tre la nueva clase y la clase base. Esta relacin puede resumirse diciendo que "la nueva clase es un lipo de la clase exis-
tente".
Esta descripcin no es simplemente una foona elegante de explicar la herencia, si no que est soportada directamente por el
lenguaje. Por ejemplo, considere una clase base denominada [nstrument que represente instrumentos musicales y una clase
derivada denominada Wind (instrumentos de viento). Puesto que la herencia garanti za que todos los mtodos de la clase
base estn disponibles tambin en la clase derivada, cualquier mensaje que enviemos a la clase base puede enviarse tambin
a la clase derivada. Si la clase Inslrumenl tiene un mtodo play() (tocar el instrumento), tambin lo tendrn los instrumen-
tos de la clase Wind. Esto significa que podemos decir con propi edad que un objeto Wind es tambin un objeto de tipo
Inslrumon!. El siguiente ejemplo ilustra cmo soporta el compilador esta idea.
11 : reusing/ Wind.java
II Herencia y generalizacin.
class Instrument {
public void play () {}
static void tune (Instrument i ) {
/ / ...
i. play () ;
II Los objetos instrumentos de viento
II porque tienen la misma interfaz:
public class Wind extends I nstrument (
public static void ma i n (String[] args )
Wind f lute = new Wi nd( )
Instrument. tune ( f lute l ; /1 Generali zacin
}
;;; , -
7 Reutilizacin de clases 155
Lo ms interesante de este ejemplo es el mtodo tune( ) (afi nar), que acept a una referencia a un objeto Instrument. Sin
embargo, en Wind.main() al mtodo tune( ) se le entrega una referencia a un objeto Wind. Dado que Java es muy estric-
to en lo que respecta a las comprobaciones de tipos, parece extrao que un mtodo que acepta un detenninado tipo pueda
aceptar tambin otro tipo distinto, hasta que nos demos cuenta de que un objeto Wind tambin es un objeto I nstrument y
de que no existe ningn mtodo que tune() pudiera invocar para un objeto Instrumeot y que no se encuentre tambin en
Wind. Dentro de tune(), el cdigo funciona tanto para los objetos Instrument como para cualquier otra cosa derivada de
Instrument, y el acto de convertir una referencia a un objeto Wind en otra referencia a Instrument se denomina upcasling
(generalizacin).
Por qu generalizar?
El tnnino est basado en la fonna en que se vienen dibujando tradicionalmente los diagramas de herencia de clase. Con la
raz en la parte superi or de la pgina y las clases derivadas distribuyndose hacia abajo (por supuesto, podramos dibujar los
diagramas de cualqui er otra manera que nos resultara til), El diagrama de herencia para Wind.java ser entonces:
Al reali zar una proyeccin de un tipo derivado al tipo base, nos movemos hacia arriba en el diagrama de herencia, y esa
es la razn de que en ingls se utilice el trmino upcasling (up = arriba, casI = proyeccin. El upcasling o generalizacin
siempre resulta seguro, porque estamos pasando de un tipo ms especfico a otro ms general. Es decir, la clase derivada es
UD superconjunto de la clase base. Puede que la clase derivada contenga ms mtodos que la clase base, pero debe contener
al menos los mtodos de la clase base. Lo nico que puede ocurrir con la interfaz de la clase durante la generali zacin es
que pierda mtodos, no que los gane, y sta es la razn por la que el compilador pennite la generali zacin sin efectuar nin-
gn tipo de proyeccin explcita y si n emplear ninguna notacin especial.
Tambin podemos realizar el inverso de la generali zacin, que se denomina downcasling (especializacin), pero esto lleva
asociado un cierto dil ema que examinaremos ms en detalle en el siguiente captulo, y en el Captulo 14, Informacin de
tipos.
Nueva comparacin entre la composicin y la herencia
En la programacin orientada a objetos, la fonna ms habitual de crear y utilizar cdigo consiste en empaquetar los datos y
mtodos en una clase y usar los objetos de dicha clase. Tambin utilizamos otras clases existentes para construir nuevas cia-
ses utilizando el mecanismo de composicin. Menos frecuentemente, debemos utilizar el mecanismo de herencia. Por tanto,
aunque al ensear programacin orientada a objetos se suele hacer un gran hincapi en el tema de la herencia, eso no quie-
re decir que se la deba usar en todo momento. Por el contrario, conviene emplearla con mesura, y slo cuando est claro que
la herencia resulta til. Una de las fonnas ms claras de detenninar si debe utilizarse composicin o herencia consiste en
preguntarse si va a ser necesario recurrir en algn momento al mecanismo de generalizacin de la nueva clase a la clase
base. Si es necesario usar dicho mecani smo, entonces la herencia ser necesaria, pero si ese mecani smo no hace falta con-
viene meditar si verdaderamente hay que emplear la herencia. En el Captulo 8, Polimotjisl/lo se proporciona una de las
razones ms importantes para utilizar la generalizacin, pero si se acuerda de preguntarse u vaya necesitar generalizar en
algn momento?" tendr una buena fonna de optar entre la composicin y la herencia.
Ejercico 16: (2) Cree una clase denominada Amphibian (anfibio). A partir de sta, defina una nueva clase denomina-
da Frog (rana) que herede de la anterior. Incluya una sere de mtodos apropiados en la clase base. En
156 Piensa en Java
main( ). cree un objeto Frog y realice una generalizacin a Amphibian, demostrando que lodos los mto-
dos siguen funcionando.
Ejercicio 17: (1) Modifique el Ejercicio 16 para que el objeto Frog sustituya las definiciones de mtodos de la clase
base (proporcione las nuevas definiciones utilizando las mismas signaturas de mtodos). Observe 10 que
sucede en main( ).
La palabra clave final
La palabra clave de Java final tiene significados ligeramente diferentes dependiendo del contexto, pero en general quiere
decir: "'Este elemento no puede modifi carse". Puede haber dos razones para que no queramos pennitir los cambios: diseo
y eficiencia. Puesto que estas dos razones son muy diferentes entre s, resulta bastante posible utilizar la palabra clave final
de manera inadecuada.
En las siguient es secciones vamos a ver los tres lugares donde final puede utilizarse: para los datos, para los mtodos y para
las clases.
Datos final
Muchos lenguajes de programacin disponen de alguna fonna de comunicarle al compilador que un elemento de datos es
"constante". Las constantes son tiles por dos razones:
1. Puede tratarse de una constante de tiempo de compilacin que nunca va a cambiar.
2. Puede tratarse de un valor inicializado en tiempo de ejecucin que no queremos que cambie.
En el caso de una constante de tiempo de compilacin, el compilador est autorizado a "compactar" el valor constante en
todos aquellos clculos que se le utilice; es decir, el clculo puede realizarse en tiempo de compilacin eliminando as cier-
tos clculos en tiempo de ejecucin. En Java, estos tipos de constantes deben ser primitivas y se expresan con la palabra
clave final . En el momento de definir una de tales constantes, es preci so definir un valor.
Un campo que sea a la vez static y final slo tendr una zona de almacenamiento que no puede nunca ser modificada.
Cuando final se utiliza con referencias a objetos en lugar de con primitivas, el significado puede ser confuso. Con una pri-
mitiva, final hace que el valor sea constante, pero con una referencia a objeto lo que final hace constante es la referencia.
Una vez inicializada la referencia a un objeto, nunca se la puede cambiar para que apunte a otro objeto. Sin embargo, el pro-
pio objeto s que puede ser modificado; Java no proporciona ninguna manera para hacer que el objeto arbitrario sea cons-
tante (podemos, sin embargo, escribir nuestras clases de modo que tengan el efecto de que los objetos sean constantes). Esta
restriccin incluye a las matrices, que son tambin objetos.
He ?qu un ejemplo donde se ilustra el uso de los campos final . Observe que, por convenio, los campos que son a la vez
static y final (es decir, constantes de tiempo de compilacin) se escriben en maysculas. utilizando guiones bajos para sepa-
rar las palabras.
11: reusing/FinalData.java
II Efecto de final sobre los campos.
import java.util.*;
import static net.mindview.util.Print.*;
class Value {
int i; II Acceso de paquete
public Value(int i) {this.i i;
public class FinalData {
private static Random rand = new Random(47)
private String id;
public FinalData (String id) { this. id = id }
II Pueden ser constantes de tiempo de compilacin:
private final int valueOne = 9;
private static final int VALUE_TWO = 99;
/1 Constante pblica tipica:
public static final int VALUE_THREE = 39;
JI No pueden ser constantes de tiempo de compilacin:
private final int i4 = rand.nextlnt(20);
static final int INT_S = rand.nextlnt(20l i
private Value vI = new Value(ll) i
private final Value v2 = new Value(22)
private static final Value VAL_3 = new Value(33) i
1/ Matrices:
private final int[] a = { 1, 2, 3, 4, 5, 6 }i
public String toString () {
return id + lO: !I + "i4 = " + i4 + ", INT_S "+ INT_5;
public static vold main (String [] args) {
FinalData fd! = new FinalData("fdl"};
jI! fdl.valueOne++i /1 Error: no se puede modificar el valor
fdl.v2.i++; /1 El objeto no es constante!
fdl.vl = new Value(9); II OK no es final
for (int i =- O; i < fdl. a .length; i++)
fdl.a[iJ++; II El objeto no es constante!
II! fdl.v2 = new Value(O); II Error: no se puede
II! fdl.VAL_3 = new Value(l); II cambiar la referencia
II! fdl.a =- new int [3] ;
print (fd1) ;
print ("Creating new FinalData");
FinalData fd2 new FinalData (" fd2") ;
print (fd1) ;
print (fd2) ;
1* Output:
fd1, i4 = 15, INT 5 = 18
Creating new FinalData
fd1, i4 15, INT_5 18
fd2, i4 = 13, INT 5 = 18
*111 ,-
7 Reutilizacin de clases 157
Dado que valueOne y VALUE_TWO son primitivas final con valores definidos en tiempo de compil acin, ambas pueden
usarse como constantes de tiempo de compilacin y no se diferencian en ningn aspecto importante. VALUE_ THREE es
la fonna ms tpi ca en que podr ver definidas dichas constantes: public que se pueden usar fuera del paquete, static para
enfatizar que slo hay uoa y final para decir que se trata de una constante. Observe que las primitivas final static con valo-
res iniciales constantes (es decir, constantes en tiempo de compil acin) se designan con letras maysculas por convenio,
separando las palabras mediante guiones bajos (al igual que las constantes en C, que es el lenguaje en el que surgi este con-
venio).
El que algo sea final no implica necesariamente el que su valor se conozca en ti empo de compil acin. El ejemplo ilustra
est inicializando i4 e lNT_5 en tiempo de ejecucin, mediante nmeros generados aleatoriamente. Esta parte del ejemplo
tambin genera la diferencia entre hacer un va lor final estt ico o no esttico. Esta diferencia slo se hace patente cuando los
valores se iniciali zan en tiempo de ejecuci n, ya que el compil ador trata de la mi sma manera los valores de tiempo de com-
pilacin (en muchas ocasiones, optimizando el cdi go para eli minar esas constantes). La diferenci a se muestra cuando se
ejecuta el programa. Observe que los valores de i4 para fd1 y fd2 son distintos, mientras que el valor para INT_5 no cam-
bia porque creemos el segundo objeto FinalData. Esto se debe a que es estt ico y se inicializa una sola vez durante la carga
y no cada vez que se crea un nuevo objeto.
Las vari ables v1 a VAL_3 ilustran el signifi cado de una referencia final . Como puede ver en main( ), el hecho de que v2
sea final no quiere decir que se pueda modificar su valor. Puesto que es una referencia, final significa que no se puede aso-
ciar v2 COIl un nuevo objeto. Podemos ver que la afinnacin tambin es cierta para las matrices, que son otro de tipo de refe-
rencia (no hay ninguna fom1a que yo conozca de hacer que las referencias a una matri z sean final ). Hacer las referencias
final parece menos ti l que definir las primitivas como final.
158 Piensa en Java
Ejercicio 18: (2) Cree una clase con un campo static final y un campo final y demuestre la diferencia entre los dos.
Valores final en blanco
Java pennite la creacin de valores finales en blanco, que son campos que se declaran como final pero que no se les p r ~
porciona un valor de inicializacin. En todos los casos, el valor final en blanco debe ser inicializado antes de utilizarlo, y
el compilador se encargar de hacer que esto sea as. Sin embargo, los valores final en blanco proporcionan mucha ms fle
xibilidad en el uso de la palabra clave final ya que, por ejemplo, un campo final dentro de una clase podr con esto ser dife-
rente para cada objeto y mantener an as su carcter de inmutable. He aqu un ejemplo:
JI : reusing/ BlankFi na l . j ava
JI Campos final "en blanco".
cIass Poppet {
private int i
Poppet (int ii I { i ii; }
public cIass BlankFinal {
private final int i = Di 1/ Valor final inicializado
private final int ji II Va l or final en blanco
private final Poppet Pi II Referencia final en blanco
II Los valores final en blanco DEBEN inicializarse en el constructor:
publie BlankFinal () {
j 1i II Inicializar valor final en blanco
p = new Poppet (l ) ; II Inicializar referencia final en blanco
public BlankFinal {int x )
j Xi II Inicializar valor final en blanco
p = new Poppet (x ) ; II Inicializar referencia final en blanco
public static void main {String[] args ) {
new BlankFinal {) ;
new BlankFinal (47 ) i
Estamos obligados a realizar asignaciones a los valores final utilizando una expresin en el punto de definicin de) campo
o bien en cada constructor. De esa fonna, se garantizar que el campo final est siempre inicializado antes de utilizarlo.
Ejercicio 19: (2) Cree una clase con una referencia final en blanco a un objeto. Realice la inicializacin de la referen-
cia final en blanco dentro de todos los constructores. Demuestre que se garantiza que el valor final esta-
r inicializado antes de utilizarlo, y que no se puede modificar una vez inicializado.
Argumentos final
Java pennite definir argumentos final declarndolos como tales en la lista de argumentos. Esto significa que dentro del
mtodo no se podr cambiar aquello a lo que apunte la referencia del argumento:
11 : reusing/ FinalArguments.java
Il uso de 11 final" con argumentos de mtodos.
class Gizmo {
publie vo id spin () {}
public class FinalArguments {
void with {final Gizmo g ) {
II ! 9 = new Gizmo () ; II Ilegal -- 9 es final
g .
el = n e',,' ;:;izmo {; ; 1/ 8K
;; . sp:n ;' i
9 r.o es :i"o. 1
-Jo ici f (final i::-.1: i ) f i+-I'; } 1/ Ko se puede car:-.:ciar
Las f !nal s510 pueden leerse:
L:c. g; f::-Ial '-n:. :' i { ret urn i 1" 1.; }
rub1'..c static mainl$tring[) args l {
?i:aLr..:::-g\.:.me"ts bf = ne"'" FinalArgume:-tts ;. ;
_ _ . '.Vl.thout (null \ ;
ti
7 Reutil izacin de clases 159
Lo" f( ) Y g( ) muestran lo que sucede cuando los argumentos primitivos son final : Se puede leer el argumento. pero
no 1l10diticarlo. Es ta caracel'stica se utili za principall11ell!e pam pasar datos a la:-; clases intemas annimas. lo cua l es un
dcl que hablaremos en el C3ptulo 10. Clases imen/Us.
Mtodos final
Hay dos razones para utilizar mtodos finaL. La primera es "'bloquear" el mtodo para impedir que cualquie.r clase quc here-
de dc esta cambi e su significado. Esto se hace por razones de disc10 cuando queremos aseguramos de que se retenga el Cl1lll-
ponallliemo de un mwdo duranre la herencia y que ese mtodo pueda ser sustituido.
razn por la que se ha sugerido en ('.1 pasado la utilizacin <,1(' los mtodos tinal es la eficiencia. En las illlplc-
Illcllfill."ionl!s anteriores de Java, si definamos un mtodo como final. pemlitamos al compi lador convenir rodas las Ihllna-
da::- a ese mtodo en llamadas en lnea. Cuando el comrilador vea una llamada a un mtodo final. poda (a su discrecin)
sal!:lrse el modo 110nnal de insertar el cdigo correspondientc almccanismos de llamada al mtodo (insenar los argumen-
tos en la pi la. saltar al cdigo del mtodo y ejecutarlo, saltar hacia arras y eliminar de la pila los argumentos y tratar el vall)]"
de retorno), para sustitui r en su lugar la llamada al mlodo por Wla copia del propio cdigo contenido en el cuerpo del m-
todo. Esto elimina el adicional de recursos asociado a la llamada al mtodo. Por supuesto. si el mtodo es de gran
!alllai1o. el cdigo empezar a cr..::cer y probablemente no detectemos ninguna mejora de velocidad por la uti
de metodos en lnea. ya qw: la mejora ser comparada con la call1idad de tiempo invertida dentro del
111CIOdo.
En 111:-. \ersiones ms recicmes de Jl\,a. la mquina virtual (en panicular. la Iccnolog<I/IOISpOI) puede dete(:tar L'Stas situa-
y eliminar el paso adicional de indireccin. por lo que ya no es necesario (de becho. se desaconseja por regla gene-
ral) util izar final para tratar de ayudar al optimizador. Con laya SES/A. lo que debemos hacer es dejar que el compilador y
la .1 V\:1 se encarguell de las cU6tiollCS de eficiencia, y slo debemos definir un como final si queremos impedir
explkitall1.:nte la del mlOdo en las clases deri\'adas.!
final y private
Los mtodos prhados de una clase son implicit3ment(' de tipo final (finales). Puesto que no Se puede acceder a un mtodo
privndo. es imposible sustituirlo en una clase dc.:-ri\ada. Podemos aadir el especifcador tionl a un mtodo priva te. pero no
h:.'Ihlri ningn efecto adicional.
I:SlC rema puede causar algo de confusin, porque si se trata de sustiruir un mtolll private (que implcitamente es fimll).
que el mecanismo funciona y el compilador no proporciona ningn mensaje de en'Oc
././ : reus ing/ FinalOverridingIll usion. java
l/ Tan slo pal-ece que podamos sustituir
1/ mt.odo privado o un mtodo privado final.
sta:ic
' o ri<!rda d ti \.'lllpo u1l1:mdo de opllmi'ar pr.:maturamente. Si d sistema funciona ':<' .:s demasiado lento. r<!sulta dndo) qu<! lo pueda soh ent<lr \.:n la
claw final. h"p."' Milldl iell./Ie! Bouks. Bo!fft'rJal'i't L'Llnticne infom1an au::rC!l de tccnicas pcrtibdo. qUl' Plledl'n servir d.: ayuJa a la hora
'.k' :ll'elomr Jos
160 Piensa en Java
class WithFinals {
II Idntico al uso de IIprivate
ll
sola:
private final void f () { print {IIWithFinals.f () 11 ) ;
I I Tambin automaaticmente 11 final" :
private void g () { print ( IIWithFinals.g () " ) }
class OverridingPrivate extends WithFinals
private final void f () {
print ( "OverridingPri vate. f () " ) ;
pri vate void 9 () {
print ( "OverridingPrivate.g () " ) ;
class OverridingPrivate2 extends OverridingPrivate
public final void f 1) {
print ( IIOv erridingPrivate2.f( ) " )
public void 9 1) {
print {"OverridingPrivate2.g() It)
public class FinalOverridingIllusion {
public static void main (String[) args)
OverridingPrivate2 op2 = new OverridingPrivate2();
op2 . f () ;
op2.gl1 ;
II Se puede generalizar:
OverridingPrivate op = op2
II Pero no se pueden invocar los mtodos:
(( ! op.f l) ;
( (! op.gl ) ;
I I Lo mismo aqu:
WithFinals wf = op2
( (! wf. f 1) ;
( (! wf.g 1) ;
1* Output:
OverridingPrivate2.f ()
OverridingPrivate2.g ()
*//(,-
La "sustitucin de los mtodos" slo puede tener lugar si el mtodo forma parte de la clase base. En otras palabras, es nece-
sario poder generalizar un objeto a su tipo base y poder invocar al mismo mtodo (este tema resultar ms claro en el
siguiente captulo). Si un mtodo es privado, no fonna parte de la interfaz de la clase base. Se trata simplemente de un cier-
to cdigo que est oculto dentro de la clase y que sucede que tiene ese nombre, pero si creamos un mtodo public, protec-
ted o con acceso de paquete con el mismo nombre en la clase derivada, no habr ninguna conexin con el mtodo que resulte
que tiene el mismo nombre en la clase base. Es decir, no habremos sustituido el mtodo, sino que si mplemente habremos
creado otro nuevo. Puesto que un mtodo private es inalcanzable y resulta invisible a efectos prcticos, a lo nico que afec-
ta es a la organizacin del cdigo de la clase en la que haya sido definido.
Ejercicio 20: (1) Demuestre que la anotacin @Override resuelve el problema descrito en esta seccin.
Ejercicio 21: (1) Cree una clase con un mtodo final. Cree otra clase que herede de la clase anterior y trate de sustituir
ese mtodo.
7 Reutilizacin de clases 161
Clases final
Cuando decimos que toda una clase es de tipo final (precediendo su definicin con la palabra clave final ), lo que estamos
diciendo es que no queremos heredar de esta clase ni pennitir que nadi e ms lo haga. En otras palabras, por alguna razn,
el diseo de nuestra clase es de tal natural eza que nunca va a ser necesario efectuar ningn cambio, o bien no queremos que
nadie defi na cl ases deri vadas por razones de seguridad.
jI : reusing/Jurassic . java
// Definicin de una clase completa como final.
class SmallBrain {}
final class Dinosaur
int i = 7;
int j = 1;
SmallBrain x
void f () ()
new SmallBrain(};
JI! class Further extends Dinosaur {}
/1 error: no se puede heredar de la clase final ' Dinosaur'
public class Jurassic
public static void main (String [] args) {
Dinosaur n = new Dinosaur() i
n. f () ;
n.i = 40;
n. j ++;
}
111 ,-
Observe que los campos de una clase final pueden ser de tipo final o no, segn deseemos. A los campos de tipo final se les
apl ican las mismas reglas independientemente de si la clase est definida como final. Sin embargo, como la clase impide la
herencia, todos los mtodos en una clase final son implcitamente final , ya que no existe ninguna fonna de sustituirlos.
Podemos aadir el especificador final a un mtodo de una clase final , pero no tiene ningn efecto adicional.
Ejercicio 22: ( 1) Cree una clas. final y trate de definir otra clase que herede de ella.
Una advertencia sobre final
En ocasiones, uno puede verse tentado a definir un mtodo corno final a la hora de definir una clase, pensando que nadie
podra querer sust ituir ese mtodo. A veces, es cierto que las cosas pueden ser as.
Pero tenga cuidado con las suposiciones que realiza. En general, es dificil prever cmo se va a reutilizar una clase, especial-
mente si se trata de una clase de propsito general. Si define un mtodo como final, puede que est impidiendo reutilizar la
clase a travs del mecanismo de herencia en algn otro proyecto de programacin, simplemente porque no haba llegado a
imaginar que esa clase pudi era ll egar a empl earse de esa f0n11a.
La biblioteca estndar de Java es un buen ejemplo de esto. En particular, la clase Vector de Java 1.011.1 se utilizaba de forma
bastante comn y todava poda haber resultado ms til si, por consideraciones de eficiencia (que no pasaban de ser una
ilusin), no se hubieran hecho todos los mtodos final. Resulta fcil imaginar que alguien quiera heredar una clase tan fun-
damental y tan til y sustituir sus mtodos, pero los di seiiadores decidieron por alguna razn que esto no era apropiado.
Resulta bastante irnico que se tomara esa decisin por dos razones distintas. En primer lugar, Stack (pila) hereda de Vector,
lo que quiere decir que Stack es un Vector, lo que no es realmente cierto desde un punto vista lgico. En cualqui er caso, se
trata de un ejemplo en el que los propios di seadores de Java decidieron que una detenninada clase heredara de Vector.
Cuando crearon Stack de esta fonna, debieron darse cuenta de que los mtodos final eran bastante restrictivos.
En segundo lugar, muchos de los mtodos ms importantes de Vector, como addElement( ) y elementAt( ) estn sincro-
nizados (synchronized). Como veremos en el Captulo 21 , Concurrencia, esta caracterst ica restringe significati vamente las
162 Piensa en Java
prestaciones. lo que probablemente anula cualquier ganancia proporcionada por final. Este ejemplo tiende a avalar la teo-
ra de que los programadores suelen equivocarse siempre a la hora de decidir dnde hay que optimizar. Resulta un poco
penoso que un diseo tan pobre terminara siendo incluido en la biblioteca estndar para que lOdo el mundo tuviera que
sufrirlo (afortunadamente, la moderna biblioteca de contenedores Java sustituye Vector por ArrayList, que se comporta de
una fonna mucho ms civilizada; lamentablemente, hoy da se sigue escribiendo cdigo que utili za la antigua biblioteca de
contenedores).
Resulta tambin interesante observar que Hashtable, otra clase importante de la biblioteca estndar 1.0/ 1.1 de Java no tiene
ningn mtodo tinal. Como ya se ha mencionado, es patente que algunas de las clases fueran di seadas por personas dis-
tintas (tendr la ocasin de comprobar que los nombres de los mtodos en Hashtable son mucho ms breves comparados
con los de Vector, lo que constituye otra prueba de esta afirnlacin). Esto es, preci samente, el tipo de cosas que no resultan
obvias para los consumidores de una biblioteca de clases. Cuando las cosas no son coherentes, damos ms trabajo del nece-
sario a los usuarios, lo cual es otro argumento en favor de las revisiones de di seo y de los programas (observe que la biblio-
teca actual de contenedores Java sustituye Hashtabl e por HashMap).
Inicializacin y carga de clases
En los lenguajes ms tradicionales. los programas se cargan de una vez como parte del proceso de arranque. Este proceso
va seguido del de inicializacin y luego del programa. El proceso de inicializacin en estos lenguajes debe controlarse cui-
dadosamente para que el orden de inicializacin de los valores estticos no cause problemas. Por ejemplo, e++ tiene pro-
blemas si uno de los valores estticos espera que otro valor estt ico sea vlido antes de que el segundo haya sido iniciali zado.
Java no tiene este problema porque adopta una tcnica de carga completamente distinta. sta es una de las actividades que
se facili tan enonnemente porque en Java todo es un objeto. Recuerde que el cdigo compilado de cada clase est ahnace-
nado en su propio archivo separado. Dicho archivo no se carga hasta que ese cdigo sea necesario. En general, podemos
decir que "el cdigo de las clases se carga en el lugar que por primera vez se utiliza". Usualmente, dicho lugar es cuando se
construye el primer objeto de esa clase, aunque la carga tambin puede tener lugar cuando se acceda a un campo esttico o
a un mtodo esttico. 2
El lugar del primer uso es tambin el lugar donde se produce la inicializacin de los elementos estticos. Todos los elemen-
tos estticos y el bloque de cdigo static se iniciali zarn en orden textual (es decir, en el orden en el que estn escritos en
la defmicin de la clase), en el punto donde se produzca la carga. Por supuesto, los valores estticos slo se inicializan una
vez.
Inicializacin con herencia
Resulta til analizar el proceso de inicializacin completo, incluyendo la herencia, para hacerse una idea general. Considere
el siguiente ejemplo:
// : reusing/ Beetle.java
// El proceso completo de inicializacin.
import static net.mindview.util.Print.*
class Insect {
private int i = 9;
protected int j;
Insect (1 {
print("i = " + i +
j = 39;
private static int xl =
j 11 + j);
printlnit("static Insect.xl initialized");
static int printlnit (String s ) {
2 El constructor es tambin un mtodo eSHi.tico, an cuando la palabra clave static no sea explcila. Por tanlo. para ser precisos, una clase se carga por pri-
mera vez cuando se accede a cualquiera de sus miembros estticos.
print (s ) ;
return 47;
public class Beetle extends Insect {
pri vate int k = printlni t ( " Beetle. k ini tialized " ) ;
pub l ic Beetle () {
print ( "k + k ) ;
print ( " j = 11 + j ) i
private static int x2
printlnit {"static Beetle.x2 initialized" ) ;
public static void main (String [J args ) {
print ( "Beetle constructor" ) ;
Beetle b = new Beetle () ;
/ * Output:
stati c Insect.xl initialized
s t atic Beetle.x2 initialized
Beetle constructor
i= 9,j=O
Beet l e.k initialized
k = 47
j = 39
* /// ,-
7 Reutilizacin de clases 163
Lo primero que sucede cuando se ejecuta Java con Beetle es que se trata de acceder a Beetle.main() (un mtodo esttico),
por lo que el cargador locali za el cdigo compilado correspondiente a la clase Beetle (en un archivo denominado
Beetle.class). Durante el proceso de carga, el cargador observa que tiene una clase base (eso es lo que dice la palabra clave
extends), por lo que procede a cargarlo. Esto suceder independientemente de si se va a construir un objeto de dicha clase
base (pruebe a desactivar con comentarios la creacin del objeto CalDO demostracin).
Si la clase base tiene a su vez otra clase base, esa segunda clase base se cargara, y as sucesivamente. A continuacin, se
realiza la inicializacin static en la clase base raz (en este caso, loseet), y luego en la siguiente clase derivada, etc. Esto es
importante, porque la inicializacin static de la clase derivada puede depender de que el miembro de la clase base haya sido
inicializado adecuadamente.
En este punto, ya se habrn cargado todas las clases necesarias, por lo que se podr crear el objeto. En primer lugar, se asig-
nan los valores predetenninados a todas las primitivas de este objeto y se configuran las referencias a objetos con el valor
null (esto sucede en una nica pasada, escribiendo ceros binarios en la memoria del objeto). A continuacin, se invoca el
constructor de la clase base. En este caso la llamada es automtica, pero tambin podemos especificar la llamada al cons-
tructor de la clase base (como primera operacin dentro del conslmctor Beetle()) utilizando super. El constructor de la clase
base pasa a travs del mismo proceso y en el mismo orden que el constructor de la clase derivada. Despus de que se com-
plete el constructor de la clase base, las variables de instancia se inicializan en orden textual. Finalmente, se ejecuta el resto
del cuerpo del constructor.
Ejercicio 23: (2) Demuestre que el proceso de carga de una clase slo tiene lugar una vez. Demuestre que la carga puede
ser provocada por la creacin de la primera instancia de esa clase o por el acceso a un miembro esttico
de la misma.
Ejercicio 24: (2) En Beelle.java, defina una nueva clase que represente un tipo especfico de la clase Beetle de la que de-
be heredar, siguiendo el mismo fonnato que las clases existentes. Trace y explique los resultados de salida.
Resumen
Tanto la herencia como la composicin pemliten crear nuevos tipos a partir de los tipos existentes. La composicin reuti-
liza los tipos existentes como parte de la implementacin subyacente del nuevo tipo, mientras que la herencia reutiliza la
interfaz.
164 Piensa en Java
Con la herencia, la clase derivada tiene la interfaz de la clase base. as que puede ser generalizada hacia la base, lo cual
resulta crtico para el polimorfismo, como veremos en el siguiente captulo.
A pesar del gran nfasis que se pone en las cuestiones de herencia cuando hablamos de programacin orientada a objetos,
al comenzar un diseilo suele ser preferible la composicin (o preferiblemente la delegacin) en una primera aproximacin,
y utilizar la herencia slo cuando sea claramente necesario. La composicin tiende a ser ms flexible. Adems. utilizando
la herencia como artificio ailadido a un tipo que se haya incluido como miembro de una clase, se puede modificar el tipo
exacto (y por tanto el comportamiento) de esos objetos miembro en tiempo de ejecucin. De este modo, se puede modifi-
car el comportamiento del objeto compuesto en tiempo de ejecucin.
A la hora de disear un sistema, nuestro objetivo consiste en localizar o crear un conjunto de clases en el que cada clase
tenga un uso especfico y no sea ni demasiado grande (que abarque tanta funcionalidad que sea dificil de reutilizar) ni inc-
modamente pequea (de modo que no se pueda emplear por s misma o si n aadirla funcionalidad). Cuando los diseos
comienzan a complicarse demasiado, suele resultar til aadir ms objetos descomponiendo los existentes en otros ms
pequeos.
Cuando se proponga diseilar un sistema, es importante tener en cuenta que el desarrollo de los programas es un proceso
incremental, al igual que el proceso de aprendizaje humano. El proceso de diseilo necesita de la experimentacin; podemos
hacer las tareas de anlisis que queramos pero es imposible que lleguemos a conocer todas las respuestas en el momento de
arrancar el proyecto. Tendr mucho ms xito (y podr probar las cosas antes) si comienza a "hacer crecer" el proyecto como
una criatura orgnica en constante evolucin, en lugar de construi r todo de una vez, como si fuera un rascacielos de cristal.
La herencia y la composicin son dos de las herramientas ms fundamentales en la programacin orientada a objetos a la
hora de realizar tales experimentos en el curso de un proyecto.
Puede encotHrar las solucioncs a los ejercicios seleccionados en el documento eleclrnico The Thillking in Jm'a Am/Ofafed So/mio" Gllide. disponible para
la venta en \\'lI'\\'.AfindViell'. l1ef.
Polimorfismo
"En ocasiones me he preguntado, ' Sr. Babbage, si introduce en la mquina datos errneos,
podr suministrar las respuestas correctas? Debo confesar que no soy capaz de comprender
qu tipo de confusin de ideas puede hacer que alguien plantee semejante pregunta".
Charles Babbage ( 1791-1871)
El polimorfismo es la tercera de las caractersticas esencial es de un lenguaj e de programacin ori entado a objetos, despus
de la abstraccin de datos y de la herenci a.
Proporciona otra dimensin de separacin entre la interraz y la implementacin, con el fin de desacoplar el qu con respec-
to al cmo. El polimorfi smo pennite mejorar la organizacin y la legibi lidad de cdigo, as como crear programas amplia-
bles que puedan "hacerse crecer" no slo durant e el proceso ori ginal de desarrollo del proyecto, si no tambin cada vez que
se desean adherir nuevas caractersticas.
Elmccanismo de encapsulacin crea nuevos tipos de datos combinando di versas caractersti cas y comportami entos. La tc-
nica de la ocultacin de la implementacin separa la interfaz de la impl ementacin haciendo que los detalles sean de tipo
privado. Este tipo de organizacin mecnica tiene bastante sent ido para las personas que tengan experiencia con la progra-
macin procedimental. Pero el polimorfi smo trata la cuesti n del acopl amiento en tnninos de tipos. En el ltimo captulo,
hemos visto que la herencia pennite tratar un objeto como si fuera de su propio tipo o como si fuera del tipo base. Esta capa-
cidad resulta crtica, porque pem1ite tratar varios tipos (todos ell os derivados del mismo tipo base) como si fueran un ruco
tipo, pudindose utili zar un mismo fragmento de cdigo para procesar de la misma manera todos esos tipos dife-
rentes. La ll amada a un mtodo polimrfico pennite que cada tipo exprese su di stincin con respecto a los otros tipos simi-
lares, siempre y cuando ambos deriven del mismo tipo base. Esta distincin se expresa mediante diferencias en el compor-
tamiento de los mtodos que se pueden invocar a travs de la clase base.
En este captulo, vamos a estudiar el tema del polimorfismo (tambi n denominado acoplamiento dinmico o acoplamiento
tardo o acoplamiento en tiempo de ejecucin) comenzando por los conceptos ms bsicos y proporcionando ejemplos sim-
ples en los que nos fijremos tan slo en el comportamiento polimrfico de los programas.
Nuevas consideraciones sobre la generalizacin
En el ltimo captul o hemos visto cmo puede utilizarse un objeto como si fuera de su propio tipo o como si fuera un obje-
to del tipo base. El acto de tomar una referencia a un objeto y tratarla como si fuera una referencia a su tipo base se deno-
mina generali=acin (upcasring) debido a la fonna en que se dibujan los rboles de herencia. en los que la clase base se
suele representar en la parte superi or.
Tambin vi mos en el captulo anterior cmo surgi el problema a este respecto, el cual se ilustra en el siguiente ejemplo
sobre instrumentos musicales.
En primer lugar, puesto que en muchos de estos ejempl os los instmmentos hacen sonar notas (Note), vamos a crear una enu-
meracin separada Note, dentro de un paquete:
11 : polymorphism/music/Note.java
1I Notas para tocar en los instrumentos musicales .
,
166 Piensa en Java
package polymorphism.music;
public enum Note {
MIDDLE_C, C_SHARP, B_FLAT; II Etc.
} 111,-
Los tipos enum se han presentado en el Captulo 5, Inicializacin y limpieza.
Aqu \ Vind es un tipo de Instrument : por tanto, Wind hereda de Instrument :
JI: polymorphism/music/lnstrument.java
package polymorphism.music;
import static net.mindview.util.Print.*
class Instrument {
public void play(Note n) {
print (" Instrument. play () ") ;
}
111,-
//: polymorphism/music/Wind.java
package polymorphism. music;
// Los objetos Wind son instrumentos
// porque tienen la misma interfaz:
public class Wind extends Instrument
JI Redefinicin de un mtodo de la interfaz:
public void play(Note n) {
System.out.println{"wind.play() " + n);
}
111,-
//: polymorphism/music/Music.java
// Herencia y generalizacin.
package polymorphism.music;
public class Music {
public sta tic void tune (Instrument i) {
II ...
i.play{Note . MIDDLE_C)
public static void main(String[) args) {
Wind flute : new Wind{)
tune (flute) ; // Generalizacin
/ * Output:
Wind.playl) MIDDLE_C
*111,-
El mtodo Music.tune() acepta una referencia a Instrument , pero tambin a cualquier cosa que se deri ve de Inst r ument.
Podemos ver que esto sucede en main( ), donde se pasa una referencia ' Vind a tune(), sin que sea necesario efectuar nin-
guna proyeccin. Esto resulta perfectamente lgico: la interfaz de Instrument debe existir en Wind, porque Wind hereda
de Instrument . La generalizacin de Wind a Instrument puede "estrechar" dicha interfaz. pero en ningn caso esa inter-
faz podr llegar a ser ms pequea que la interfaz completa de Instrument .
Por qu olvidar el tipo de un objeto
Music.java puede rcsultarle un poco extrao. Por qu alguien debera olvidar intencionadamente el tipo de un objeto? Esto
es lo que sucede cuando efect uamos una generalizacin, y parece que sera mucho ms sencillo si tune() simplemente toma-
r una referenc ia a \Vind como argumento. Esto plantea un punto esencial : si hiciramos eso, necesitaramos escribir un
8 Polimorfismo 167
nuevo mtodo tunee ) para cada clase deri vada de Instrument que incluyramos en nuestro sistema. Suponga que sigui-
ramos esta forma de razonar y aadi ramos dos instrumentos Stringed (instmmentos de cuerda) y Brass (instrumentos de
metal):
// : polymorphism/music/Music2.java
/1 Sobrecarga en lugar de generalizacin.
package polymorphism.music;
import static net.mindview.util.Print.*;
class Stringed extends Instrument {
public void play (Note n) {
print (" Stringed. play () " + n);
class Brass extends Instrument {
public void play (Note n) {
print ( "Brass. play () I! + n};
public class Music2 {
public static void tune (Wind i) {
i .play {Note .MIDDLE el;
public static void tune (Stringed i) {
1.play(Note.MIDDLE e);
public static void tune (Brass i) (
i.play(Note.MIDDLE_e) ;
public static void main(String[] args )
Wind flute = new Wind()
Stringed violin new Stringed();
Brass frenchHorn new Brass () i
tune (flute) i /1 Sin generalizacin
tune (violin) i
tune (frenchHorn) i
/ * Output:
Wind.play() MIDDLE_e
Stringed .play () MIDDLE_C
Brass .play () MIDDLE_C
*/// ,-
Esta solucin funciona, pero presenta una desvemaja importante: es necesario escribir mtodos especficos del tipo para
cada nueva clase derivada de Instrument que aadamos. Esto significa, en primer lugar, un mayor esfuerzo de programa-
cin, pero tambin quiere decir que si queremos ailadir un nuevo mtodo como tunee ) o un nuevo tipo de clase derivada
de Instrument, el trabajo adicional necesario es considerable. Si a esto le aadimos el hecho de que el compilador no nos
dar ningn mensaje de error si nos olvidamos de sobrecargar alguno de los mtodos, todo el proceso de gestin de los tipos
se vuelve itmlanejabl e.
No sera mucho ms fcil , si pudiramos. limitamos a escribir un nico mtodo que tomara la clase base como argumen-
to y no ninguna de las clases derivadas especficas? En otras palabras: no sera mucho ms adecuado si pudiramos olvi-
damos de que hay clases derivadas y escribir el cdigo de manera que slo se entendiera con la clase base?
Eso es exactamente 10 que el polimorfi smo nos pemlite hacer. Sin embargo, la mayora de los programadores que proceden
del campo de los lenguajes de programacin procedimentales suelen tener problemas a la hora de entender cmo funciona
el polimorfismo.
168 Piensa en Java
Ejercicio 1:
El secreto
(2) Cree una clase Cyele, con subclases Uni cyele, Bicycl e y Tri cyele. Demuestre que se puede generali-
zar una instancia de cada tipo a Cycle mediante un mtodo ride( ).
La dificultad C011 Musi c.java puede verse ejecutando el programa. La salida es Wi nd.play(). Se trata claramente de la sali-
da deseada, pero no parece tener sentido que el programa funcione de esa fonna. Examinemos el mtodo tune( ):
public static void tune (Instrument i) {
/ / ...
i.play(Note.MIDDLE_CI;
El mtodo recibe una referencia a Instrument. De modo que cmo puede el compilador saber que esta referencia a
Instr ument apunta a un objeto Wind en este caso y no a un objeto Br ass o Stringcd? El compi lador no puede saberlo. Para
comprender mejor esta cuestin, resulta til que examinemos el tema del acoplamiento.
Acoplamiento de las llamadas a mtodos
El hecho de conectar una llamada con el cuerpo del mtodo se denomina acoplamiento. Cuando se realiza el acoplamiento
antes de ejecutar el programa (es decir, cuando lo realizan el compilador y el montador, si es que existe uno), el proceso se
llama acoplamiento temprano (early binding). Puede que haya odo este tnnino antes porque en los lenguajes procedimen-
tales, como por ejemplo C, slo existe un tipo de llamadas a mtodos y ese tipo es precisamente, el acoplamiento tempra-
no, as que no existe ninguna posibilidad de elegir.
La parte confusa del programa anterior es precisamente la que se refiere al acoplamiento temprano, porque el compilador
no puede saber cul es el mtodo correcto que hay que llamar cuando slo dispone de una referencia Instrument.
La solucin es el acoplamiento tardo (late binding), que quiere decir que el acoplamiento tiene lugar en tiempo de ejecu-
cin basndose en el tipo del objeto. El acoplamiento tardo tambin se denomina acoplamiento dinmico o acoplamie11lo
en tiempo de ejecucin. Cuando un lenguaje implementa el mecanismo de acoplamiento tardo, debe haber alguna manera
de detenninar el tipo del objeto en tiempo de ejecucin, con el fin de llamar al mtodo apropiado. En otras palabras, el com-
pilador sigue sin saber cul es el tipo del objeto, pero el mecanismo de invocacin del mtodo lo averigua y ll ama al cuer-
po de mtodo correcto. El mecanismo de acoplamiento tardo vara de un lenguaje a otro, pero podemos considerar que en
todos los objetos debe incorporarse una cierta infonnacin sobre el tipo del objeto.
El mecanismo de acoplamiento de mtodos en Java utiliza el acoplamiento tardo a menos que el mtodo sea esttico o de
tipo final (los mtodos prvate son implcitamente final). Esto quiere decir que, normalmente, no es necesario tomar nin-
guna decisin acerca de si debe producirse el acoplamiento tardo, ya que ste tendr lugar automticamente.
Para qu querramos declarar un mtodo como final? Como hemos indicado en el captulo anterior, esto evita que nadie
pueda sustihlir dicho mtodo en las clases derivadas. Adems, y todava ms importante, esta palabra clave desactiva en la
prctica el acoplamiento dinmico, o ms bien le dice al compilador que el acoplamiento dinmico no es necesario. Esto
permite que el compilador genere un cdigo ligeramente ms eficiente para las llamadas a mtodos fi nal. Sin embargo, en
la mayora de los casos. no ser perceptible la ganancia de velocidad en el programa, por lo que lo mejor es utilizar final
nicamente por decisin de diseo, y no como intento de mejorar las prestaciones.
Especificacin del comportamiento correcto
Ahora que sabemos que todo el acoplamiento de mtodos en Java tiene lugar pol imrficamente a travs del acoplamiento
tardo, podemos escribir el cdigo de fonna que se comunique con la clase base, a sabiendas de que todos los casos donde
estn involucradas las clases derivadas funcionarn correctamente con el mismo cdigo. 0, dicho de otro modo, "enviamos
un mensaje a un objeto y dejamos que el objeto averige qu es lo que tiene que hacer".
El ejemplo clsico en la programacin orientada a objetos es el de las "fonnas". Se suele utilizar comnmente porque resul
ta fcil de visualizar, pero lamentablemente puede hacer que los programadores inexpertos piensen que la programacin
orientada a objetos slo sirve para la programacin grfica, lo cual, por supuesto, no es cierto.
8 Polimorfismo 169
El ejemplo de las fonnas tiene una clase base denominada Shapc (fonna) y varios tipos derivados: Ci rel e (crculo), Squar e
(cuadrado). Tri angle (tringulo), etc. La razn por la que este ejemplo es tan adecuado es porque es fcil decir "un crculo
es un tipo de fom1a" y que el lector lo entienda. El diagrama de herencia muestra las relaciones:
cin
+
Generaliza
en el diagr
de heren
ama
t
t
Referencia
a Circle
cia
t
. _ . _ . ...J
I
Circle
drawO
eraseO
Shape
drawO
eraseO
I
Square Triangle
drawO drawO
eraseO eraseO
La generalizacin puede tener lugar en una instmccin tan simple como la siguiente:
Shape s = new Circle();
Aqu , se crea un objeto Circle, y la referencia resultante se asigna inmediatamente a un objeto Shape, (lo que podra pare-
cer un error asignar un ti po a otro); sin embargo, es perfectamente correcto, porque un objeto Circle es una forma (Shape)
debido a la herencia. Por tanto, el compil ador aceptar la instruccin y no generar ni ngn mensaje de error.
Suponga que invoca uno de los mtodos de la clase base (que han sido sustituidos en las clases derivadas):
s.draw() ;
De nuevo. cabra esperar que se invocara el mtodo draw() de Shape porque, despus de todo, esto es una referencia a
Shape, asi que cmo podria el compi lador hacer cualquier otra cosa? Sin embargo, se invoca el mtodo apropiado
Circle.dr aw( ) debido al acoplamiento tardio (polimorfismo).
El siguiente ejemplo presenta las formas de una manera ligeramente distinta. En primer lugar, vamos a crear una biblioteca
reutilizable de tipos Shape:
//: polymorphism/shape/Shape.java
package polymorphism.shape
public cIass Shape {
public void draw() {}
public void erase () ()
///,-
1/: polymorphism/shapelCircle.java
package polymorphism.shape
impore static net.mindview.util.Print.*
pubIic cIass CircIe extends Shape {
pubIic void draw() { print ( "CircIe.draw() ")
pubIic void erase () { print ( nCircIe. erase () ")
///, -
/1: polymorphism/shape/Square.java
package polymorphism.shape
import static net.mindview.util.Print.*;
pubIic cIass Square extends Shape {
pubIic void draw() { print("Square.draw(}");
170 Piensa en Java
public void erase () { print (lISquare. erase () "); }
// /,-
JI: pOlymorphism/shape/Triangle.java
package polymorphism.shape
import static net.mindview.util.Print.*
public class Triangle extends Shape {
public void draw () { print ("Triangle . draw () 11) ;
public void erase () { print ("Triangle. erase () 11) ;
// /,-
/1: pOlymorphism/shape/RandomShapeGenerator.java
l/Una 11 fbrica" que genera formas aleatoriamente.
package polymorphism.shape
import java.util.*;
public class RandomShapeGenerator
private Random rand = new Random(47);
public Shape next () {
switch(rand.nextInt(3) )
default:
case 0, return new Circle() ;
case 1, return new Square () ;
case 2, return new Triangle(} ;
1/: polymorphism/Shapes.java
II Polimorfismo en Java.
import polymorphism.shape.*
public class Shapes {
private static RandomShapeGenerator gen =
new RandomShapeGenerator(}
public static void main(String[] args}
Shape[] s = new Shape[9];
II Rellena la matriz con formas:
for(int i = O; i < s.length i++}
s [i] = gen. next ()
II Realiza llamadas a mtodos polimrficos:
for(Shape shp : s}
shp. draw () ;
1* Output:
Triangle. draw ()
Triangle. draw ()
Square. draw ()
Triangle. draw ()
Square. draw ()
Triangle. draw ()
Square. draw ()
Triangle. draw {}
Circle. draw ()
* ///>
La clase base Shape establece la interfaz comn para cualqui er otra clase que herede de Shape; en tnninos conceptuales.
representa a todas las formas que puedan dibujarse y borrarse, Cada clase deri vada sust ituye estas definiciones con el fin de
proporcionar un comportamiento distintivo para cada tipo especfico de forma.
8 Polimorfismo 171
es una especie de "fbrica" que genera una referencia a un objeto Shape aleatoriamente selec-
cionado cada vez que se invoca su mtodo next( ). Observe que el upcasting se produce en las instrucciones return, cada
una de las cuales toma una referencia a Ci rcle, Square o Triangle y la devuelve desde next() con el tipo de retorno, Shape.
Por tanto, cada vez que se invoca next(). nunca tenemos la oprotunidad de ver de qu tipo especfico se trata. ya que siem-
pre obtenemos una referencia genrica a Shape.
main() contiene una matri z de referencias Shape que se rellena mediante llamadas a RandomShapeGenerator.next(). En
este punto, sabemos que tenemos objetos Shape, pero no podemos ser ms especficos (ni tampoco puede serlo el compi-
lador). Sin embargo, cuando recorremos esta matriz e invocamos draw() para cada objeto, tiene lugar el comportamiento
correspondiente a cada tipo espec fl co, como por arte de magia, tal y como puede ver si analiza la salida que se obtiene al
ejecutar el programa.
La razn de crear las fonnas aleatoriamente es que as puede percibirse mejor que el compilador no puede tener ningn
conocimiento especial que le pennite hacer las llamadas correctas en tiempo de compilacin. Todas las J1amadas a draw()
deben tener lugar mediante el mecanismo de acoplamiento dinmico.
Ejercici o 2:
Ejercici o 3:
Ejercici o 4:
Ejercici o 5:
(1) Aada la anotacin @Overr ideal ejemplo de procesami ento de fonnas.
(1) Aada un nuevo mtodo a la clase base de Shapes.java que imprima un mensaje, pero sin sustituirlo
en las clases derivadas. Explique lo que sucede. Ahora, sustityalo en una de las clases derivadas pero no
en las otras y vea lo que sucede. Finalmente, sustityalo en todas las clases deri vadas.
(2) Aada un nuevo tipo de objeto Shape a Shapes.j ava y verifique en main() que el polimorfismo fun-
ciona para el nuevo tipo al igual que para los tipos anteriores.
( 1) Pani endo del Ejercicio 1, aada un mtodo wheels() a Cycle, que devuelva el nmero de ruedas.
Modifique ride() para invocar wheels() y verifique que func iona el polimorfismo.
Ampliabil idad
Vol vamos ahora al ejempl o de los instrumentos musicales. Debido al polimorfi smo, podemos aadi r al sistema todos los
nuevos tipos que deseemos sin modificar el mtodo tune() . En un programa orientado a objetos bi en diseado, la mayora
de los mtodos (o todos ellos) seguirn el mtodo de t une() y slo se comunicarn con la interfaz de la clase base. Ese lipo
Instrument
void playO
String whatO
void adjustO
I
Wind Percussion Stringed
void playO void playO void playO
String whatO String whatO String whatO
void adjustO void adjustO void adjustO
Woodwind Brass
void playO void playO
Stri ng whatO void adjustO
172 Piensa en Java
de programas es extensible (ampliable) porque puede aadir nueva funcionalidad heredando nuevos tipos de datos a p r ~
tir de la clase base comn. Los mtodos que manipulan la interfaz de la clase base no necesitarn ser modificados para poder
utili zar las nuevas clases.
Consi dere lo que sucede si tomamos el ejemplo de los illstnullentos y aadimos ms mtodos a la clase base y una serie de
clases nuevas. Puede ver el diagrama correspondiente al final de la pgina anterior.
Todas estas nuevas clases funcionan correctament e con el mtodo amiguo tune(), sin necesidad de modificarl o. Incluso si
tune() se encontrara en un archivo separado y aadiramos nuevos mtodos a la interfaz de Instrument. tune() seguira
funcionando correctamente. si n necesidad de recompilarlo. He aqu la implementacin del diagrama:
// : polymorphism/music3/Music3.java
// Un programa ampliable.
package polymorphism.music3
import polymorphism.music . Note
import static net . mindview.util.Print. *
class Instrument {
void play(Note n) { print("Instrument.play{) " + n); }
String what () { return It Instrument lO }
void adj ust () { print ("Adj usting Instrument")
class Wind extends Instrument {
void play(Note n) {print("Wind.play{) + n) }
String what () { return "Wind" i }
void adjust() { print{"Adjusting Wind");
class Percussion extends Instrument {
void play(Note n) {print("Percussion.play() + n) }
String what () { return "Percussion"; }
void adjust() { print("Adjusting Percussion
lt
);
class Stringed extends Instrument {
void playlNote ni { printl"Stringed . playll " + ni; }
String what () { return "Stringed"; }
void adjust() { print("Adjusting Stringed
U
) i
class Brass extends Wind {
void play(Note n) { print(IIBrass . play() " + n) }
void adj ust () { print (" Adjusting Brass 11) ;
class Woodwind extends Wind {
void playlNote ni { printl"Woodwind.playll " + ni; }
String what () { return IIWoodwind"; }
public class Music3 {
// No importa el tipo, por lo que los nuevos
/1 tipos aadidos al sistema funcionan bien :
public static void tune I Instrument i l {
/ / ...
i.playINote.MIDDLE_CI;
public static void tuneAll (Instrument [] e) {
for{Instrument i : e)
tune(i) i
public static vold main(String[) args)
// Upcasting durante la adicin a la matriz:
Instrument[] orchestra = {
new Wind() I
new Percussion () I
new Stringed () I
new Brass () ,
new Woodwind ()
) ;
tuneAll(orchestral;
/ * Output:
wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
woodwind.play() MIDDLE_C
* /// ,-
8 Polimorfismo 173
Los nuevos mtodos son what( ), que devuelve una referencia String con una descripcin de la clase y adjust(), que pro-
porciona alguna fanna de ajustar cada instmmento.
En main( ), cuando insertamos algo dentro de la matriz orchestra, se produce automticamente una generalizacin a
Jnstrument.
Podemos ver que el mtodo t une( ) es completamente ignorante de todos los cambios de cdigo que han tenido lugar alre-
dedor suyo, a pesar de 10 cual sigue funcionando perfectamente. sta es, exactamente, la funcionalidad que se supone que
el polimorfismo debe proporcionar. Los cambios en el cdigo no generan ningn problema en aquellas partes del programa
que no deban verse afectadas. Dicho de otra fon11a , el polimorfismo es una tcnica importante con la que el programador
puede "separar" las cosas que cambian de las cosas que pennanecen.
Ejercici o 6: ( 1) Modifique Music3.java de modo que what() se convierta en el mtodo toString() del objeto raz
Object. Pruebe a imprimir los objetos (nstr ument utilizando System.out.pr intln() (sin efectuar ningu-
na proyeccin de tipo).
Ejercicio 7: (2) Ailada un nuevo tipo de objeto lnstrument a Music3.java y verifique que el polimorfismo funciona
para el nuevo tipo.
Ejercicio 8: (2) Modifique Music3.java para que genere aleatoriamente objetos [ostrument de la mi sma forma que 10
bace Shapes.java.
Ejercicio 9: (3) Cree una jerarquaa de herencia Rodent: Mouse, Gerbil, Hamster, etc (roedor: ratn, jerbe, hamster,
etc.). En la clase base proporcione los mtodos que son comunes para todos los roedores, y sustituya estos
mtodos en las clases derivadas para obtener diferentes comportami entos dependiendo del tipo espec fi co
de roedor. Cree una matri z de objetos Rodeot , rellnela con diferentes tipos especficos de roedores e
invoque los mtodos de la clase base para ver lo que sucede.
Ejercici o 10: (3) Cree una clase base con dos mtodos. En el primer mtodo, invoque el segundo mtodo. Defina una
clase que herede de la anterior y sustituya el segundo mtodo. Cree un objeto de la clase derivada, reali-
ce una generalizacin (lIpcasling) al tipo base y llame al primer mtodo. Explique 10 que sucede.
Error: "sustitucin" de mtodos private
He aqu un ej emplo de error de un programa que se puede cometer de manera inadvertida:
11 : polymorphismJPrivateOverride.java
JI Intento de sustituir un mtodo privado .
package polymorphism
import static net . mindview.util.Print.*;
174 Piensa en Java
public class PrivateOverride {
private void f () { print("private f () 11);
public static void main(String[] args)
PrivateOverride po = new Derived() j
po.r O;
class Derived extends PrivateOverride {
public void r () { print ( "public r () ") ;
} / * Output ,
private f ()
* /// ,-
Podra esperar, razonabl emente, que la salida fuera "public f( )", pero los mtodos pri vados son automticamente de tipo
final , y estn tambin ocultos a ojos de la clase deri vada. Por esta razn, el mtodo f() de la clase derivada es, en este caso,
un mtodo completamente nuevo, ni siquiera est sobrecargado, ya que la versin de f( ) en la clase base no es visible en
Derived.
El resultado de esto es que slo los mtodos no privados pueden ser sustituidos, as que hay que estar atento al intento inco-
rrecto de sustituir mtodos de tipo priva te, ya que esos intentos no generan ninguna advertencia del compi lador, sino que
el sistema no har, seguramente, 10 que se espera. Para evitar las confusiones, conviene utilizar en la clase deri vada un nom-
bre diferente al del mtodo private de la clase base.
Error: campos y mtodos static
Una vez famili ari zados con el tema del polimorfi smo, podemos tender a pensar que todo ocurre polimrfi camente. Sin
embargo, las nicas ll amadas que pueden ser polimrficas son las llamadas a mtodos normales. Por ejemplo, si accedemos
a un campo directamente, ese acceso se resolver en tiempo de compil acin, como se ilustra en el siguiente ejemplo: 1
// : polymorphism/ FieldAccess.java
// El acceso directo a un campo se determina en tiempo de compilaci6n.
class Super {
public int field = O
public int getField O { return field; }
c lass Sub extends Super
public int field = 1:
public int getField () return field; }
public int getSuperField () { return super. field
public class FieldAccess (
public static void main(String[] args ) {
Super sup = new Sub(); // Upcast
System.out.println(ltsup.field = n + sup.field +
", sup . getField () = n + sup. getField () ) :
Sub sub = new Sub (} :
System.out.println("sub.field = +
sub . field + 11 I sub. getField () +
sub.getField () +
ti, sub . getSuperField () = n +
sub . getSuperField ()) ;
I Gracias a Randy Nichols por plantear esta cuestin.
/ * Output:
sup. field
sub. field =
,///:-
0, sup getField{)
1, sub getField{)
8 Polimorfismo 175
1
1, sub.getSuperField {) = O
Cuando un objeto Sub se generaliza a una referencia Super, los accesos a los campos son resueltos por el compi lador, por
lo que no son polimrficos. En este ejemplo, hay asignado un espacio de almacenamiento distinto para Super.field y
Sub.lield. Por tanto, Sub contiene realmente dos campos denominados lield: el suyo propio y el que obti ene a partir de
Super. Sin embargo, cuando se hace referencia al campo field de Super no se genera de fanna predetenninada una refe-
rencia a la versin almacenada en Super; para poder acceder al campo field de Super es necesario escribir explcitamente
super.lield.
Aunque esto ltimo pueda parecer algo confuso. en la prctica no ll ega a plamearse casi nunca, por una razn: por regla
general, se definen todos los campos como priva te, por lo que no se accede a ellos directamente, sino slo como efecto
secundario de la invocacin a mtodos. Adems, probablemente nunca le demos el mi smo nombre de la clase base a un
campo de la clase derivada, ya que eso resultara muy confuso.
Si un mtodo es de tipo stane, no se comporta de fonna polimrfica:
JJ : polymorphismJStaticPolymorphism.java
11 Los mtodos estticos no son polimrficos.
class StaticSuper {
public static String staticGet()
return "Base staticGet () u i
public String dynamicGet () {
return u Base dynamicGet () " ;
class StaticSub extends StatieSuper {
publie statie String statieGet () {
return "Derived statieGet(}"
public String dynamicGet () {
return "Derived dynamicGet() u;
public class StaticPolymorphism {
public statie void main (String[] args ) {
StaticSuper sup : new StaticSub(); JJ Generalizacin
System,out.println(sup.staticGet{)) ;
System.out . println(sup.dynamicGet()) i
J* Output:
Base staticGet()
Derived dynamicGet()
, /// :-
Los mtodos estticos estn asociados con la clase y no con los objetos individuales.
Constructores y polimorfismo
Corno suele suceder, los constructores difieren de los otros tipos de mtodos, tambin en lo que respecta al polimorfi smo.
Aunque los constructores no son polirnrficos (se trata realmente de mtodos estticos, pero la declaracin static es impl-
cita), tiene gran importancia comprender cul es la fonna en que funcionan los constructores dentro de las j erarquas com-
plejas y en presencia de polimorfismo. Esta compresin de los fundamentos nos ayudar a evitar errores desagradables.
176 Piensa en Java
Orden de las llamadas a los constructores
Hemos hablado brevemente del orden de las llamadas a los constmclOres en el Captulo 5, Inicializacin y Iimpie:a, y tam-
bin el Captulo 7. Relllili:acin de clases, pero eso fue antes de introducir el concepto de polimorfi smo.
El conslructor de la clase base siempre se invoca durante el proceso de construccin correspondiente a una clase deri vada.
Esta llamada provoca un desplazamiento automtico hacia arriba en la jerarqua de herencia, invocndose un constructor
para todas las clases base. Esto tiene bastant e sentido, porque el constructor tiene asignada una tarea especial: garantizar que
el objeto se constmye apropiadamente. Una clase deri vada slo tiene acceso a sus propios miembros y no a los de la clase
base (aquellos mi embros tpicamente de tipo private). Slo el constructor de la clase base dispone del conocimiento y del
acceso adecuados para inicializar sus propios elementos. Por tanto, resulta esencial que se invoquen todos los constructo-
res, en caso contrario, no podra const ruirse el mtodo completo. sta es la razn por la que el compilador impone que se
realice una llamada al constructor para cada parte de una clase derivada. Si no especificamos explcitamente una llamada a
un constructor de la clase base dentro del cuerpo de la clase deri vada, el compilador invocar de manera automtica el cons-
tructor predetenninado. Si no hay ningn constmctor predetenninado, el compilador generar un error (en aquellos casos
en que una determinada clase no tenga ningn constructor, el compilador sintetizar automticamente un constructor prede-
terminado).
Veamos un ejempl o que muestra los efectos de la composicin, de la herencia y del polimorfi smo sobre el orden de cons-
truccin:
1/: polymorphism/Sandwich . java
/ 1 Orden de las llamadas a los constructores.
package polymorphism
import static net.mindview. util.Print.*
class Meal
Meal {l print ( "Meal () " )
class Bread
Bread () { print ( liBread () 11 ) j
class Cheese
Cheese () { print ( IICheese () " )
class Le t tuce
Lettuce {) { print {"Lettuce {) " ) j
class Lunch extends Meal {
Lunch () { print ( "Lunch () " )
class PortableLunch
PortableLunch (l
extends Lunch {
print {"PortableLunch () 11 ) }
public class Sandwich extends PortableLunch
private Bread b = new Bread ()
private Cheese e = new Cheese{ )
private Lettuce 1 = new Lettuce()
public Sandwich () { print ( " Sandwich () " )
public static void main (String [] args l {
new Sandwich ()
1* Output:
Meal (1
Lunch!)
portabl eLunch ()
Bread (1
Cheese ()
Lettuce ()
Sandwich ()
. /// ,-
8 Polimorfismo 177
Este ejemplo crea una clase compleja a partir de otras clases y cada una de estas clases dispone de un constructor que se
anuncia a s mismo. La clase importante es Sandwich, que refleja tres niveles de herencia (cuatro si contamos la herencia
implcita a partir de Object) y tres objetos miembro. Podemos ver en main() la salida cuando se crea un objeto Sandwich.
Esto quiere decir que el orden de llamada a los constnlctores para un objeto complejo es el siguiente:
1. Se invoca al constructor de la clase base. Este paso se repite de fanna recursiva de modo que la raz de la jerar-
qua se constmye en primer lugar, seguida de la siguiente clase derivada, etc., hasta alcanzar la clase si tuada en
el nivel ms profundo de la jerarqua.
2. Los ini cial izadores de los miembros se invocan segn el orden de declaracin.
3. Se invoca el cuerpo del constmctor de la clase derivada.
El orden de las llamadas a los constructores es importante. Cuando utilizamos los mecanismos de herencia, sabemos todo
acerca de la clase base y podemos acceder a los miembros de tipo public y protected de la misma. Esto quiere decir que
debemos poder asumir que todos los dems miembros de la clase base son vlidos cuando los encontremos en la clase deri-
vada. En UD mtodo nonnal , el proceso de construccin ya ha tenido lugar, de modo que todos los miembros de todas las
partes del objeto habrn sido construidos. Sin embargo, dentro del constructor debemos poder estar seguros de que todos los
miembros que utilicemos hayan sido construidos. La nica fonna de garantizar esto es invocando primero al constmctor de
la clase base. Entonces, cuando nos encontremos dentro del constructor de la clase derivada, todos los miembros de la clase
base a los que queremos acceder ya habrn sido inicializados. Saber que todos los miembros son vlidos dentro del cons-
tructor es tambin la razn de que, siempre que sea posible, se deban inicializar todos los objetos miembro (los objetos
incluidos en la clase mediante los mecanismos de composicin) en su punto de definicin dentro de la clase (por ejemplo.
b, e y I en el ejemplo anterior). Si se ajusta a esta prctica a la hora de programar, le ser ms fcil garantizar que todos los
miembros de la clase base y objetos miembro del objeto actual hayan sido inicializados. Lamentablemente, este sistema no
nos permi te gestionar todos los casos, como veremos en la siguiente seccin.
Ejercicio 11: (1) Aada una clase Pickle a Sandwich.j ava.
Herencia y limpieza
Cuando se utili zan los mecani smos de composicin y de herencia para crear una nueva clase, la mayor parte de las veces
no tenemos que preocupamos por las tareas de limpieza; los subobjetos pueden nonnalmente dejarse para que los procese
el depurador de memoria. Sin embargo, si hay algn problema relativo a la limpieza, es necesario actuar con diligencia y
crear un mtodo dispose() (ste es el nombre que yo he seleccionado, pero usted puede utili zar cualquier otro que indique
que estamos deshacindonos del objeto) en la nueva clase. Y, con la herencia, es necesario sustituir dispose( ) en la clase
deri vada si necesitamos realizar alguna tarea de limpieza especial que tenga que tener lugar como parte de la depuracin de
memoria. Cuando se sustituya dispose() en una clase heredada, es importante acordarse de invocar la versin de dispose()
de la clase base, ya que en caso contrario las tareas de limpieza propias de la clase base no se llevarn a cabo. El siguiente
ejemplo ilustra esta situacin:
11 : polymorphism/ Frog. j ava
/1 Limpieza y herencia.
package polymorphism;
import static net.mindview.util.Print.*;
class Characteristic {
private String S;
Characteristic (String s ) {
this.s = S;
178 Piensa en Java
print ("Creating Characteristic " + s) i
protected void dispose()
print ( "disposing Characteristic " + s) i
class Description {
private String Si
Description {String s} {
this.s = Si
print ("Creating Description " + s) i
protected void dispose () {
print ("disposing Description " + s);
class LivingCr eature {
private Characteristic p
new Characteristic("is alive
"
) i
private Description t =
new Description("Basic Living Creature 'l) i
Li vingCreature () {
print (" Li vingCreature () ,, ) i
protected void dispose () {
print ( "LivingCreature dispose") i
t.di spose() i
p.dispose () i
class Animal extends LivingCreature {
private Characteristic p =
new Characteristic ("has heart") i
private Description t =
new Description ("Animal not Vegetable");
Animal () { print ("Animal () "); }
protected void dispose () {
print ("Animal dispose");
t. dispose () ;
p.dispose() i
super.dispose() i
class Amphibian extends Animal {
private Cha racteristic p =
ne w Characteristic ( "can live in water " ) i
private Description t =
new Description ( "Both water and land");
Amphibian () {
print ("Amphibian () " ) ;
protected void dispose()
print ( "Amphibian dispose");
t.dispose() i
p.dispose() ;
super.dispose() ;
public class Frog extends Amphibian {
privace Characteristic p = new Characteristic ( UCroaks" ) ;
private Description t = new Description("Eats Bugs") i
public Frog () { print (11 Frog () 11 ) ; }
protected void dispose () {
print ( 11 Frog dispose 11 ) ;
t .dispose () ;
p.dispose() ;
super.dispose() ;
public static void main (String [] args ) {
Frog frog = new Frog {);
print(IIBye! " ) ;
frog .dispose () i
/ * Output:
Creating Characteristic i5 alive
Creating Description Basie Living Creature
LivingCreature ()
Creating Characteristic has heart
Creating Description Animal nat Vegetable
Animal ()
Creating Characteristic can live in water
Creating Description 80th water and land
Amphibian ()
Creating Characteristic Croaks
Creating Description Eats Bugs
Frog()
Bye!
Frog dispose
disposing Description Eats Bugs
disposing Characteristic Croaks
Amphibian dispose
disposing Description Both water and land
disposing Characteristic can live in water
Animal dispose
disposing Description Animal not Vegetable
disposing Characteristic has heart
LivingCreature dispose
disposing Description Basic Living Crea tu re
disposing Characteristic is alive
*///,-
8 Polimorfismo 179
Cada clase de la jerarqua tambin conti ene objetos miembro de los tipos Characteristic y Description, que tambin habr
que borrar. El orden de borrado debe ser el inverso del orden de inicializacin, por si acaso uno de los subobjetos depende
del otro. Para los campos, esto quiere decir el inverso del orden de declaracin (puesto que los campos se ini cializan en el
orden de declaracin). Para las clases base (siguiendo la nonna utilizada en e++ para los destructores), debemos real izar
primero las tareas de limpi eza de la clase derivada y luego las de la clase base, La razn es que esas tareas de limpieza de
la clase derivada tuvieran que invocar algunos mtodos de la clase base que requieran que los componentes de la clase base
continen siendo accesibles, as que no debemos destruir esos componentes prematuramente. Analizando la salida podemos
ver que se borran todas las partes del objeto Frog en orden inverso al de creacin.
A partir de este ejemplo, podemos ver que aunque no siempre es necesario realizar tareas de limpieza, cuando se ll evan a
cabo es preciso hacerlo con un gran cuidado y una gran atencin.
180 Piensa en Java
Ejerc ic io 12: (3) Modifique el Ejerci cio 9 para que se muestre el orden de ini ciali zacin de las clases base y de las cl a-
ses deri vadas. Ahora aiiada obj etos miembro a las clases base y deri vadas, y muestre el orden en que se
ll eva a cabo la inicializacin durante el proceso de construccin.
Observe tambi n en el ejemplo anterior que un obj eto Frog "posee" sus objetos miembro: crea esos objetos miembro y sabe
durante culJto tiempo tienen que exi stir (tanto como dure el objeto Frog), de modo que sabe cundo invocar el mtodo
dispose() para borrar los objetos miembro. Sin embargo, si uno de estos obj etos miembro es compartido con otros objetos,
el probl ema se vuelve ms compl ejo y no podemos simplemente asumir que basta con invocar di spose(). En estos casos,
puede ser necesario un recuento de referencias para llevar la cuenta del nmero de obj etos que siguen pudi endo acceder a
un obj eto compartido. He aqu un ejemplo:
//: polymorphism/ReferenceCoun t i ng . java
// Limpieza de objetos miembro compartidos .
import static net.mindview. util.Pr int. *
class Shared {
private int refcount = O;
pr i vate static long counter = O;
private fi na l l ong id = counter++;
public Shared () {
pr i nt ("Creat ing " + this);
public void addRe f () { refcount++ i
prot ected void dispose () {
if{ - - r efcoun t == O)
print ("Disposing n + this) i
public String toString() { return "Shar ed " + id; }
class Compos i ng {
private Shared shared;
private stat ic long counter = O;
private final long id = coun ter++;
public Composing (Shared shared) {
print ("Creating " + this);
this.shared = shared
this . shared . addRef() ;
protected void dispose()
print ("disposing " + this)
shared.dispose() ;
public String toString () { return "Composing " + id; }
public class ReferenceCounting {
public static void main (String [] args) {
Shared shared = new Shared {}
Composi ng[ ] composing = { new Compos i ng(shared),
new Composing(shared}, ne w Composi ng{sharedl,
new Composing(shared}, new Compos i ng(shared) }
for(Composing c : composing)
c.dispose() ;
/ * Output :
Creating Shared O
Creating Composing O
Creating Composing 1
8 Polimorfismo 181
creating Composing 2
creating
Composing 3
creating Composing 4
disposing Composing O
disposing Composing 1
disposing Composing 2
disposing Composing 3
disposing Composing 4
Disposing Shared O
* /// ,-
El contador statie long counter lleva la cuenta del nmero de instancias de Shared que son creadas y tambin crea un valor
para id. El tipo de eounter es long en lugar de int, para evitar el desbordamiento (se trata slo de una buena prctica de
programacin: es bastante improbable que esos desbordamientos de contadores puedan producirse en ninguno de los ejem-
plos de eSle libro). La variable id es de tipo final porque no esperamos que cambie de valor durante el tiempo de vida del
objeto.
Cuando se asocia el objeto compartido a la clase, hay que acordarse de invocar addRef( ), pero el mtodo dispose( ) lleva-
r la cuenta del nmero de referencias y decidir cundo hay que proceder con las tareas de limpieza. Esta tcnica requiere
un cierta diligencia por nuestra parte, pero si estamos compartiendo objetos que necesiten que se ll eve a cabo una determi-
nada tarea de limpieza, no son muchas las opciones que tenemos.
Ejercicio 13: (3) Aada un mtodo finalize( ) a RefcrcnccCounting.java para verificar la condicin de rerminacin
(vase el Captulo S,inicializacin y limpieza).
Ejercici o 14: (4) Modifique el Ejercicio 12 para que uno de los objetos miembro sea un objeto compartido. Utilice el
mtodo de recuento del nmero de referencias y demuestre que funciona adecuadamente.
Comportamiento de los mtodos polimrficos dentro de los constructores
La jerarqua de llamada a constructores plantea un dilema interesante. Qu sucede si estamos dentro de un constmctor e
invocamos un mtodo con acoplamiento dinmico del objeto que est siendo constmido?
Dentro de un mtodo nonnal , la llamada con acoplamiento dinmico se resuelve en tiempo de ejecucin, porque el objeto
no puede saber si pertenece a la clase en la que se encuentra el mtodo o a alguna de las clases derivadas de la mi sma.
Si invocamos un mtodo con acoplamiento dinmico dentro de un constructor, tambin se utiliza la definicin sustituida de
dicho mtodo (es decir, la definicin del mtodo que se encuentra en la clase actual). Sin embargo, el efecto de eSla llama-
da puede ser inesperado, porque el mtodo sustituido ser invocado antes de que el objeto haya sido completamente cons-
truido. Esto puede hacer que queden acuitas algunos errores realmente dificiles de detectar.
Conceptualmente, la tarea del constructor es hacer que el objeto comience a existir (10 que no es una tarea trivial). Dentro
de cualquier constructor, puede que el objeto completo slo est fonnado parcialmente, ya que de lo nico que podemos
estar seguros es de que los objetos de la clase base han sido inicializados. Si el constructor es slo uno de los pasos a la hora
de constmir un objeto de una clase que haya sido derivada de la clase correspondiente a dicho constructor, las partes deri-
vadas no habrn sido todava inicializadas en el momento en que se invoque al constructor actual. Sin embargo, una llama-
da a un mtodo con acoplamiento dinmico se "adentra" en la jerarqua de herencia, invocando un mtodo dentro de una
clase derivada. Si hacemos esto dentro de un constructor, podramos estar invocando un mtodo que manipulara miembros
que todava no han sido inicializados, lo cual constituye una receta segura para que se produzca un desastre.
Podemos ver el problema en el siguiente ejemplo:
JJ : polymorphismJPolyConstructors.java
JJ Los constructores en presencia de pOlimorfismo
JJ pueden no producir los resultados esperados.
import static net.mindview.util.Print.*;
class Glyph {
void draw() { print(nGlyph.draw() "); }
Glyph () {
182 Piensa en Java
print ( "Glyph () befo re draw () " ) ;
draw (} ;
pri nt ( "Glyph () after draw () " ) i
class RoundGlyph extends Glyph
private int radius '" 1;
RoundGlyph ( int r ) {
radius '" r;
print ( "RoundGl}'Ph. RoundGlyph () I radius
void draw () {
11 + radius ) ;
print ( "RoundGlyph. draw () I radius 11 + radius ) ;
public class PolyConstructors {
public static void main (String [] args) {
new RoundGlyph(S ) ;
/ * Output:
Glyph () before draw ()
RoundGlyph . draw(), radius = o
Glyph () after draw ()
RoundGlyph. RoundGlyph () ,radius 5
* ///,-
Glyph.draw() est diseado para ser sustituido, lo que se produce en RoundGlyph. Pero el constructor de Glyph invoca
este mtodo y la llamada tennina en RoundGlyph.draw(), que parece que fuera la intencin originaL Pero si examinamos
la sal ida, podemos ver que cuando el constructor de Glyph invoca draw(), el valor de radius no es ni siqui era el valor ini-
cial predetenninado de 1, sino que es O. Esto provocar, probablemente, que se dibuje en la pantalla un punto, o nada en
absoluto, con lo que el programador se quedar contemplndolo tratando de imaginar por qu no funciona el programa.
El orden de inicializacin descrito en la seccin anterior no est completo del todo, y ah es donde radica la clave para resol-
ver el misterio. El proceso real de inicializacin es:
1. El almacenamiento asignado al objeto se inicializa con ceros binarios antes de que suceda ninguna otra cosa.
2. Los constructores de las clases base se invocan tal y como hemos descrito anteriormente. En este punto se invo-
ca el mtodo sustituido draw( ) (s, se invoca antes de que ll ame al constructor de RoundGlyph) y ste descu-
bre que el valor de radius es cero, debido al Paso 1.
3. Los inicializadores de los miembros se invocan segn el orden de declaracin.
4. Se invoca el cuerpo del constructor de la clase derivada.
La parte buena de todo esto es que todo se inicializa al menos con cero (o con lo que cero signifique para ese tipo de datos
concreto) y no simplemente con datos aleatorios. Esto incluye las referencias a objetos que han sido incluidas en una clase
a travs del mecanismo de composicin, que tendrn el valor null. Por tanto, si nos olvidamos de inicializar esa referencia,
se generar una excepcin en tiempo de ejecucin. Todo lo dems tomar el valor cero, lo que usualmente nos sirve como
pista a la hora de examinar la salida.
Por otro lado, es posible que el programador se quede horrorizado al ver la salida de este programa: hemos hecho algo per-
fectamente lgico, a pesar de lo cual el comportamiento es misteriosamente errneo, sin que el compilador se haya queja-
do (e++ produce un comportamiento ms racional en esta situacin). Los errores de este tipo podran quedar ocultos
fcilmente, necesitndose una gran cantidad de tiempo para descubrirlos.
Como resultado, una buena directriz a la hora de implementar los constructores es: "Haz lo menos posible para garantizar
que el objeto se encuentre en un estado correcto y, siempre que puedas evitarlo, no invoques ningn otro mtodo de esta
clase". Los nicos mtodos seguros que se pueden invocar dentro de un constructor son aquellos de tipo final en la clase
8 Polimorfismo 183
base (esto tambin se aplica a los mtodos privados, que son automticamente de tipo final ). Estos mtodos no pueden ser
sustituidos Y no pueden, por tanto, darnos este tipo de sorpresas. Puede que no siempre seamos capaces de seguir esta direc-
triz, pero al menos debemos tratar de cumplirl a.
Ejercici o 15: (2) Aada una clase Recta ngularGlyph a PolyCoostructors.java e ilustre el problema descrito en esta
seccin.
Tipos de retorno covariantes
Java SES aade los denominados tipos de retorno covarianles, lo que quiere decir que un mtodo sustinlido en una clase
derivada puede devolver un tipo deri vado del tipo devuelto por el mtodo de la clase base:
JI : polymorphism/CovariantReturn .java
class Grai n {
public String toString () { rE"turn "Grain" i }
class Wheat extends Grain {
public String toString () { return "Wheat"; }
cIass MilI {
Grain p r ocess () { return new Grain () }
class WheatMil1 extends MilI {
Wheat process () { return new Wheat {) ; }
public class CovariantRetur n {
public static void main{String[] args ) {
MilI m = new Mill (}
Grain 9 = m.process{}
System.out.println(g) ;
m = new WheatMill {) i
9 = m.process()
System. out.println{g) ;
/ * Output:
Grain
Wheat
*// /0-
La diferencia clave entre Java SE5 y las versiones anteriores es que en stas se obligara a que la versin sustituida de
process( ) devolviera Grain, en lugar de Wheat, a pesar de que Wheat deriva de Graio y sigue siendo, por tanto. un tipo
de retomo legtimo. Los tipos de retomo covariantes penniten utilizar el tipo de retorno Wheat ms especfico.
Diseo de sistemas con herencia
Una vez que sabemos un poco sobre el polimorfismo, puede llegar a parecemos que todo debera heredarse, ya que el poli-
morfismo es una herramienta tan inteligente. Pero la realidad es que esto puede complicar nuestros di seos innecesariamen-
te, de hecho, si decidimos utilizar la herencia como primera opcin a la hora de utilizar una clase existente con el fin de
formar otra nueva, las cosas pueden volverse innecesariamente complicadas.
Una tcnica mejor consiste en tratar de utilizar primero la composicin, especialmente cuando no resulte obvio cul de los
dos mecanismos debera emplearse. La composicin no hace que el diseo tenga que adoptar una jerarqua de herenci a.
Pero, asimismo, la composicin es ms flexible, porque permite seleccionar dinmicamente un tipo (y por tanto un compor-
184 Piensa en Java
tamiento), mientras que la herencia exige que se conozca un tipo exacto en tiempo de compi lacin. El sigui ente ejemplo
ilustra esto:
jI : polymorphism/Transmogrify.java
/1 Modificacin dinmica del comportamiento de un objeto
/1 mediante la composicin (el patrn de diseo basado en estados).
import static net.mindview.util.Print.*
class Actor {
public void act () {}
class HappyActor extends Act or {
public void act () { print ( "HappyActor") i
class SadActor extends Actor {
public void act {) { print ( "SadActor
ll
);
class Stage {
private Ac t or actor = new HappyActor {);
public void change () { actor = new SadActor () ;
public void performPlay () { actor. act () ; }
public c l ass Transmogrify {
public static void main (String (] args) {
Stage stage = new Stage()
stage.performPlay() ;
stage.change() ;
stage .performPlay() ;
/ * Output :
HappyActor
SadActor
' /1/ , -
Un objeto Stage contiene una referencia a un objeto Actor, que se inicializa para que apunte a un objeto HappyActor. Esto
significa que performPlay() produce un comportamiento concreto. Pero, como una referencia puede redirigirse a un obje-
to di stinto en tiempo de ejecucin, podramos almacenar una referencia a un objeto SadActor en actor, y entonces el com-
portamiento producido por performPlay() variara. Por tanto, obtenemos una mayor flexibilidad dinmica en liempo de
ejecucin (esto se denomina tambin patrn de diseo basado en estados, consulte Thinking in Patterns (with Java) en
www.MindView.net) . Por contraste, no podemos decidir realizar la herenci a de fonna diferente en tiempo de ejecucin, el
mecanismo de herencia debe estar perfectamente determinado en tiempo de compilacin.
Una regla general sera: "Utilice la herencia para expresar las diferencias en comportamiento y los campos para expresar las
variaciones en el estado". En el ejemplo anterior se utilizan ambos mecanjsmos; definimos mediante herencia dos clases dis-
tintas para expresar la diferencia en el mtodo act() y Stage utiliza la compos icin para permitir que su estado sea modifi-
cado. Dicho cambio de estado, en este caso, produce un cambio de comportamiento.
Ejercicio 16: (3) Siguiendo eJ ejempJ o de Transmogrify.java, cree una cJase Starship que contenga una referencia
AJertStatus que pueda indicar tres estados di stintos. Incluya mtodos para verificar los estados.
Sustitucin y extensin
Podra parecer que la forma ms limpia de crear una jerarqua de herencia sera adoptar un enfoque "puro"; es decir, slo
los mtodos que hayan sido establecidos en la clase base sern sustituidos en la clase derivada, como puede verse en este
diagrama:
8 Polimorfismo 185
Shape
draw()
erase()
I I
Circle Square Triangle
draw() draw() draw()
erase() erase() erase()
Esto podra decirse que es una relacin de tipo "es-un" porque la interfaz de una clase establece lo que dicha clase es. La
herenc ia garant iza que cualquier clase derivada tendr la interfaz de la clase base y nada ms. Si seguimos este diagrama,
las clases derivadas no tendrn nada ms que lo que la interfaz de la clase base ofrezca.
Esto podra considerarse como una sustitucin pura, porque podemos sustituir perfectamente un objeto de la clase base o
un objeto de una clase deri vada y no nos hace falta conocer ninguna infomlcin adicional acerca de las subclases a la hora
de utilizarlas:
Habla con Shape --------------------,.. Circle, Square, Une o un nuevo tipo de Shape
Mensaje
Relacin ~ e s u n
En otras palabras, la clase base puede recibir cualquier mensaje que enviemos a la clase deri vada, porque las dos tienen
exactamente la misma interfaz. Debido a esto lo que tenemos que hacer es generalizar a partir de la clase derivada, sin
tener que preocuparnos de ver cul es el tipo exacto del objeto con el que estemos tratando. Todo se maneja medi ante el
pol imorfi smo.
Cuando vemos las cosas de esta fom18, debe parecer que las relaciones puras de tipo "es-un" son la fonna ms lgica de
impl ementar las cosas, y que cualquier otro tipo de diseo resulta confuso por comparacin. Pero esta forma de pensar es
un error. Tan pronto comencemos a pensar de esta foona, miraremos a nuestro alrededor y descubriremos que ampliar la
interfaz (mediante la palabra clave extends) es la perfecta solucin para un problema concreto. Este tipo de solucin podra
denominarse relacin de tipo "es-coma-un", porque la clase deri vada es como la clase base: tiene la misma interfaz elemen-
tal y tiene, adems, ot ras caractersticas que requi eren mtodos adicionales para implementarlas:
Uselul
voi d I()
void g()
MoreUselul
void I()
void g()
void u()
void vO
void w()
Suponga que esto
representa una
interfaz compl eja
"Es-coma-un"
Ampliacin
de la interfaz
186 Piensa en Java
Aunque este enfoque tambin resulta til y lgico (dependiendo de la situacin) tiene una desventaja. La parte ampliada de
la interfaz en la clase derivada no est di sponibl e en la clase base, por lo que, una vez que efectuemos una generalizacin
no podremos invocar los nuevos mtodos:
Habla con el objeto
Useful
-_._-_._ ............ ..
Mensaje
parte de Useful
parte eUseful
Si no estamos haci endo generalizaciones, no debe haber ningn problema, pero a menudo nos encontraremos en situacio-
nes en las que necesitamos descubrir el tipo exacto del objeto para poder acceder a los mtodos ampliados de dicho tipo. En
la siguiente seccin se explica cmo hacer esto.
Especializacin e informacin de tipos en tiempo de ejecucin
Puesto que perdemos la informacin especfica del tipo mediante el proceso de generalizacin (l/pcast, que consiste en
moverse hacia arriba por la jerarqua de herencia), tiene bastante sentido que para extraer la infonnacin de tipos; es decir,
para volver a descender por la jerarqua de herencia, utilicemos un proceso de especializacin (downcasf). Sin embargo,
sabemos que una generalizacin siempre es segura, porque la clase base no puede tener una interfaz ms amplia que la clase
derivada; por tanto, se garantiza que todo mensaje que enviemos a travs de la interfaz de la clase base ser aceptado. Pero
con una especializacin no sabemos realmente si una determinada fonna, por ejemplo, es un crculo u otra cosa: tambin
podra ser un tringulo, un cuadrado o algn otro tipo de forma.
Para resolver este problema, tiene que haber alguna manera de garantizar que la especializacin se efecte de fonna correc-
ta, de modo que no hagamos accidentalmente una proyeccin sobre el tipo inadecuado y luego enviemos un mensaje que el
objeto no pueda aceptar. Si no podemos garantizar que la especializacin se efecte de manera correcta, nuestro programa
no ser muy seguro.
En algunos lenguajes (como e++) es necesario realizar una operacin especial para poder ll evar a cabo una especializacin
de tipos de forma correcta, pero en Java todas las proyecciones de tipos se comprueban. Por tanto, aunque parezca que este-
mos utilizando simplemente una proyeccin de tipos nonnal, usando parntesis, dicha proyeccin se comprueba en tiempo
de ejecucin para garantizar que se trate, de hecho, del tipo que creemos que es. Si no lo es, se obtiene una excepcin
ClassCastException. Este acto de comprobacin de tipos en tiempo de ejecucin se denomina informacin de tipos en tiem-
po de ejecl/cin (RTTI, rl/ntime type in/ormation). El siguiente ejemplo ilustra el comportamiento de RTTI:
(( , polymorphism(RTTI . java
II Especializacin en informacin de tipos en tiempo de ejecucin (RTTI) .
(( {ThrowsException}
cIass UsefuI {
public void f 1) {}
public void g 1) {}
cIass MoreUseful extends
pubIic void f 1) { }
pubIic void gl) { }
public void ul! {}
public void vI! {}
pubIic void w() { }
pubIic cIass RTTI {
Use fuI {
pubIic static void main{String[] args) {
Useful[] x " {
new Use fuI () ,
new MoreUseful ()
} ;
x[O].f();
X[l].g () ;
/ 1 Tiempo de compilacin: mtodo no encontrado en Useful :
II ! x[l].u () ;
(( MoreUseful)x[l] ) . u () i // Especializacin/ RTTI
(( MoreUseful ) x[OJ) .u () ; 1/ Excepcin generada
}
111 ,-
8 Polimorfismo 187
Como en el diagrama anterior, MoreUseful ampla la int erfaz de Usefu). Pero. como se tTata de una clase heredada tam-
bin puede generalizarse a Useful. Podemos ver esta generalizacin en accin durante la inicializacin de la matriz x en
m.iu(). Puesto que ambos objetos de la matriz son de clase Useful , podemos enviar los mtodos f() Y g() a ambos. mien-
tras que si tratamos de invocar u() (que slo existe en MoreUseful), obtendremos un mensaje de error en tiempo de com-
pilacin.
Si queremos acceder a la interfaz ampliada de un objeto MoreUseful , podemos tratar de efectuar una especializacin. Si se
trata del tipo correcto. la operacin tendr xito. En caso contrario, obtendremos una excepcin ClassCastException. No
es necesario escribir ningtin cdigo especial para esta excepcin. ya que indica un error del programador que puede produ-
cirse en cualquier lugar del programa. La etiqueta de comentario {ThrowsExcept-ion} le dice al sistema de construccin de
los ejemplos de este libro que cabe esperar que este programa genere una excepcin al ejecutarse.
El mecanismo RTTI es ms complejo de 10 que este ejemplo de proyeccin simple pennite intuir. Por ejemplo, existe una
fonna de ver cul es el tipo con el que estamos tratando antes de efectuar la especializacin. El Captulo 14, Informacin
de (ipos est dedicado al estudio de los diferentes aspectos de la infoonacin de tipos en tiempo de ejecucin en Java.
Ejercicio 17: (2) Utilizando la jerarqua Cyele del Ejercicio 1, aliada un mtodo balance() a Unicyele y Bicyele, pero
Resumen
no a Tricycle. Cree instancias de los tres tipos y generalcelas para formar una matriz de objetos Cyclc.
Trate de invocar balance() en cada elemento de la matriz y observe los resultados. Realice una especia-
li zacin e invoque balance( ) y observe lo que sucede.
Polimorfismo significa "diferentes f0n11as". En la programacin orientada a objetos, tenemos una misma interfaz definida
en la clase base y diferentes fonnas que utilizan dicha interfaz: las diferentes versiones de los mtodos dinmicamente aco-
pIados.
Hemos visto en este captulo que resulta imposible comprender. o incluso crear, un ejemplo de polimorfismo sin utilizar la
abstraccin de datos y la herencia. El polimorfi smo es una caracterstica que no puede anali zarse de manera ai slada (a dife-
rencia, por ejemplo, del anli sis de la instruccin switch), sino que funciona de manera concertada, como parte del esque-
ma global de relaciones de clases.
Para usar el polimorfi smo, y por tanto las tcnicas de orientacin a objetos, de manera efectiva en los programas, es nece-
sario ampliar nuestra vi sin del concepto de programacin, para incluir no slo los miembros de una clase indi vidual, sino
tambi n los aspectos comunes de las distintas clases y las relaciones que tienen entre s. Aunque esto requiere un esfuerzo
significativo, se trata de un esfuerzo que merece la pena. Los resultados sern una mayor velocidad a la hora de desarrollar
programas, una mejor organi zac in del cdigo y la posibilidad de di sponer de programas ampliabl es, y un mantenimiento
del cdigo ms eficiente.
Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico rhe rhillkillg in Jav(I Annotafed So/lIti01l Guide, disponible para
la venia en
Interfaces
Las interfaces y las clases abstractas proporcionan una fonna ms estructurada de separar la
interfaz de la implementacin.
Di chos mecanismos no son tan comunes en los lenguajes de programacin. C++-, por ejemplo, slo tiene soporte indirecto
para estos conceptos. El hecho de que existan palabras clave del lenguaje en Java para estos conceptos indica que esas ideas
fueron consideradas lo suficientemente importantes como para proporcionar un soporte directo.
En primer lugar, vamos a examinar el concepto de clase abstracta, que es una clase de tnnino medio entre una clase nor-
mal y una interfaz. Aunque nuestro primer impul so pudiera ser crear una interfaz, la clase abstracta constituye una herra-
mienta importante y necesaria para construir clases que tengan algunos mtodos no implementados. No siempre podemos
utili zar una interfaz pura.
Clases abstractas y mtodos abstractos
En todos los ejemplos de "i nstmmentos musicales" del captulo anterior,los mtodos de la clase base Instrument eran siem-
pre "ficticios". Si estos mtodos llegan a ser invocados, es que hemos hecho algo mal. La razn es que lnstrument no tiene
otro sentido que crear una intelfa= comln para todas las clases derivadas de ella.
En dichos ejemplos, la nica razn para establecer esta interfaz comn es para poder expresarla de manera diferente para
cada uno de los di stintos subtipos. Esa interfaz establece una forma bsica, de modo que podemos expresar todo aquello que
es comn para todas las clases deri vadas. Otra fonna de decir esto sera decir que Instrument es una clase base abstracta.
o simplemente una clase abstracta.
Si tenemos una clase abstracta como Instrument, los objetos de dicha clase especfica no tienen ningn significado propio
casi nunca. Creamos una clase abstracta cuando queremos manipular un conjunto de clases a travs de su interfaz comn.
Por tanto, el propsito de lnstrument consiste simplemente en expresar la interfaz y no en una implementacin concreta.
por lo que no tiene sentido crear un objeto Instr ument y probablemente convenga impedir que el usuario pueda hacerlo.
Podemos impedirlo haciendo que todos los mtodos de Instrument generen errores, pero eso retarda la infonnaci n hasta
el momento de la ejecucin y requiere que el usuario realice pruebas exhaustivas y fiables. Generalmente, resulta preferible
detectar los problemas en tiempo de compil acin.
Java proporciona un mecani smo para hacer esto denominado mtodo abstracto. Se trata de un mtodo que es incompl eto:
slo tiene una declaracin, y no di spone de un cuerpo. He aqu la si ntaxi s para la declaracin de un mtodo abstracto:
abstract void f();
Una clase que contenga mtodos abstractos se denomina clase abstracta. Si una clase contiene uno o ms mtodos abstrac-
tos, la propia clase debe calificarse como abstraet, (en caso contrario, el compilador generar un mensaje de error).
Si una clase abstracta est incompleta, qu es 10 que se supone que el compi lador debe hacer cuando alguien [rate de ins-
ranciar un objeto de esa clase? El compilador no puede crear de manera segura un objeto de una clase abstracta, por lo que
I Para los programadores de C++, se trata del anftlogo a lasjimciones 'irrita/es puras de C++.
190 Piensa en Java
generar un mensaje de error. De esta fonna. el compil ador garal1li za la pureza de la clase abstracta y no es necesario preo
cuparse de si se la va a utilizar correctamente.
Si definimos una clase heredada de una clase abstracta y queremos construir objelos del nuevo tipo. deberemos proporcio
nar defini ciones de mtodos para todos los mtodos abstractos de la clase base. Si no lo hacemos (y podemos decidir no
hacerlo). el1lonces la clase derivada ser tambi n abstracta. y el compilador nos obligar a calificar esa clase con la pal abra
clave abstrael.
Resulta posible definir una clase como abstracta sin incluir ningn mtodo abstracto. Esto resulta til cuando tenemos una
clase en la que no tiene sentido tener ningn mtodo abstracto y. sin embargo. queremos evitar que se generen instancias de
dicha clase.
La clase Instrument del captulo ant eri or puede lransfonnarse fcilmente en una clase abstracta. Slo algunos de los mto
dos sern abstracTOs, ya que definir una clase como abstracta no obli ga a que todos los mtodos sean abstractos. He aqu el
ejempl o modificado:
abstraet lnstrument
abstract void playO;
String whatO r ... '1}
abstraet voi d adjustO;
exte ds exte nds ext nds
Wind Percussion Stringed
void playO voi d playO void playO
String whatO String whatO String whatO
void adjustO void adjustO void adjustO
ext nds
extr nds
Woodwind Brass
void playO void playO
String whatO voi d adjustO
He aqu el ejempl o de la orquesta modificado para uti lizar clases y mtodos abstractos:
/1 : interfaces/ musie4 / Musie4.java
1/ Clases y mtodos abstractos.
package interfaees.musie4
import polymorphism.musie.Note
import statie net.mindview.util.Print.*;
abstraet elass Instrument {
private int i /1 Storage allocated for eaeh
publie abstraet void play {Note n)
publie String what () { return "Instrument" }
publie abstraet void adjust () ;
class Wind extends Instrument
public void play (Note n ) {
print ("Wind. play () + n);
public String what () { return "Wind" i }
public void adjust () {}
class Percussion extends Instrument {
public void play (Note n) {
print(IIPercussion.playO " + n) i
public String what () { return npercussion";
public void adjust() {}
clasE Stringed extends Instrument {
public void play (Note n ) {
print (" Stringed. play () 11 + n) i
public String what () { return "Stringed!1;
public void adj ust () {}
class Brass extends Wind {
public void play{Note n)
print (JI Brass. play () 11 + n) i
public void adjust() { print("Brass.adjust() n);
class Woodwind extends Wind {
public void play (Note n) {
print("Woodwind.play() " + n);
public String what () { return "Woodwind 11 i
public class Music4 {
// No me preocupa el tipo, por lo que los nuevos tipos
/1 aadidos al sistema seguirn funcionando:
static void tune (Instrument i) {
// ...
i.play{Note.MIDDLE_C) i
static void tuneAll (Instrument [J e) {
tor (Instrument i e)
tune (i) i
public static void main(String[] args)
II Generalizacin durante la insercin en la matriz:
Instrument[] orchestra = {
new Wind (),
};
new Percussion(),
new Stringed () ,
new Brass () ,
new Woodwind ()
tuneAll(orchestra) i
9 Interfaces 191
192 Piensa en Java
} / * Output,
Wind.play () MIDDLE_C
Percussion.play () MIDDLE_C
Stringed.play () MIDDLE_C
Brass.play () MIDDLE_C
Woodwind.play () MIDDLE_C
* /// , -
Podemos ver que no se ha efecnlado ningn cambio, salvo en la clase base.
Resulta til crear clases y mtodos abstractos porque hacen que la abstraccin de una clase sea explcita, e nfannan tanto
al usuario como al compilador acerca de cmo se pretende que se utilice esa clase. Las clases abstractas tambin resultan
tiles como herramientas de rediseo, ya que permiten mover fcilmente los mtodos comunes hacia arriba en la jerarqua
de herencia.
Ejercicio 1:
Ejercicio 2:
Ejercicio 3:
Ejercicio 4:
Interfaces
(1) Modifique el Ejercicio 9 del captulo anterior de modo que Rodent sea una clase abstracta. Defina los
mtodos de Rodent como abstractos siempre que sea posible.
(1) Cree una clase abstracta sin incluir ningn mtodo abstracto y verifique que no pueden crearse instan-
cias de esa clase.
(2) Cree una clase base con un mtodo print( ) abstracto que se sustituye en una clase deri vada. La ver-
sin sustituida del mtodo debe imprimir el valor de una variable int definida en la clase derivada. En el
punto de definicin de esta vari able, proporcione un valor di stinto de cero. En el constructor de la clase
base, ll ame a este mtodo. En main( ), cree un objeto del tipo derivado y luego invoque su mtodo
print( ). Expl ique los resultados.
(3) Cree una clase abstracta sin mtodos. Defllla una clase derivada y andale un mtodo. Cree un mto-
do esttico que tome una referencia a la clase base, especialcelo para que apunte a la clase derivada e
invoque el mtodo. En main( ), demuestre que este mecanismo funciona. Ahora, incluya la declaracin
abstracta del mtodo en la clase base, eliminando as la necesidad de la especiali zacin.
La palabra clave interface lleva el concepto de abstraccin un paso ms all. La palabra clave abstract pennite crear uno
o ms mtodos no definidos dentro de una clase: proporcionamos parte de la interfaz, pero sin proporcionar la implementa-
cin correspondiente. La implementacin se proporciona de las clases que hereden de la clase actual. La palabra clave inter-
face produce una clase compl etamente abstracta, que no proporciona ninguna implementacin en absoluto. Las interfaces
pemten al creador determinar los nombres de los mtodos, las li stas de argumentos y los tipos de retomo, pero si n especi-
ficar ningn cuerpo de ningull mtodo. Una interfaz proporciona simplemente una fomla, sin ninguna implementacin.
Lo que las interfaces hacen es decir: "Todas las clases que implementen esta interfaz concreta tendrn este aspecto". Por
tanto. cualqui er cdigo que utili ce una interfaz concreta sabr qu mtodos pueden invocarse para di cha interfaz yeso es
todo. Por tanto, la interfaz se utiliza para establecer un "protocolo" entre las clases (algunos lenguaj es de programacin
orientados a objetos di sponen de una palabra clave denominada pr%col para hacer lo mi smo).
Sin embargo, una interfaz es algo ms que simplemente una clase abstracta ll evada hasta el extremo, ya que permite
reali zar una variante del mecanismo de "herencia mltiple" creando una clase que pueda generalizarse a ms de un tipo
base.
Para crear una interfaz, utilice la palabra clave interface en lugar de class. Al igual que COIl una clase, puede aadir la pala-
bra clave public antes de interface (pero slo si dicha interfaz est definida en un archivo del mi smo nombre). Si no inclui -
mos la palabra clave public, obtendremos un acceso de tipo paquete, porque la interfaz slo ser utilizable dentro del mismo
paquete. Una int erfaz tambin puede contener campos, pero esos campos sern implci tamente de tipo static y final.
Para definir una clase que se adapte a una int erfaz concreta (o a un gmpo de interfaces concretas), utilice la palabra clave
implements que quiere decir: " La interfaz especifica cul es el aspecto, pero ahora vamos a decir cmo fimciono". Por lo
dems, la defini cin de la clase derivada se asemeja al mecani smo normal de herencia. El diagrama para el ejemplo de los
instnl mentos musicales sera el siguiente:
9 Interfaces 193
Instrument
void play();
String what();
void adjust();
imPleTents impl ments
Wind Percussion Stringed
void play() void play() void play()
String what() String what() String what()
void adjust() void adjust() void adjust()
ext nds
extf nds
Woodwind Brass
void play() void play()
String what() void adjust()
Podemos ver en las clases Woodwind y Brass que una vez que hemos implementado la interfaz, la implementacin pasa a
ser una clase normal que puede ampliarse de la forma usual.
Podemos declarar explcitamente los mtodos de una interfaz como public, pero esos mtodos sern pblicos an cuando
no lo especifiquemos. Por tanto, cuando implementemos una interfaz, los mtodos de esa interfaz deben estar definidos
como pblicos. En caso contrario, se revertira de f0n113 predeterminada al acceso de tipo paquete, con lo que estaramos
reduciendo la accesibilidad de los mtodos durante la herencia, cosa que el compilador de Java no permite.
Podemos ver esto en la versin modificada del ejemplo Instrumeot. Observe que todos los mtodos de la interfaz son estric-
tamente una declaracin, que es lo nico que el compilador pemite. Adems, ninguno de los mtodos de Instrument se
decl ara corno public, pero de lodos modos son pblicos de manera automtica:
11 : interfaces/ musicS/ MusicS . java
II Interfaces .
package interfaces.mus icS
i mport p olymorphism.music.Not e ;
i mport stati c net . mindview util . Print .*
int erface I nstrument {
II Constante de t i empo de compilacin:
int VALUE = 5; II static & final
II No puede tener definiciones de mtodos :
voi d play (Not e n); II Automticamente pblico
voi d adjust()
class Wind i mplements I nstrume nt {
public void p l ay (Note n ) {
print(thi s +".play () "+ n)
public String toString () { r eturn "Wind " }
publ i c void adjust () {print (this + ". adjust () " ) ;
194 Piensa en Java
class Percussion implements Instrument
publ ic void play (Note n) {
print (this + ".play() " + n);
public String toString() { return uPercussion"; }
public void adjust () { print (this + ". adjust () ") ;
class Stringed implements Instrument
public void play (Note n) {
print (this + ".play() " + n) i
public String toString () { return UStringed" i }
public void adjust () { print (this + ". adjust () ,,) ;
class Brass extends Wind {
public String toString () { return uBrass" i }
class Woodwind extends Wind {
public String toString () { return "Woodwind!l; }
public class MusicS {
II No le preocupa el tipo, por lo que los nuevos tipos
II que se aaden al sistema seguirn funcionando:
static void tune (Instrument i) {
//
i.play(Note.MIDDLE_C) ;
static void tuneAll (Instrument [] e) {
for(Instrument i : e)
tune (i);
public static void main (String [] args) {
II Generalizacin durante la insercin en la matriz:
Instrument[] orchestra = {
new Wind(),
new Percussion(),
new Stringed () ,
new Brass () ,
new Woodwind ()
) ;
tuneAll(orchestra) ;
1* Output:
Wind.play() MIDDLE_C
Percussion.play(} MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
* ///,-
En esta versin del ejemplo hemos hecho otro cambio: el mtodo \Vhat() ha sido cambiado a toString(), dado que esa era
la fOnTIa en que se estaba utilizando el mtodo. Puesto que toString() forma parte de la clase raz Object, no necesita apa-
recer en la interfaz.
El resto del cdigo funciona de la misma manera. Observe que no importa si estamos generalizando a una clase "nonnar'
denominada Instrument. a una clase abstracta llamada lnstrurncnt, o a una interfaz denominada Instrument. El compor-
9 Interfaces 195
tamiento es siempre el mismo. De hecho. podemos ver en el mtodo lune( ) que no existe ninguna c\idencia acerca de si
Instrumcnt es una clase "nannal", una clase abstracta o una interfaz.
Ejercicio 5: (2) Cree una interfaz que contenga Ires mtodos en su propio paquete. Implemente la interfaz en un paque-
te diferente.
Ejercicio 6: (2) Demuestre que lodos los mtodos de una merfaz son automticamente pblicos.
Ejercicio 7: ( 1) Modifique el Ejercicio 9 del Captulo 8. Polimorfismo, para que Roden! sea una interfaz.
Ejercicio 8: (2) En polymorphism'sandwich.java. cree una interfaz denominada FastFood (con los mtodos apro-
piados) y cambie Sandwich de modo que tambin implemente FastFood.
Ejercicio 9: (3) Redisee Music5.jav3 moviendo los mtodos comunes de \Vind. Percussion y Stringed a una clase
abstracta.
Ejercicio 10: (3) Modifique MusicS.java aadiendo una interfaz Pla)' able. Mueva la declaracin de playO de
lnstrument a Playable. Afiada Playable a las clases derivadas incluyndola en la li sta implements.
Modifique tune() de modo que acepte un objeto Playable en lugar de un objeto Instrument.
Desacoplamiento completo
Cuando un mtodo funciona con una clase en lugar de con una interfaz, estamos limitados a utili zar dicha clase o sus sub-
clases. Si quisiramos apli car ese mtodo a una clase que no se encontrara en esa jerarqua, no podramos. Las int erfaces
relajan esta restri ccin considerabl emente. Como resultado, permiten escribir cdigo ms reutil izable.
Por ejemplo. suponga que disponemos de una clase Processor que ti ene sendos mtodos Dame () y process( ) que toman
una cierta entrada, la modifican y generan una salida. La clase base se puede ampli ar para crear diferentes tipos de objetos
Processor . En este caso, los subt ipos de Processor modifican objetos de tipo String (observe que los tipos de retomo pue-
den ser covariantes, pero no los tipos de argumentos):
11: interfaces/classprocessor/Apply.java
package interfaces.classprocessor
import java.util.*
import static net.mindview util.Print.*
class Processor {
public String name()
return getClass () . getSimpleName () ;
Object process (Object input) { return input }
class Upcase extends Processor {
String process (Obj ect inputl { l/Retorno covariante
return ((Stringl input ) .toUpperCase ();
class Downcase extends Processor {
String process (Object input ) {
return String) input) . toLowerCase () ;
class Splitter extends Processor {
String process (Object input) {
II El mtodo split() divide una cadena en fragmentos:
return Arrays. toString ( ( (String) input) . spl i t ( " tl
196 Piensa en Java
public class Apply {
public stacic void process (Processor p, Object s ) {
print ( "Using Processor 11 + p. name () ) i
print (p.process {s )) ;
public sta tic String s
"Disagreement with beliefs i5 by definition incorrect";
public static void main (String [ ) args ) {
process (new Upcase () , s ) i
process (new Downcase () , s) i
process (new Splitter() I s ) i
1* Output:
Using Processor Upcase
DISAGREEMENT WITH BELIEFS IS BY DEFINITION INCORRECT
Using Processor Downcase
disagreement witb beliefs ls by definition incorrect
Using Processor Splitter
[Disagreement, wich, beliefs, is, by, definition, incorrect]
* /// ,-
El mtodo Apply.process() toma cualquier tipo de objeto Processor y lo aplica a un objeto Object, imprimiendo despus
los resultados. La creacin de un mtodo que se comporte de fonna diferente dependiendo del objeto argumento que se le
pase es lo que se denomina el patrn de diseo basado en estrategias. El mtodo conti ene la parte fija del algoritmo que hay
que implementar, mientras que la estrategia cOOliene la parte que varia. La estrategia es el objeto que pasamos, y que con-
tiene el cdigo que hay que ejecutar. Aqu , el obj eto Processor es la estrategia y en main( ) podemos ver cmo se aplican
tres estrategias diferentes a la cadena de caracteres s.
El mtodo split( ) es parte de la clase String; toma el objeto String y lo divide utilizando el argumento como frontera, y
devolviendo una matri z Stringll . Se utiUza aqu como forma abreviada de crear una matriz de objetos String.
Ahora suponga que descubrimos un conjunto de filtros electrnicos que pudieran encajar en nuestro mtodo Apply.process( ):
/ / : interfaces/ filters / Waveform.java
package interfaces.filters;
public class Waveform {
private static long counter;
private final long id = counter++;
public String t oString () { return "Waveform " + id; }
1/ / , -
// : interfaces/ filters / Filter.java
package interfaces.filters;
public class Filter {
public String name ()
return getClass () .getSimpleName () ;
public Waveform process (Waveform input ) { return input; }
/ / />
// : interfaces/ filters / LowPass.java
package interfaces.filters;
public class LowPass extends Filter {
double cutoff;
public LowPass (double cutoff) { this . cutoff
public Waveform process (Waveform input )
return input; // Dummy processing
cutoff; }
/1: interfacesjfilters/HighPass.java
package interfaces.filtersi
public class HighPass extends Filter {
double cutoff;
public HighPass (double cutoff) { this. cutoEf :: cutoff i }
public Waveform process (Waveform input) { return input; }
///,-
1/: interfaces/filters/BandPass.java
package interfaces.filtersi
public class BandPass extends Filter {
double lowCutoff, highCutoff;
public BandPass (double lowCut, double highCut) {
lowCutoff = lowCut
highCutoff = highCut
public Waveform process (Waveforrn input) { return input; }
/// , -
9 Interfaces 197
Filter tiene los mi smos elementos de interfaz que Processor, pero puesto que no hereda de Processor (puesto que el crea-
dor de la clase Filter no tena ni idea de que podramos querer usar esos objetos como objetos Processor). no podemos uti-
lizar un obj eto Filter con el mtodo Apply.process( ), a pesar de que funcionara. Bsicamente, el acoplamiento entre
Apply. process( ) y Processor es ms fuerte de lo necesario y esto impide que el cdigo de Apply.processO pueda reutili-
zarse en lugares que sera til. Observe tambin que las entradas y salidas son en ambos casos de tipo Waveform.
Sin embargo, si Processor es una interfaz, las restricciones se relajan lo suficiente como para poder reutili zar un mtodo
Apply. process( ) que acepte dicha interfaz. He aqu las versiones modificadas de Processor y Apply:
//: interfaces/interfaceprocessor/Processor . java
package interfaces.interfaceprocessor;
public interface Proeessor {
String name () ;
Objeet process(Object input);
/// ,-
//: interfaces/interfaeeproeessor/Apply.java
package interfaees.interfaceprocessor
import static net.mindview.util.Print .*
public class Apply {
public static void process(Proeessor p, Objeet s) {
print ("Using Processor ti + p. name () ) ;
print(p.process(s ;
La primera fonna en que podemos reutilizar el cdi go es si los programadores de clientes pueden escribir sus clases para
que se adapten a la int erfaz, como por ejemplo:
//: interfaces/interfaceproeessor/StringProcessor.java
package interfaces.interfaeeprocessor;
import java.util.*;
publie abstraet class StringProcessor implements Processor{
publ ic String name () {
return getClass () . getSimpleName ()
public abstraet String proeess(Objeet input);
public static String s
"If she weighs the same as a duck, shets made of wood";
198 Piensa en Java
public static void main(String[] args)
Apply.process(new Upcase(), s);
Apply.process{new Downcase(), s);
Apply.process(new Splitter(), s);
class Upcase extends StringProcessor {
public String process (Object input) { / / Retorno covariante
return ((String) input) . toUpperCase () ;
class Downcase extends StringProcessor {
public String process (Object input) {
return ((String) input) . toLowerCase() ;
class Splitter extends StringProcessor {
public String process (Object input ) {
return Arrays. toStrng ( ( (String) input ) . split (It " )) ;
1* Output:
Using Processor Upcase
IF SHE WEIGHS THE SAME AS A DUCK, SHE'S MACE OF WOOD
Using Processor Downcase
if she weighs the same as a duck, she's made of wood
Using Processor Splitter
[If, she, weighs, the, same, as, a, duck, , she's, made, of, wood]
* //1 , -
Sin embargo, a menudo nos encontramos en una si tuacin en la que no podemos modificar las clases que queremos usar.
En el caso de los filtros electrnicos. por ejemplo, la correspondiente biblioteca la hemos descubierto, en lugar de desarro-
llarla. En estos casos, podemos utilizar el patrn de di seiio adaprador. Con dicho patrn de diseo, lo que hacemos es escri-
bir cdigo para lOmar la interfaz de la que di sponemos y producir la que necesitamos, como por ejemplo:
//: interfaces/interfaceprocessor/FilterProcessor.java
package interfaces.interfaceprocessor;
import interfaces.fiIters.*
cIass FiIterAdapter implements Processor
Filter filter
public FilterAdapter(Filter filter) {
this.filter : filter
public String nameO { return filter.name()
public Waveform process (Object input) {
return filter.process((Waveform)input);
public class FilterProcessor {
public static void main(String[] args) {
Waveform w = new Waveform();
Apply.process(new FilterAdapter(new LowPass(l.O)), w);
Apply.process(new FilterAdapter (new HighPass(2.0)}, w)
Apply.process(
new FilterAdapter{new BandPass{3 . 0, 4.0 )), w) i
} / * Output,
Using Processor LowPass
Waveform o
Using Processor HighPass
Waveform o
Using Processor BandPass
Waveform O
* /// >
9 Interfaces 199
En esta aplicacin, el patrn de di seo de adaptacin, el constmctorFilterAdapter, toma la interfaz que tenemos (Fi lter)
y produce un objeto que tiene la interfaz Processor que necesitamos. Observe tambin la utilizacin del mecanismo de dele-
gacin en la clase FilterAdapter.
Desacoplar la interfaz de la implementacin pennite aplicar las interfaces a mltiples implementaciones diferentes, con lo
que el cdigo es ms reutilizable.
Ejercicio 11: (4) Cree una clase con un mtodo que tome como argumento un objeto String y produzca un resultado en
el que se intercambie cada pareja de caracteres contenida en el argumento. Adapte la clase para que fun-
cione con interfaceprocessor.Apply.process().
"Herencia mltiple" en Java
Puesto que una interfaz no dispone de implementacin (es decir, no hay ningn almacenamiento asociado con una interfaz)
no hay nada que impida combinar varias interfaces. Esto resulta muy til en ocasiones, como por ejemplo cuando queremos
impl ementar el concepto "una x es una a y una b y una c". En e++, este acto de combinar mltiples interfaces de clase se
denomina herencia mltiple, y puede 1I egar a resultar muy completo, porque cada clase puede tener una implementacin.
En Java, podemos hacer lo mismo, pero slo una de las clases puede tener una implementacin, por lo que los problemas
de e++ no aparecen en Java cuando se combinan mltiples interfaces:
Clase base I interfaz 1
I
abstracta o concreta
I
interfaz 2 1 ...
.......
I intet n I
Mtodos de la clase base I interfaz 1 I interfaz 2 1
. . . I interfaz n I
En una clase deri vada, no estamos obligados a tener una clase base que sea abstracta o concreta (una que no tenga mtodos
abstractos). Pero si realizamos la herencia de algo que no sea una interfaz, slo podemos heredar de una de esas clases; los
restantes elementos base debern ser interfaces. Hay que colocar todos los nombres de interfaz detrs de la palabra clave
implements y separarlos mediante comas. Podemos incluir tantas interfaces como queramos y podemos realizar general i-
zaciones (upeast) a cada interfaz, porque cada una de esas interfaces representa un tipo independiente. El siguiente ejemplo
muestra una clase concreta que se combina con varias interfaces para producir una nueva clase:
11 : interfaces/Adventure.java
II Interfaces mltiples .
interface CanFight
void fight () i
interface CanSwim
void swim ( ) i
interface CanFly {
200 Piensa en Java
voidfly(l;
class ActionCharacter {
public void fight 11 {}
class Hero extends ActionCharacter
implements CanFight, CanSwim, CanFly
public void swim (1 {}
public void fly (1 {}
public class Adventure {
public static void t (CanFight x ) { x. fight () i
public static void u(CanSwim xl { x.swim (} i
public static void v (CanFly xl ( x. fly (); )
public static void w (ActionCharacter xl { x. f ight () ;
public static void main (String [] args) {
Hero h = new Hero( ) ;
t(hl; II
u (hl; II
v(hl; II
w(hl; II
)
111>
Treat
Treat
Treat
Treat
it as
it as
it as
it as
a CanFight
a CanSwim
a CanFly
an ActionCharacter
Puede ver que Bero combina la clase concreta ActionCharacter con las interfaces CanFight, CanSwim y CanFly.
Cuando se combina una clase concreta con illl erfaces de esta forma, la clase concreta debe expresarse en primer lugar y las
interfaces indicarse a continuacin (en caso contrario, el compilador nos dar un error).
La signatura de fight() es igual en la interfaz CanFight y en la clase ActionCharacter. Asimi smo, a fight() no se le pro-
porciona una definicin en Hero. Podemos ampliar una int erfaz, pero lo que obtenernos entonces ser otra interfaz. Cuando
queramos crear un objeto, todas las definiciones debern haber sido ya proporcionadas. Aunque "ero no proporciona expl-
citamente una defini cin para fight(), di cha definicin est incl uida en ActionCharacter; por tanto, es posi ble crear obj e-
tos Hero.
En la clase Adventure, podemos ver que hay cuatro mtodos que toman argumentos de las di stintas interfaces y de la clase
concreta. Cuando se crea un objeto Hero, se le puede pasar a cualquiera de estos mtodos, lo que significa que estar sien-
do generalizado en cada caso a cada una de las interfaces. Debido a la forma en que se di sean las interfaces en Java, este
mecani smo funciona sin que el programador tenga que preocuparse de nada.
Recuerde que una de las principales razones para utilizar interfaces es la que se ilustra en el ejemplo anterior: para reali zar
generalizaciones a ms de un tipo base (y poder di sfrutar de la fl exibilidad que esto proporciona). Sin embargo, una segun-
da razn para uti li zar interfaces coincide con la raZn por la que utili zamos clases base abstractas: para impedir que el pro-
gramador de cl ientes cree un objeto de esta clase y para establecer que slo se trata de una interfaz.
Esto hace que surja una cuestin: debemos utili zar una interfaz o una clase abstracta? Si resulta posibl e crear nuestra clase
base sin ninguna definicin de mtodo y sin ninguna vari able miembro, siempre son preferibles las interfaces a las clases
abstractas. De hecho, si sabemos que algo va a ser una clase base, podemos considerar si resultara conveni ente transfor-
marla en interfaz (hablaremos ms sobre este tema en el resumen del captulo).
Ejercicio 12: (2) En Adventure.java, aada una interfaz ll amada CanClimb, siguiendo el patrn de las otras int erfa-
ces.
Ejercicio 13: (2) Cree una interfaz y herede de ell a otras dos nuevas interfaces. Defina, mediante herencia mltipl e, una
tercera interfaz a partir de estas otras dos.
2
.2 Este ejemplo muestra cmo las interfaces evitan el denominado problema del rambo", que se presenta en el mecanismo de herencia mlti ple de C++.
9 Interfaces 201
Ampliacin de la interfaz mediante herencia
podemos aadir fcilmente nuevas dec laraciones de mtodos a una interfaz utilizando los mecani smos de herencia, y tam-
bin podemos combinar varias interfaces mediante herencia para crear una nueva interfaz. En ambos casos, obtendremos
una interfaz llueva, como se ve en el siguiente ejemplo:
/1 : interfaces/HorrorShow.java
JI Ampliacin de una interfaz mediante herencia.
interface Monster
void menace () ;
interface DangerousMonster extends Monster {
void destroy () i
interface Lechal
void kill () ;
c!ass DragonZilla implements DangerousMonster {
public void menace () {}
public void destroy () {}
interface Vampire extends DangerousMonster, Lethal {
void drinkBlood();
class VeryBadVampire implements Vampire {
public void mena ce () {}
pub1ic void destroy () {}
pub1ic void kill () {}
public void drinkB100d () {}
public class HorrorShow {
static void u(Monster b) { b.menace();
static void v (DangerousMonster d) {
d.menace() ;
d.destroy () ;
static void w(Letha1 1 ) { l.kill( ) ; }
public static void main(String[] args)
DangerousMonster barney = new DragonZilla() i
u (barney) i
)
v (barney) i
Vampire vlad
u (v1ad) ;
v(v1ad) ;
w (vladl ;
/// ,-
new VeryBadVampire() i
es una extensin simple de Monster que produce una nueva interfaz. sta se implementa en
DragonZilla.
202 Piensa en Java
La siI1laxis empleada en Vampire slo funciona cuando se heredan interfaces. Nonnalmente, slo podemos utilizar extends
con una nica clase. pero extends puede hacer referencia a mltiples interfaces base a la hora de construir una nueva inter-
faz. Como puede ver, los nombres de interfaz est simplemente separados por comas.
Ejercicio 14: (2) Cree tres interfaces, cada una de ellas con dos mtodos. Defina mediante herencia una nueva interfaz
que combine las tres, aadiendo un nuevo mtodo. Cree una clase implerncnrando la nueva interfaz y que
tambin herede de una clase concreta. A cont inuacin, escriba cuatro mtodos, cada uno de los cuales tome
una de las cuatro interfaces C01110 argumento. En main(), cree un objeto de esa clase y pselo a cada uno
de los mtodos.
Ejercicio 15: (2) Modifique el ejercicio anterior creando una clase abstracta y haciendo que la clase derivada herede de
ella.
Colisiones de nombres al combinar interfaces
Podernos encontramos con un pequeo problema a la hora de implementar mltiples interfaces. En el ejemplo anterior, tanto
CanFight como ActionCharacter tienen sendos mtodos idnticos void fight( ). El que haya dos mtodos idnticos no
resulta problemtico, pero qu sucede si los mtodos difieren en cuanto a signatura o en cuanto a tipo de retorno? He aqu
un ejemplo:
11 : interfaces/lnterfaceCollision.java
package interfaces;
interface Il void f(); }
interface I2 int f(int i) ;
interface I3 int f (); }
class e { public int f() {
class C2 implements 11, 12
public void f () {}
return 1 ; } }
public int f (int i) { return 1; } II sobrecargado
class C3 extends C implements 12 {
public int f (int i ) { return 1; } II sobrecargado
class C4 extends C implements 13
II I dntico. No hay problema:
public int f () { return 1; }
II Los mtodos slo difieren en el tipo de retorno:
II! class CS extends C implements 11 {}
//! interface 14 extends Il, I3 {} ///,-
La dificultad surge porque los mecanismos de anulacin, de implementacin y de sobrecarga se entremezclan de forma com-
pleja. Asimismo, los mtodos sobrecargados no pueden diferir slo en cuanto al tipo de retorno. Si quitarnos la marca de
comentario de las dos ltimas lneas, los mensajes de error nos informan del problema:
lnterfaceCollisionjava:23: f( ) in e CanllO( implemenl f( ) in 11 ; attempting lO use incompatible relurn type
foune!: inr
required: void
lntelfaceCollisionjava:24: Inlelfaces /3 ane! 11 are incompatible; bOlh dejinef( ), b1ll \Vith different relUrn
Iype
Asimismo, utilizar los mismos nombres de mtodo en diferentes interfaces que vayan a ser combinadas suele aumentar,
generalmente, la confusin en lo que respecta a la legibilidad del cdigo. Trate de evi tar la utilizacin de nombres de mto-
do idnticos.
9 Interfaces 203
Adaptacin a una interfaz
Una de las razones ms importantes para utili zar interfaces consiste en que con ellas podemos di sponer de mltiples imple-
mentaciones para una mi sma interfaz. En los casos ms simples, esto se lleva a la prctica empleando un mwdo que acep-
ta una interfaz, lo que nos deja total libertad y responsabilidad para implementar dicha interfaz y pasar nuestro objeto a dicho
mtodo.
Por tanto, uno de los usos ms comunes para las interfaces es el patrn de diseo basado en estrategia del que ya hemos
hablado: escribimos un mtodo que realice ciertas operaciones y dicho mtodo toma como argumento una interfaz que espe-
cifiquemos. Bsicamente. lo que estamos diciendo es: " Puedes utilizar mi mtodo con cualquier objeto que quieras, siem-
pre que ste se adapte a mi interfaz". Esto hace que el mtodo sea ms flexible, general y reutili zable.
Por ejemplo, el constructor para la clase Scanner de Java SES (de la que hablaremos ms en detalle en el Captulo 13,
Cadenas de caracteres) admite una interfaz Readable. Como veremos, Readable no es un argumento de ningn otro mto-
do de la bibli oteca estndar de Java, fue creado pensando especficamente en Scanner, de modo que Scanner no tenga que
restringir su argumento para que sea una clase determinada. De esta forma, podemos hacer que Scanner funcione con ms
tipos de datos. Si creamos una nueva clase y queremos poder usarla con Scanner, basta con que la hagamos de tipo
Readable, como por ejemplo:
/1: interfaces/RandomWords . java
/1 Implementacin de una interfaz para adaptarse a un mtodo.
import java.nio. * ;
import java.util .* ;
public class RandomWords implements Readable {
private static Random rand = new Random(47) ;
private static final char[) capitals =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray () ;
private static final char [] lowers =
"abcdefghijklmnopqrstuvwxyz".toCharArray() ;
private static final char[] vowels =
"aeiou
lt
toCharArray () ;
private int caunt;
public RandomWords (int count) { this .count
public int read (CharBuffer cb) (
if(count-- == O)
count }
return -1; // Indica el final de la entrada
cb.append(capitals[rand.nextlnt(capitals.length)]) ;
for(int i = O; i <: 4; i++) {
cb.append(vowels[rand.nextlnt(vowels.length)]) ;
cb . append(lowers[rand.nextInt(lowers.length)]) ;
cb.append(" ti);
return 10; /1 Nmero de caracteres aadidos
public static void main (String [] args ) {
Scanner S = new Scanner(new RandomWords(10
while(s.hasNext(
System.out.println(s . next() ;
/ * Output :
Yazeruyac
Fowenucor
Goeazimom
Raeuuacio
Nuoadesiw
Hageaikux
Ruqicibui
Numasetih
204 Piensa en Java
Kuuuuozog
Waqizeyoy
*///,-
La interfaz Readablc slo requiere que se implemente un mtodo read( ). Dentro de read( ), aadimos la infonnacin al
argumento CharBuffer (hay varias formas de hacer esto, consulte la documentacin de CharBuffer), o devolvemos -]
cuando ya no haya ms datos de entrada.
Supongamos que disponemos de una clase base que an no impl ementa Readable. en este caso, cmo podemos hacer que
funcione con Scanner? He aqu un ejemplo de una clase que genera nmeros en coma flotante aleatorios.
ji: interfaces/RandornDoubles.java
import java.util. *
public class RandornDoubles
private static Random rand = new Random(47)
publ ie double next () ( return rand . nextDouble () i
publ ic static void main(String[] argsl {
RandomDoubles rd = new RandomDoubles();
for(int i := Di i < 7; i ++)
System.out . print(rd.next() + 11 ");
1* Output:
0.7271157860730044 0.5309454508634242 0.16020656493302599 0.18847866977771732
0.5166020801268457 0.2678662084200585 0 . 2613610344283964
*///,-
De nuevo. podemos utili zar el patrn de di seo adaptador, pero en este caso la clase adaptada puede crearse heredando e
implementando la interfaz Readable. Por tanto, si utilizamos la herencia pseudo-mltiple proporcionada por la palabra cl ave
interface, produciremos una nueva clase que ser a la vez Ra ndomDoubles y Readable:
11: interfaces/AdaptedRandornDoubles.java
11 Creacin de un adaptador mediante herencia.
import java.nio . *;
import java.util. *
public class AdaptedRandornDoubles extends RandomDoubles
implements Readable {
private int count;
public AdaptedRandornDoubles(int count} {
this.count = count
public int read(CharBuffer cb) {
if(count- - == O)
return -1;
String resul t = Double. toString (next ( + U 11
cb. append (result) ;
return result.length();
public static void main (String [] args) {
Scanner s = new Scanner(new AdaptedRandomDoubles(7
while(s.hasNextDouble () )
System.out.print(s.nextDouble() + 11 " ) i
1* Output:
0.7271157860730044 0.5309454508634242 0 .16020656493302599 0.18847866977771732
0.5166020801268457 0 . 2678662084200585 0 . 2613610344283964
* /// ,-
Puesto que podemos aadir de esta fomla Ulla interfaz a cualquier clase existente, podemos deducir que un mtodo que tome
como argumento una interfaz nos permit ir adaptar cualquier clase para que funcione con dicho mtodo. Aqu radica la ver-
dadera potencia de utilizar interfaces en lugar de clases.
9 Interfaces 205
Ejerci cio 16: (3) Cree una clase que genere una secuencia de caracteres. Adapte esta clase para que pueda utilizarse
COlll0 entrada a un objeto Scanner.
Campos en las interfaces
Puesto que cualquier campo que incluyamos en una interfaz ser automticamente de tipo static y final , la interfaz consti-
tuye una herramienta convenicmc para crear gmpos de valores constantes. Antes de Java SES, sta era la nica fonna de
produci r el mismo efecto que con la palabra clave enum en e o C++. Por tanto, resulta habitual encontrarse con cdigo ante-
rior a la versin Java SES que presenta el aspecto sigui ente:
ji : interfaces / Months.java
1/ Uso de interfaces para crear grupos de constantes.
package interfaces;
public interface Months
int
JANUARY = 1, FEBRUARY = 2,
APRIL = 4, MAY = S, JUNE =
AUGUST = 8, SEPTEMBER = 9,
MARCH = 3,
6, JULY 7,
OCTOBER = 10,
NOVEMBER = 11, DECEMBER = 12,
/// ,-
Observe la utilizacin del esti lo Java, en el que todas las letras estn en maysculas (con guiones bajos para separar las dis-
lintas palabras que formen un determinado identificador) en los casos de valores estt icos finales con inicializadores cons-
tantes. Los campos de una interfaz son automticamente pblicos, asi que el atributo public no se especifica explcitamente.
Con Java SES. ahora di sponemos de la palabra clave enum, mucho ms potente y flexibl e, por lo que rara vez tendr sen-
tido que utilicemos interfaces para definir constantes. Sin embargo. quiz se encuentre en muchas ocasiones con esta tcni-
ca antigua a la hora de leer cdigo heredado (los suplementos de este libro di sponibles en Wlvlv.MindView.nef proporcionan
una descripcin completa de la tcnica previa a Java SES para producir tipos enumerados utilizando interfaces). Puede
encontrar ms detalles sobre el uso de la palabra clave enum en el Captulo 19, Tipos enumerados.
Ejercicio 17: (2) Demuestre que los campos de una interfaz son implcitamente de tipo sta tic y final .
Inicializacin de campos en las interfaces
Los campos definidos en las interfaces no pueden ser valores "finales en blanco", pero pueden inicializarse con expresiones
no constantes. Por ejemplo:
11 : interfaces/ RandVals.java
II Inicializacin de campos de interfaz con
II inicializadores no constantes.
i mport java.util.*i
public interface RandVals
Random RAND = new Random (47 } ;
int RANDOM_INT = RAND.nextInt (lO} ;
long RANDOM_LONG = RAND.nextLong () * 10;
float RANDOM_FLOAT = RAND.nextLong() * 10;
double RANDOM_DOUBLE = RAND.nextDouble ( ) * 10;
/// ,-
Puesto que los campos son estticos, se inicializan cuando se carga por primera vez la clase, lo que tiene lugar cuando se
accede por primera vez a cualquiera de los campos. He aqu una prueba simple:
11 : interfaces/ TestRandVals.java
import static net.mindview.util.Print.*;
public class TestRandVals {
206 Piensa en Java
public static void main (String [] args ) (
print (RandVals.RANDOM INT) i
print (RandVals.RANDOM LONG) ;
print {RandVals.RANDOM FLOAT) i
print (RandVals.RANDOM DOUBLE ) ;
/ * Output:
8
-32032247016559954
-8.5939291E18
5.779976127815049
*///,-
Los campos, por supuesto, no fonnan parte de la interfaz. Los valores se almacenan en el rea de almacenamiento esttico
correspondiente a dicha interfaz.
Anidamiento de interfaces
Las interfaces pueden anidarse dentro de clases y dentro de otras interfaces.
3
Esto nos revela una serie de caractersticas
interesantes:
//: interfaces/nesting/Nestinglnterfaces.java
package interfaces.nesting;
class A
interface B
void f () ;
public class Blmp implements B {
public void f () {}
private class Blmp2 implements B {
public void f () {}
public interface C {
void f () ;
class Clmp implements C {
pUblic void f () {}
private class Clmp2 implements C {
public void f () {}
private interface O {
void f () ;
private class Dlmp implements O {
public void f () {}
public class Dlmp2 implements O (
public void f () {}
public D getD () { return new DImp2 (); }
pri vate O dRef;
public void receiveD (D d) {
dRef = d;
dRef. f () ;
] Grac ias a Martin Danner por hacer una pregunta a este respecto en un seminario.
interface E {
interface G
void f () ;
JI "public" redundante:
public interface H {
void f () ;
void 9 () ;
JI No puede ser private dentro de una interfaz:
li t private interface 1 {}
public class Nestinglnterfaces
public class Blmp implements A.S
public void f 1) {}
class Clmp implements A.e {
public void f () {}
// No se puede implementar una interfaz privada excepto
JI dentro de la clase definitoria de dicha interfaz :
// ! class Dlmp implements A. D {
II ! public void f () {}
II ! }
class Elmp implements E
public void 9 1) {}
class EGlmp implements E.G {
public void f 1) {}
class Elmp2 implements E {
public void 9 1) {}
class EG implements E. G
public void f () {}
public static void main (String [] args) {
A a = new A () i
JI No se puede acceder a A.D:
II! A.O ad = a.getOI );
JI S610 puede devolver a A.O:
II ! A.Olmp2 di2 = a . getOI ) ;
JI No se puede acceder a un miembro de la interfaz:
II! a.getOI ) .f();
/ / S610 otra A puede utilizar getD () :
A a2 = new A () ;
a2.receiveD(a.getD()) ;
}
111 ,-
9 Interfaces 207
La sintaxi s para anidar una interfaz dentro de una clase es razonablemente amplia. Al igual que las interfaces no anidadas,
las anidadas pueden lener visibilidad pblica o con acceso de paquele.
Como caracterstica adicional, las interfaces tambin pueden ser pri vadas, como podemos ver en A.D (se necesita la misma
sintaxis de cualificacin para las interfaces anidadas que para las clases anidadas). Para qu sirve una interfaz anidada pri-
vada? Podemos suponer que slo puede implementarse como clase interna privada, como en DImp, pero A.DImp2 mues-
208 Piensa en Java
tra que tambin puede implementarse como clase pblica. Sin embargo. A.Dlmp2 slo puede utili zarse como ella mi sma.
No se nos pennite mencionar el hecho de que implementa la interfaz privada D, por lo que implementar interfaces privadas
es una fomla de forzar la defini cin de los mtodos de dicha interfaz sin aadi r ninguna informacin de tipos (es decir, sin
permi tir ninguna generalizacin).
El mtodo getD() nos revela un dato adicional acerca de las interfaces privadas: se trata de un mtodo pblico que
ve una referencia a un interfaz privada. Qu podemos hacer con el valor de retomo de este mtodo? En main(), podemos
ver varios intentos de utili zar el valor de retomo, todos los cuales fallan. La nica cosa que funciona es entregar el valor de
retomo a un objeto que tenga penni so para usarlo. que en este caso es orro objeto A, a travs del mtodo receiveD( ).
La interfaz E muestra que podemos ani dar unas interfaces dentro de otras. Sin embargo, las reglas acerca de las interfaces.
en particular, que lodos los elementos de la interfaz tienen que ser pblicos, se imponen aqu de manera estricta, por lo que
una interfaz anidada dentro de otra ser automticamente pblica y no puede nunca definirse como privada.
Nestinglnterfaces muestra las diversas fonnas en que pueden implementarse las interfaces anidadas. En particular,
ve que, cuando implementamos una interfaz, no estamos obligados a implemenrar ninguna de las interfaces anidadas
tro de ella. Asimismo, las interfaces pri vadas no pueden impl ementarse fuera de sus clases definitorias.
Ini cialmente, pudi era parecer que estas caractersti cas slo se hubi eran miad ido para garanti zar la coherencia si ntctica, pero
mi experiencia es que una vez que se conoce una caracterstica siempre se descubren ocasiones en las que puede resultar
til.
Interfaces y factoras
El objeto principal de una interfaz es pem1itir la existencia de mltiples implement aciones, y una fonna tpi ca de producir
objetos que encajen con una interfaz es el denominado patrn de di selio de mtodo factora. En lugar de llamar a un
tructor directamente. invocamos un mtodo de creacin en un objeto factora que produce una impl ementacin de la
faz; de esta forma. en teora, nuestro cdigo estar completamente aislado de la implementacin de la interfaz, haciendo as
posible intercambiar de manera transparente una impl ementacin por otra. He aqu un ejemplo que muestra la estructura del
mtodo factora:
// : interfaces/Factories.java
import static net.mindview.util.Print.*;
interface Service
void methodl () ;
void method2 () j
interface ServiceFactory
Service getService();
class Implementationl implements Service {
Implementationl () {} /1 Package access
public void methodl () {print (" Implementationl methodl");}
public void method2 () {print ( "Implementationl method2");}
class ImplementationlFactory implements ServiceFactory {
public Service getService () (
return new Implementationl{);
class Implementation2 implements Service {
Implementacion2 () {} /1 Acceso de paquete
public void methodl () {print (" Implementation2 methodl n) j}
public void method2 () {print (JI Implementation2 method2") i }
class Implementation2Factory implements ServiceFactory {
public Service getService () {
return new Implementacion2();
public c!ass Factaries {
public static void serviceConsumer(ServiceFactory face)
Service s = fact . getService();
s.methadl() ;
s.method2() ;
public static void main(String[] args )
serviceConsumer(new ImplementationlFactory()) i
// Las implementaciones son completamente intercambiables:
serviceConsumer(new Implementation2Factory());
/ * Output:
Imp lementacianl methadl
Implementatian! mechod2
Implementation2 methadl
Implementation2 method2
*///,-
9 Interfaces 209
Sin el mtodo factora, nuestro cdigo tendra que especificar en algn lugar el tipo exacto de objeto Service que se estu-
viera creando, para poder invocar el constnlctor apropiado.
Para qu sirve aadir este nivel adicional de indirecci n? Una razn comn es para crear un marco de trabajo para el de-
sarrollo. Suponga que estamos creando un sistema para juegos que pennita, por ejemplo, jugar tanto al ajedrez como a las
damas en un mi smo tablero.
JI: interfaces/Games.java
II Un marco de trabajo para juegos utilizando mtodos factora.
import static net.mindview.util . Print.*
interface Game { boolean move(); }
interface GameFactory { Game getGame();
class Checkers implements Game {
private int moves = O;
private static final int MOVES = 3;
public boolean move () {
print ("Checkers move " + moves);
return ++moves != MOVES;
class CheckersFactory implements GameFactory {
public Game getGame() { return new Checkers()
class Chess implements Game {
private int moves = O;
private static final int MOVES 4;
public boolean move () {
print (ItChess move ti + moves);
return ++moves != MOVES;
210 Piensa en Java
class ChessFactory implements GameFactory {
public Game getGame () { return new Chess () i
public clas9 Games {
public static void playGame(GameFactory factory) {
Game s = factory getGame();
while(s.move())
public static void main(String[] args)
playGame(new CheckersFactory{));
playGame (new ChessFactory());
/ *
Output:
Checkers move O
Checkers move 1
Checkers move 2
Chess move O
Chess move 1
Chess move 2
Chess move 3
* /// ,-
Si la clase Carnes representa un fragmenlO complejo de cdigo, esta tcnica permite reutilizar dicho cdigo con diferentes
tipos de juegos. Podemos fcilmente imaginar otros juegos ms elaborados que pudieran beneficiarse a la hora de desarro-
llar este diseo.
En el siguiente captulo, veremos una fonna ms elegante de implementar las factoras utili zando clases internas annjmas.
Ejercicio 18: (2) Cree una interfaz Cycle, con implementaciones Unicycle, Bicycle y Tricycle. Cree factorias para cada
tipo de Cycle y el cdigo necesario que utilicen estas factoras.
Ejercicio 19: (3) Cree un marco de trabajo utilizando mtodos factora que pennita simular las operaciones de lanzar
una moneda y lanzar un dado.
Resumen
Resulta bastante tentador concluir que las interfaces resultan tiles y que, por tanto, siempre son preferibles a las clases con-
cretas. Por supuesto, casi siempre que creemos una clase, podemos crear en su lugar una interfaz y una factora.
Mucha gente ha caido en esta tentacin creando interfaces y factoras siempre que era posible. La lgica subyacente a este
enfoque es que a lo mejor podemos necesitar en el futuro una implementacin diferente, por lo que aadimos siempre dicho
nivel de abstraccin. Esta tcnica ha llegado a convertirse en una especie de optimizacin de diseo prematura.
La realidad es que todas las abstracciones deben estar motivadas por una necesidad real. Las interfaces deben ser algo que
utilicemos cuando sea necesari o para optimizar el cdigo, en lugar de incluir ese nivel adicional de indireccin en todas par-
tes, ya que ello hace que aumente la complej idad. Esa complejidad adicional es significativa, y bacer que alguien trate de
comprender ese cdigo tan complejo slo para descubrir al final que hemos aadido las interfaces "por si acaso" y sin una
razn real, esa persona sospechar, con motivo, de todos los diseos que realicemos.
Una directriz apropiada es la que seala que las clases resultan preferibles a las melfaces. Comience con clases y, si est
claro que las interfaces son necesarias, redisee el cdigo. Las interfaces son una herramienta muy conveniente, pero est
bastante general izada la tendencia a utili zarlas en demasa.
Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico Tire Thinking in Jam Annotated So/lItion Guide, disponible para
la venta en wv,'w.J."indl1elOlet.
Clases internas
Resulta posible situar la definicin de una clase dentro de la definicin de otra. Dichas clases se
llaman clases internas.
Las clases internas constituyen una caracterstica muy interesante, porque nos peml ite agrupar clases relacionadas y contro-
lar la visibilidad mutua de esas clases. Sin embargo, es importante comprender que las clases internas son algo tota lmente
distinto almccanismo de composicin del que ya hemos hablado.
A primera vista, las clases int ernas parecen un simple mecanismo de ocultacin de cdigo: colocamos las clases dentro de
otras clases. Sin embargo, como veremos, las clases internas sirven para algo ms que eso: la clase interna conoce los deta-
ll es de la clase contenedora y puede comunicarse con ella. Asimismo, el tipo de cdigo que puede escribirse con las clases
internas es ms elegante y claro (aunque no en todas las ocasiones, por supuesto).
Inicialmente, las clases internas pueden parecer extraas y se requiere cierto tiempo para ll egar a sentirse cmodo al utili-
zarlas en los di seilos. La necesidad de las clases internas no siempre resulta obvia, pero despus de describir la sintaxis bsi-
ca y la semntica de las clases internas, la seccin "Para qu sirven las clases internas?" debera permitir que el lector se
haga una idea de los beneficios de emplear este tipo de clases.
Despus de dicha seccin, el resto del captulo contiene un anlisis ms detallado de la sintaxi s de las clases internas. Estas
caractersticas se proporcionan con el fin de cubrir por completo el lenguaje, pero puede que no tengamos que usarlas nunca,
o al menos no al principio. Asi pues, puede que el lector slo necesite consultar las partes iniciales del captulo dejando los
anlisis ms detallados como material de referencia.
Creacin de clases internas
Para crear una clase interna, el procedimiento que se utiliza es el que cabra suponer: la definicin de la clase se incluye den-
tro de otra clase contenedora:
11 : innerclasses/ Parcell.java
II Creacin de clases internas.
public class Parcel1 {
class Contents {
private int i = 11;
public int value () { return i }
class Destination {
}
privace String label;
Destination(String whereTo )
label = whereTo
String readLabel () { return label;
II La utilizacin de clases internas se asemeja
II a la de cualquier otra cl ase, dentro de Pareel1:
212 Piensa en Java
public void ship (String dest) {
Contents e = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel( i
public static void main(String[] args)
Parcell p = new Pareell();
p. ship (UTasmania " ) i
/ * Output:
Tasmania
* /// , -
Las clases internas utili zadas dentro de ship( ) parecen clases n0n11ales. Aqu, la nica diferencia prctica es que los nom-
bres estn anidados dentro de Pareell. Pronto veremos que esta diferencia no es la nica.
Lo ms nOlmal es que una clase externa tenga un mtodo que devuelva una referencia a una clase interna, como puede verse
en los mtodos to( ) y contents( ):
ji : innerclasses/Parce12.java
/1 Devolucin de una referencia a una clase interna.
public class Parcel2
class Contents {
private int i : 11;
public int value () { return i; }
class Destination {
private String label
Destination{String whereTo)
label = whereTo
String readLabel {) { return label;
public Destination to (String s) {
return new Destination(s)
public Contents contents()
return new Contents()
public void ship{String dest)
Contents c : contents{);
Destination d : to{dest)
System.out.println{d.readLabel()) i
public static void main(String[] args)
Parcel2 p = new Parcel2()
p.ship{"Tasmania " ) i
Parce!2 q = new Parcel2{)
II Definicin de referencias a clases internas:
Parcel2.Contents e = q.contents{) i
Parce!2.Destination d = q . to(IBorneo") i
1* Output:
Tasmania
* /// ,-
Si queremos construir un objeto de la clase interna en cualquier lugar que no sea dentro de un mtodo 110 esttico de la clase
externa, debemos especificar el tipo de dicho objeto como NombreClaseExterna.NombreClaseJl7terna, corno puede verse en
main( ).
10 Clases internas 213
Ejercici o 1:
( 1) Escriba una clase denominada Outer que contenga una clase interna llamada Joner . Aada un mto-
do a Outer que devuel va un objeto de tipo Inner . En main(), cree e inicialice una referencia a un objeto
Joner .
El enlace con la clase externa
Hasta ahora, parece que las clases internas 5011 simplemente un esquema de organizacin de cdigo y de ocultacin de nom-
bres. lo cua l resulta til pero no especialmente necesario. Sin embargo, las cosas son ms complejas de lo que parecen, cuan-
do se crea una clase interna, cada objeto de esa clase interna di spone de un enlace al objeto contenedor que lo ha creado.
por lo cual puede acceder a los miembros de dicho objeto contenedor sin utilizar ninguna cualificacin especial. Adems,
las clases internas ti enen derechos de acceso a todos los elementos de la clase contenedora.! El siguiente ejemplo ilustra esta
caracleristica:
11 : innerclasses / Sequence . java
II Almacena una secuencia de objetos.
i nterface Selector {
boolean end () ;
Object current () ;
void next ( ) ;
public class Sequence {
private Object [] items;
private int next = O;
public Sequence (int size )
public void add (Object x )
if(next < items.length)
items[next++] = Xi
items new Object [size]; }
private class SequenceSelector implements Selector
private int i = O;
public boolean end () { return i == items.length;
publ ic Obj ect current ( ) { return i tems [i]; }
public void next () { if (i < items . length) i++; }
public Selector selector () {
return new SequenceSelector() i
public static void main(String[] argsl {
Sequence sequence = new Sequence(lO);
for(int i = O; i < 10; i++}
sequence.add {Integer . toString(i ;
Selector selector = sequence.selector( } ;
while ( ! selector. end (l) {
System.out.print {selector.current () + " " ) i
selector.next( ) ;
1- Output:
0 1234 5 6 7 8 9
' /11 ,-
1 Esto difiere significati vament e del diseno de clases anidadas en C ++, que simplemente se trata de un mecanismo de ocultacin de nombres. No hay n n ~
giln enlace al objeto contenedor ni nillgtin tipo de penni sos implcitos en C++.
214 Piensa en Java
La secuencia Sequence es simplemente una matriz de tamao fijo de objetos Object con una clase envoltorio. Invocamos
add( ) para aiiadir un nuevo objelO al final de la secuencia (si queda si tio). Para extraer cada uno de los objetos de la secuen_
cia, hay una interfaz denominada Selector. ste es un ejemplo del patrn de diseo iterador del que hablaremos ms en deta-
lle posterionnente en el libro. Un Selector pennite ver si nos encontramos al final de la secuencia [ende )], acceder al objeto
acnlal [current()] y desplazarse al objeto siguiente [next()] de la secuencia. Como Selector es una interfaz, otras clases pue-
den implementar la interfaz a su manera y otros mtodos pueden tomar la interfaz como argumento, para crear cdigo de
propsito ms general.
Aqu. SequenceSelector es una clase privada que proporciona la funcionalidad Selector. En main(), podemos ver la crea-
cin de una secuencia, seguida de la adicin de una serie de objetos de tipo String. A continuacin, se genera un objeto
Selector con una llamada a selector(), y este objeto se utiliza para desplazarse a travs de la secuencia y seleccionar cada
elemento.
A primera vista, la creacin de SequenceSelector se asemeja a la de cualquier otra clase interna. Pero examinemos el ejem-
plo en ms detalle. Observe que cada uno de los mtodos [end(), current() y next()] bace referencia a items, que es una
referencia que no forma parte de SequenceSelector, sino que se encuentra en un campo privado dentro de la clase contene-
dora. Sin embargo, la clase interna puede acceder a los mtodos y campos de la clase contenedora como si fueran de su pro-
piedad. Esta caracterstica resulta muy cmoda, como puede verse en el ejemplo anterior.
As pues, una clase interna tiene acceso automtico a los miembros de la clase contenedora. Cmo puede suceder esto? La
clase interna captura en secreto una referencia al objeto concreto de la clase contenedora que sea responsable de su crea-
cin. Entonces, cuando hacemos referencia a un miembro de la clase contenedora, dicha referencia se utiliza para seleccio-
nar dicho miembro. Afortunadamente, el compilador se encarga de resolver todos estos detalles por nosotros, pero resulta
evidente que slo podr crearse un objeto de la clase interna en asociacin con otro objeto de la clase contenedora (cuando,
como veremos pronto, la clase interna sea no esttica). La construccin del objeto de la clase interna necesita de una refe-
rencia al objeto de la clase contenedora y el compi lador se quejar si no puede acceder a dicha referencia. La mayor parte
de las veces todo este mecanismo funciona sin que el programador tenga que intervenir para nada.
Ejercicio 2:
Ejercicio 3:
(1) Cree una clase que almacene un objeto String y que disponga de un mtodo toString( ) que muestre
esa cadena de caracteres. Aada varias instancias de la nueva clase a un objeto Sequence y luego visual-
celas.
(1) Modifique el Ejercicio 1 para que Outer tenga un campo private String (inicializado por el construc-
tor) e lnner tenga un mtodo toString( ) que muestre este campo. Cree un objeto de tipo (nner y visua-
licelo.
Utilizacin de .this y .new
Si necesita generar la referencia al objeto de la clase externa, basta con indi car el nombre de la clase externa seguido de un
punto y de la palabra clave this. La referencia resultante tendr automticamente el tipo correcto, que se conoce y se com-
prueba en tiempo de compi lacin, por lo que no hay ningn gasto adicional en tiempo de procesamiento. He aqu un ejem-
plo que muestra cmo utilizar .this:
ji : innerclasses/DotThis.java
1/ Cualificacin del acceso al objeto de la clase externa.
public class DotThis
void f () { System.out.println("DotThis.f() ") i }
public class Inner {
public DotThis outer () {
return DotThis.this
jj Un "this" hara referencia al "thisl! de Inner
public Inner inner () { return new Inner () ;
public static void main (St ring [) args) {
DotThis dt = new DotThis()
DotThis.Inner dti = dt.inner()
dti.outer () .f () ;
} 1* Output,
Do tThis. f ()
*111 ,-
10 Clases internas 21 5
Algunas veces, necesitamos decir a un objeto que cree otro objeto de una de sus clases internas. Para hacer esto es necesa-
rio proporcionar una referencia al objeto de la clase externa en la expresin oew, utilizando la sintaxis .ne\\' , como en el
siguiente ejemplo:
1/ : innerclasses/DotNew.java
// Creacin de una clase interna directamente utilizando la sintaxis .new.
public class DotNew (
public class Inner {}
public static void main(String(] args )
DotNew dn = new DotNew( ) ;
DotNew.lnner dni = dn.new Inner()
Para crear un obj eto de la clase interna directamente, no se utiliza esta mi sma famla haciendo referencia al nombre de la
cl ase externa DotNew como cabra esperar, sino que en su lugar es necesari o utili zar un objeto de la clase externa para crear
un objeto de la clase interna. como podemos ver en el ejemplo anterior. Esto resuelve tambin las cuestiones relativas a los
mbitos de los nombres en la clase interna, por lo que nunca escri biramos (porque, de hecho, no se puede) dO.De\\'
DotNew.lnner( ).
No es posible crear un objeto de la clase interna a menos que ya se di sponga de un objeto de la clase externa. Esto se debe
a que el objeto de la clase interna se conecta de manera transparente al de la clase externa que lo haya creado. Sin embar-
go, si defini mos una clase anidada, (una clase interna esttica), entonces no ser necesari a la referencia al objeto de la clase
externa.
A continuacin puede ver cmo se apli cara .Den' al ejemplo " Parcer':
11 : innerclasses/Parcel3.java
JI Utilizacin de .new para crear instancias de clases internas.
public class Parcel3
cl ass Contents {
private int i = 11
public int value () { return i }
c lass Destination {
private String label
Destination (String whereTo) { label = whereTo }
String readLabel () { return label }
public static void main{String[] args)
Parcel3 p = new Parcel3{);
JI Hay que usar una instancia de la clase externa
II para crear una instancia de la clase interna:
Parcel3.Contents e = p . new Contents();
Parcel3 . Destination d = p.new Destination(ITasmania");
}
111 >
Ejercici o 4:
Ej ercicio 5:
(2) Aada un mtodo a la clase Sequence.SequenceSelector que genere la referencia a la clase externa
Sequence.
( 1) Cree una clase con una clase interna. En otra clase separada, cree una instancia de la clase interna.
216 Piensa en Java
Clases internas y generalizacin
Las clases int ernas muestran su utilidad real cuando comenzamos a generalizar a una clase base y, en parti cular, a una inter_
faz. (El efecto de generar Wla referencia a una interfaz a partir de un obj eto que la implemente es prcticamente el mismo
que el de reali zar una generali zacin a una clase base). La razn es que entonces la clase interna (la impl ementaci n de la
interfaz) puede ser no visible y estar no di sponibl e, lo cual resulta muy til para ocultar la impl ementacin. Lo nico que se
obti ene es una referencia a la clase base o a la interfaz.
Podemos crear interfaces para los ejemplos anteriores:
11: innerclasses/Destination. java
public interface Destination {
String readLabel() i
} 111,-
Ij : innerclassesjContents . java
public interf ace Contents {
int value () ;
} 1110-
Ahora Contents y Destination representan interfaces disponibles para el programador de clientes. Recuerde que una inter-
faz hace que todos sus miembros sean automticamente pblicos.
Cuando obtenemos ulla referencia a la clase base o a la interfaz, es posible que no podamos averiguar el tipo exacto, como
se muestra en el siguiente ejemplo:
JI : innerclassesjTestParcel . java
class Parcel4 {
private class PCont ent s implements Contents {
private i n t i = 11;
public int val ue () { return i; }
protected c l ass PDestination implements De stination
private String label;
private PDestination (String whereTo) {
label = whereTo
public String readLabel () { return label;
public Destination destination (String s) {
return new PDestination(s);
public Contents contents()
return new PContents();
publie class TestParcel {
public static void main (String [] args) {
parcel4 p = new PareeI4() i
Contents c = p.contents();
Destination d = p . destination(UTasmania
U
) ;
IJ Ilegal -- no se puede acceder a la clase privada :
JI! Parcel4 . PContents pe = p . new PConten ts();
}
111 ,-
En Parcel4 hemos aadido algo nuevo. La clase interna PContents es priva te, as que slo puede acceder a ell a Parcel4.
Las clases normales (no internas) no pueden ser privadas o protegidas; slo pueden tener acceso pblico o de paquete.
PDestination es protegida, por lo que slo pueden acceder a ella Parcel4, las clases cont enidas en el mismo paquete (ya
10 Clases in lemas 217
que protected tambin proporciona acceso de paquete) y las clases que hereden de Parcel4. Esto quiere decir que el pro-
gramador de clientes tiene un conocimiento de estos miembros y un acceso a los mismos restringido. De hecho, no pode-
mos ni siquiera realizar una especializacin a una clase interna privada (ni a una clase interna protegida, a menos que
estemos usando una clase que herede de ella), porque no se puede acceder al nombre, como podemos ver en class
TestParcel. Por tanto, las clases internas privadas proporcionan una forma para que los diseadores de clases eviten com-
pletamente las dependencias de la codificacin de tipos y oculten totalmente los detalles relativos a la implementacin.
Adems. la extensin de una interfaz resulta intil desde la perspectiva del programador de clientes, ya que ste no puede
acceder a ningn mtodo adicional que no forme parte de la interfaz pblica. Esto tambin proporciona una oportunidad
para que el compilador de Java genere cdigo ms eficiente.
Ejercici o 6 : (2) Cree una interfaz con al menos un mtodo, dentro de su propio paquete. Cree una clase en un paque-
te separado. Aada una clase interna protegida que implemente la interfaz. En un tercer paquete, defina
una clase que herede de la anterior y, dentro de un mtodo, devuelva un objeto de la clase interna protegi-
da, efecnlando una generalizacin a la interfaz durante el retorno.
Ejercicio 7:
Ejercicio 8:
(2) Cree una clase con un campo privado y un mtodo privado. Cree una clase interna con un mtodo que
modifique el campo de la clase externa e invoque el mtodo de la clase externa. En un segundo mtodo
de la clase externa, cree un objeto de la clase interna e invoque su mtodo, mostrando a continuacin el
efecto que esto tenga sobre el objeto de la clase externa.
(2) Detennine si una clase externa tiene acceso a los elementos privados de su clase interna.
Cl ases internas en los mtodos y mbitos
Lo que hemos visto hasta ahora son los usos tpi cos de las clases internas. En general , el cdigo que escribamos y el que
podamos leer donde aparezcan clases internas estar compuesto por clases internas "simples" que resulten fciles de com-
prender. Sin embargo, las sintaxis de las clases internas abarca varias otras tcnicas ms complejas. Las clases internas pue-
den crearse dentro de un mtodo o incluso dentro de un mbito arbi trario. Existen dos razones para hacer esto:
1. Como hemos visto anterionllente, podemos estar implementando una interfaz de algn tipo para poder crear y
devolver una referencia.
2. Podemos estar tratando de resolver un problema complicado y queremos crear una clase que nos ayude a encon-
trar la solucin, pero sin que la clase est pblicamente disponible.
En los siguientes ejemplos, vamos a modificar el cdigo anterior para utilizar:
1. Una clase definida dentro de un mtodo
2. Una clase definida dentro de un mbito en el interior de un mtodo
3. Una clase annima que implemente una interfaz
4. Una clase annima que ample una clase que disponga de un constructor no predeterminado
5. Una clase annima que se encargue de la inicializacin de campos
6. Una clase annima que lleve a cabo la construccin utilizando el mecanismo de inicializacin de instancia (las
clases internas annimas no pueden tener constmctores).
El primer ejemplo muestra la creacin de una clase completa dentro del mbito de un mtodo (en lugar de dentro del mbi-
to de otra clase). Esto se denomina clase interna local:
11 : innerclasses/ ParcelS. java
11 Anidamiento de una clase dentro de un mtodo.
public class ParcelS {
public Destination destination (String s) {
class PDestination implements Destination
private String label;
private PDestination (String whereTo) {
label = whereTo
218 Piensa en Java
public String readLabel () { return label; }
return new PDestination(s);
public static void main (String [] args) {
ParcelS p = new Parce15();
Destination d = p.destination{tlTasmania
tl
);
La clase PDestination es parte de destination( ) en lugar de ser parte de Parcel5. Por tanto, no se puede acceder a
PDestination fuera de destination(). Observe la generalizacin que tiene lugar en la instruccin return: lo nico que sale
de destination( ) es una referencia a Destination, que es la clase base. Por supuesto, el hecho de que el nombre de la clase
PDestination se coloque dentro de destination( ) no quiere decir que PDestinatioD no sea un objeto vlido una vez que
destination( ) tennina.
Podemos utilizar el identificador de clase PDestination para nombrar cada clase interna dentro de un mi smo subdirectorio
sin que se produzcan colisiones de clases.
El siguiente ejemplo muestra cmo podemos anidar clases dentro de un mbito arbitrario.
jj : innerclasses/Parcel6.java
ji Anidamiento de una clase dentro de un mbito.
public class Parce16 {
private void internalTracking(boolean b ) {
Hlbl {
class TrackingSlip {
private String id;
TrackingSlip (String s) {
id = Si
String getSlip I I { return id; }
TrackingSlip ts = new TrackingSlip ("s lip" ) ;
String s = ts.getSlip() i
ji NO se puede usar aqu! Fuera de mbito:
jj! TrackingSlip ts = new TrackingSlip("x
tl
) ;
public void track() { internalTracking(true};
public static void main{String[] args) {
Parce16 p = new Parce16();
p. track ();
La clase TrackingSlip est anidada dentro del mbito de una instruccin ir. Esto 00 quiere decir que la clase se cree con-
dicionalmente; esa clase se compila con todo el resto del cdigo. Sin embargo, la clase no est disponible fuera del mbito
en que est definida. Por lo dems, se asemeja a una clase nornlal.
Ejercicio 9: (1) Cree una interfaz con al menos un mtodo e implemente dicha interfaz definiendo una clase intema
dentro de un mtodo que devuelva una referencia a la interfaz.
Ejercicio 10: (1) Repita el ejercicio anterior, pero definiendo la clase interna dentro de un mbito en el interior de un
mtodo.
Ejercicio 11: (2) Cree una clase interna privada que implemente una interfaz pblica. Escriba un mtodo que devuel va
una referencia a una instancia de la clase interna privada, generalizada a la interfaz. Demuestre que la clase
interna est completamente oculta, tratando de realizar una especiali zacin sobre la misma.
Clases internas annimas
El siguiente ejemplo puede parecer un tanto extrao:
JI : innerclasses/Parce17.java
// Devolucin de una instancia de una clase interna annima.
public class Pareel7
public Contents contents()
return new Contents () { / / Inserte una definicin de clase
private int i = 11
publ ic int value () { return i i
} /1 En este caso hace falta el punto y coma
public static void rnain(String[] args) {
Pareel7 p = new Parce17()
Contents e = p.contents();
10 Clases internas 219
El mtodo contents( ) combina la creacin del valor de retorno con la definicin de la clase que representa dicho valor de
retorno. Adems, la clase es annima, es decir, no tiene nombre. Para complicar an ms las cosas, parece como si estuvi-
ramos empezando a crear un objeto Contents, y que entonces, antes de lJ egar al punto y coma, dij ramos "Un momento:
voy a introducir una definicin de clase".
Lo que esta extrai'ia sintaxis significa es: "Crea un objeto de una clase annima que herede de Contents". La referencia
devuelta por la expresin De\\' se generalizar automticamente a una referencia de tipo Contents. La sintaxis de la clase
interna annima es una abreviatura de:
11: innerclasses/Parce17b.java
II Versin expandida de Parce17.java
public class Parce17b {
class MyContents implements Contents
private int i = 11;
public int value () { return i i }
public Contents contents () { return new MyContents () j
public static void main(String[] argsl {
Parce17b p new Parce17b();
Contents c = p.contents();
En la clase interna annima, Contents se crea utilizando un constructor predeterminado.
El siguiente cdigo muestra lo que hay que hacer si la clase base necesita un constructor con un argumento:
11 : innerclasses/ParceI8.java
II Invocacin del constructor de la clase base.
public class Parcel8 {
public Wrapping wrapping(int xl
II Llamada al constructor de la clase base:
return new Wrapping(x) { II Pasar argumento del constructor.
public int value () {
return super. value () * 47;
}i II Punto y coma necesario
public sta tic void main (String [] argsl {
220 Piensa en Java
Parcele p = new Parcel8 () ;
Wrapping w = p.wrapping (lO) ;
Es decir, simplemente pasamos el argumento apropiado al constructor de la clase base. C0l110 sucede aqu con la x que se
pasa en oew Wrapping(x). Aunque se trata de una clase nonnal con una implementaci n, Wr apping se est usando tamo
bin como "interfaz" con sus clases derivadas:
JI : innerclasses / Wrapping.java
public class Wrapping {
prvate int i;
public Wrapping ( int x ) { i = x; }
public int value () { return i; }
/// ,-
Como puede observar. \ Vr apping liene un constructor que requiere un argumento, para que las cosas sean un poco ms inte-
resantes.
El punt o y coma si mado al final de la clase intema annima no marca el final del cuerpo de la clase, sino el final de la expre-
sin que contenga a la clase annima. Por tanto, es una utili zacin idntica al uso del punto y coma en cualquier otro lugar.
Tambi n se puede realizar la inicializacin cuando se definen los campos en una clase annima:
// : innerclasses/ ParceI9 . java
// Una clase interna annima que realiza la
// inicializacin. Versin ms breve de ParceIS.java.
public class Parcel9 {
/1 El argumento debe ser final para poder utilizarlo
/1 dentro de la clase interna annima:
public Destination destination (final String dest ) (
ret urn new Destination () {
private String label = dest;
public String readLabel () { return label; }
};
public static void main (String [] args l (
Parcel9 p = new ParceI9 () ;
Destination d = p.destination ( IOTasmania
lO
) ;
Si estamos definiendo una clase interna annima y queremos usar un objeto que est definido fuera de la clase interna n ~
nima. el compilador requiere que la referencia al argumento sea final, como puede verse en el argumento de destination().
Si nos olvidamos de hacer eslO, obtendremos un mensaje de error en tiempo de compilacin.
Mientras que estemos simplemente reali zando una asignacin a un campo, la tcnica empl eada en este ejempl o resulta ade-
cuada. Pero qu sucede si necesitamos reali zar algn tipo de actividad similar a la de los constmclOres? No podemos dis-
poner de un constmctor nominado dentro de una clase annima (ya que la clase no tiene ningn nombre), pero con el
mecani smo de inicializacin de instancia. podemos, en la prctica, crear un constructor para una clase interna annima. de
la fomla siguiente:
11 : innerclasses/ AnonymousConstructor.java
II Creacin de un constructor para una clase interna annima.
import static net.mindview.util.Print.*;
abstract class Base {
public Base (int i) {
print ( "Base constructor, i
public abstract void f () ;
" + i);
pUblic class AnonyrnousConstructor {
public static Base getBase(int i)
return new Base (i) {
{ print(IIInside instance initializer") i
public void f () {
print ( " In anonymous f () " ) ;
}
} ;
public stati c
Base base
base . f (1 ;
/ * Output :
void main (String [] args) {
getBase(47) ;
Base constructor, i = 47
rnside instance initializer
In anonymous fe)
* /// >
10 Clases internas 221
En este caso, la variable i no tena porqu haber sido final. Aunque se pasa i al constructor base de la clase annima. nunca
se utili za esa variable dentro de la clase annima.
He aqui un ejemplo con inicializacin de instancia. Observe que los argumentos de destination( ) deben ser de tipo final ,
puesto que se los usa dentro de la clase annima:
ji : innerclasses/ParcellO. java
JI USO de "inicializacin de instancia" para realizar
II la construccin de una clase interna annima .
public class ParcellO
public Destination
destination(final String dest, final float price) {
return new Dest ination () {
private int cost
};
II Inicializacin de instancia para cada objeto :
(
cost = Math.round(price);
if (cost > 100)
System. out . println ( "Over budget!");
private String label = dest;
public String readLabel () { return label j }
public static void main{String[] args) (
ParcellO p = new ParcellO();
Destination d = p.destination{"Tasmania", lOl.395F) i
1* Output :
Over budget!
* /1/ , -
Dentro del inicializador de instancia, podemos ver cdigo que no podra ejecutarse como parte de un iniciali zador de campo
(es decir, la instruccin if). Por tanto, en la prctica, un iniciali zador de instancia es el constructor de una clase interna an-
nima. Por supuesto, esta solucin est limitada: no se pueden sobrecargar los ini cializadores de instancia, as que slo pode-
mos disponer de uno de estos constructores.
Las clases imemas annimas estn en cierta medida limitadas si las comparamos con el mecani smo nonnal de herenci a, por-
que tienen que extender Ull a clase o implementar una interfaz, pero no pueden hacer ambas cosas al mi smo tiempo. Y, si
implementamos una interfaz. slo podemos implementar una.
222 Piensa en Java
Ejercicio 12: ( 1) Repita el Ejerci cio 7 ut ili zando una cl ase interna annima.
Ejercicio 13: ( 1) Repita el Ejercicio 9 utilizando una cl ase interna annima.
Ejercicio 14: ( 1) Modifique interfaces/Hor rorShow.j.v. para impl ementar DangerousMonster y Va mpire utilizando
clases annimas.
Ejercicio 15: (2) Cree una clase con un constructor no predetemli nado (uno que tenga argumentos) y sin ningn cons
tmctor predetenninado (es decir, un constructor sin argumenlos). Cree una segunda clase que tenga un
mtodo que devuel va una referencia a un obj eto de la primera cl ase. Cree el obj eto que hay que devolver
defini endo Ulla clase intema annima que herede de la primera clase.
Un nuevo anlisis del mtodo factora
Observe que el ej emplo interfaces/ Factor ies.java es mucho ms atracti vo cuando se utilizan clases internas annimas:
JI: innerc l asses/Factories . java
import static net . mindview . util . Print .*
interface Service
void methodl () i
void method2 () i
interface ServiceFactory
Service getService( ) i
class Implementationl implements Se r vice {
private Implementationl () {}
public void methodl () {print ( "Implementationl methodl" ) }
public void method2 () {print ( !I Implementationl method2" ) ;}
public static ServiceFactory factory
new ServiceFactory () {
public Service getService () {
return new Implementationl () ;
}
} ;
class Implementation2 implements Service {
private Implementation2 () {}
public void methodl () {print ( " Implementation2 methodl" ) }
public void method2 () {print ( " Implementation2 method2" ) ; }
public static ServiceFactory factory
new ServiceFactory () {
public Service getService () {
return new Implementation2( ) ;
}
};
public class Factories {
public static void serviceConsumer(ServiceFactory fact )
Service s = fact.getService ()
s . methodl (} ;
s.method2 () ;
public static void main (String{) args ) {
serviceConsumer ( Implementationl . factory) ;
/1 Las implementaciones son completamente intercambiables :
serviceConsumer{Implementation2.factory) i
1* Output:
Implementationl methodl
rmplementationl method2
Implementation2 methodl
Implementation2 method2
* / 1/,-
10 Clases internas 223
Ahora. los constructores de hnplementationl e Implementation2 pueden ser pri vados y no hay ninguna necesidad de crear
una clase nominada C01110 facrofi a. Adems, a menudo slo hace falta un ni co objeto fa ctora, de modo que aqu lo hemos
creado C0l110 un campo estti co en la implementacin de Service. Asimismo, la sintaxis resultante es ms clara.
Tambin podemos mejorar el ej empl o interfaces/Games.java utilizando clases internas annimas:
JI: innerclasses/Games . java
II Utilizacin de clases internas annimas con el marco de trabajo Game.
import static net.mindview. util . Print .*
interface Game ( boolean move() i }
interface GameFactory ( Game getGame();
class Checkers implements Game {
private Checkers () {}
private int moves = O;
private static final int MOVES = 3i
public boolean move() {
print ("Checkers move " + moves);
ret urn ++moves != MOVESi
public static GameFactory factory = new GameFactory()
public Game getGame () { return new Checkers () i }
} ;
class Chess implements Game
private Chess() {}
private int moves = O;
private static final int MOVES = 4;
public boolean move ()
print ("Chess move " + moves);
return ++moves != MOVES;
public static GameFactory factory = new GameFactory(}
public Garne getGame () { return new Chess (); }
} ;
public class Garnes {
public static void playGame(GameFactory factory) {
Garne s = factory getGame();
while{s.move())
public static void main(String[] args) {
playGame(Checkers . factory) ;
playGame(Chess . factory) ;
/* Output:
224 Piensa en Java
Checkers move O
Checkers move 1
Checkers move 2
Chess move O
Chess move 1
Chess move 2
Chess move 3
, /// ,-
Recuerde el consejo que hemos dado al final del ltimo captulo: U,Uice las clases con preferencia a las in/elfaces. Si su
diseo necesita una interfaz, ya se dar cuenta de ello. En caso contrario, no emplee una interfaz a menos que se vea obli
gado.
Ejercicio 16: (1) Modifique la sol ucin del Ejercicio 18 del Capitulo 9, Imelfaces para utilizar clases internas annimas.
Ejercic io 17: (1) Modifique la solucin del Ejercicio 19 del Capitulo 9, In/e/faces para utilizar clases internas annimas.
Clases anidadas
Si no es necesario disponer de una conexin entre el objeto de la clase ntema y el objeto de la clase extema, podemos defi-
nir la clase interna como esttica. Esto es lo que comnmente se denomina una clase anidada.
2
Para comprender el signi fi-
cado de la palabra clave static cuando se la aplica a las clases internas, hay que recordar que el objeto de una clase intema
normal mantiene implcitamente una referencia al objeto de la clase contenedora que lo ha creado. Sin embargo, esto no es
cierto cuando definimos una clase interna corno esttica. Por tanto, una clase anidada significa:
1. Que no es necesario un objeto de la clase externa para crear un objeto de la clase anidada.
2. Que no se puede acceder a un objeto no esttico de la clase externa desde un objeto de una clase anidada.
Las clases anidadas difieren de las clases internas ordinarias tambin en otro aspecto. Los campos y los mtodos en las cla-
ses internas l10rnlales slo pueden encontrarse en el nivel externo de una clase, por lo que las clases internas nomlales no
pueden tener datos estticos, campos estticos o clases anidadas. Si.n embargo, las clases anidadas pueden tener cualquiera
de estos elementos:
ji : innerclasses/Parcel11.java
ji Clases anidadas (clases internas estticas).
public class Parcel11 {
private static class ParcelContents implements Contents {
private int i = 11;
public int value() { return i }
protected static class ParcelDestination
implements Destination
private String label
private ParcelDestination (String whereTo ) {
label = whereTo
public String readLabel () { return label }
jj Las clases anidadas pueden contener otros elementos estticos:
pubEe statie void f () {)
sta tic int x = 10;
static class AnotherLevel {
pubEe statie void f (1 {)
static int x = 10;
2 Guarda cierto parecido con las clases anidadas de C++, salvo porque dichas clases no penniten acceder a miembros privados a dilercncia de lo que suee-
de en Java.
public static Destinatan destination(String s) {
return new ParcelDestination(s) i
public static Contents contents()
return new ParcelContents() i
public static void main(String[J args)
Contents e = contents () i
Destinatian d = destinatan ("Tasmania" ) i
}
111 ,-
10 Clases internas 225
En main(), no es necesario ningn obj eto de Pareel1 ); en su lugar, utilizamos la sintaxis nornlal para seleccionar un mi em-
bro esttico con el que invocar los mtodos que devuelven referencias a Contents y Desti nation.
Como hemos visto anterionnente en el captulo. en una clase interna normal (no esttica), el vnculo con el objeto de clase
externa se utili za empleando una referencia this especiaL Una clase anidada no tiene referencia this especial, lo que hace
que sea anloga a un mlodo eSllico.
Ejercicio 18 : (1) Cree una clase que contenga una clase anidada. En ma in( ), cree una instancia de la clase anidada.
Ejercicio 19 : (2) Cree una clase que contenga una clase interna que a su vez contenga otfa clase interna. Repita el pro-
ceso utilizando clases anidadas. Observe los nombres de los archivos .class generados por el compilador.
Clases dentro de interfaces
Normalmente, no podemos incluir cualquier cdigo dentro de una interfaz, pero una clase anidada puede ser parte de Ulla
interfaz. Cualquier clase que coloquemos dentro de una interfaz ser automticamente pblica y esttica. Puesto que la clase
es esttica no viola las reglas de las interfaces: simplemente, la clase anidada se incluye dentro del espacio de nombres de
la interfaz. Podemos incluso implementar la interfaz contenedora dentro de la clase contenedora de la fonna siguiente, como
por ejemplo en:
jJ: innerclasses/classInInterface . java
jJ {main: Classlnlnterface$Test}
public interface ClassInInterface
void howdy () i
class Test implements ClasslnInterface
public void howdy () {
System.out.println(HHowdy! ") i
public static void main (String [J args) {
new Test () . howdy () i
1* Output:
Howdy!
*//1,-
Resulta bastante til anidar una clase dentro de una interfaz cuando queremos crear un cdigo comn que baya que em-
plear con todas las diferentes implementaciones de dicha interfaz.
Anteriornlente en el libro ya sugeramos incluir un mtodo main( ) en todas las clases. con el fin de utilizarlo como meca-
nismo de prueba de estas clases. Una desventaja de esta tcnica es la cantidad de cdigo compilado adicional con la que hay
que trabajar. Si esto representa un problema, pruebe a utilizar una clase anidada para incluir el cdigo de prueba:
JI : innerclassesJTestBed.java
JI Inclusin del cdigo de prueba en una clase anidada .
JI {main : TestBed$Tester}
public class TestBed {
226 Piensa en Java
public void f{) { System.out.println(tlf() 11);
public statie elass Tester {
public static void main (String [] args) {
TestBed t = new TestBed();
t . f () ;
1* Output:
f ()
* /// ,-
Esto genera una clase separada denominada TcstBcd$Tester (para ejecutar el programa, escribiramos java
TestBedSTestcr ). Puede utilizar esta clase para las pmebas, pero no necesitar incluirla en el producto final , bastar con
borrar TestBed$Tester. cl ass antes de crear el producto definitivo.
Ejercicio 20: (1) Cree una interfaz que contenga una clase anidada. Implemente esta interfaz y cree una instancia de la
clase anidada.
Ejercicio 21: (2) Cree una interfaz que contenga una clase anidada en la que haya un mtodo esttico que invoque los
mtodos de la interfaz y muestre los resultados. Implemente la interfaz y pase al mtodo una instancia de
la implementacin.
Acceso al exterior desde una clase mltiplemente anidada
No importa con qu profundidad pueda estar anidada una clase interna: la clase anidada podr acceder transparentemente a
todos los miembros de todas la clases dentro de las cuales est anidada, como podemos ver en el siguiente ejemplo: 3
11 : innerclasses/MultiNestingAeeess.java
II Las clases anidadas pueden acceder a todos los miembros de
II todos los niveles de las clases en las que est anidada.
elass MNA
private void f () {}
class A
pri vate void 9 () {}
publ ie elass B {
void h() (
g ();
f ();
publie elass MultiNestingAeeess {
publie static void main{String[] args ) {
MNA mna = new MNA () ;
MNA.A mnaa = mna.new A{);
MNA.A.B mnaab = mnaa.new B();
mnaab.h() ;
Puede ver que en MNA.A. B. los mtodos g() y f() son invocables sin necesidad de ninguna cualificacin (a pesar del hecho
de que son privados). Este ejemplo tambin ilust ra la sintaxis necesaria para crear objetos de clases internas mltiplemente
anidadas cuando se crean los objetos en una clase diferente. La sintaxis '. new' genera el mbito correcto, por lo que no hace
falta cualificar el nombre de la clase dentro de la llamada al constmctor.
3 Gracias de nuevo a Martin Danncr.
10 Clases internas 227
Para qu se usan las clases internas?
Hasta este momento, hemos analizado buena parte de los detalles sintcticos y semnticos que describen la fanna de fun-
cionar de las clases internas. pero esto no responde a la pregunta de para qu sirven las clases internas. Por qu los disea-
dores de Java se tomaron tantas molestias para aadir esta caracterstica fundamental al lenguaje?
Nonnalmente, la clase interna hereda de otra clase o implementa una interfaz y el cdigo incluido en la clase interna mani-
pula el objeto de la clase externa dentro del cual hubiera sido creado. As pues, podramos decir que una clase interna
proporciona lm3 especie de ventana hacia la clase externa.
Una de las cuestiones fundamentales acerca de las clases internas es la siguiente: si simplemente necesitamos una referen-
cia a una interfaz, por qu no hacemos simplemente que la clase externa implemente dicha interfaz? La respuesta es que:
"Si eso es todo lo que necesita, entonces esa es la manera de hacerlo". Por tanto, qu es lo que distingue una clase interna
que implementa una interfaz de una clase externa que implementa la misma interfaz? La respuesta es que no siempre dis-
ponemos de la posibilidad de trabajar con interfaces, sino que en ocasiones nos vemos forzados a trabajar con implementa-
ciones. Por tanto, la razn ms evidente para utilizar clases internas es la siguiente:
Cada clase in/erna puede heredar de una implementacin de manera independiente. Por tamo, la clase
interna no est limitada por el hecho de si la clase externa ya est heredando de una implementacin.
Sin la capacidad de las clases internas para heredar, en la prctica, de ms de una clase concreta o abstracta, algunos pro-
blemas de diseo y de programacin seran intratables. Por tanto, una forma de contemplar las clases internas es decir que
representan el resto de la solucin del problema de la herencia mltiple. Las interfaces resuelven parte del problema. pero
las clases internas permiten en la prctica una "herencia de mltiples implementaciones". En otras palabras, las clases inter-
nas nos pemliten en la prctica heredar de varios elementos que no sean interfaces.
Para analizar esto con mayor detalle, piense en una situacin en la que tuviramos dos interfaces que deban de alguna forma
ser implementadas dentro de una clase. Debido a la flexibilidad de las interfaces, tenemos dos opciones: una nica clase o
una clase interna.
/1: innerclasses/MultiInterfaces.java
II Dos formas de implementar mltiples interfaces con una clase.
package innerclassesi
interface A {}
interface 8 {}
class X implements A, B {}
class y implements A
8 makeB (1 {
JI Clase interna annima:
return new 8 (1 {};
public class Multilnterfaces
static void takesA (A al {}
static void takesB (8 b) {}
public static void main (String [) argsl {
X x = new X(l i
y Y = new Y () i
takesA (x) i
takesA (y) i
takesB (x) i
takesB(y . makeB()) i
228 Piensa en Java
Por supuesto. esto presupone que la estrucrura del cdigo tenga sentido en ambos casos desde el punto de vista lgico. Sin
embargo. nonnalmente dispondremos de algn tipo de directriz, extrada de la propia naturaleza del problema, que nos indi
car si debemos utilizar una nica clase o una clase interna. pero en ausencia de cualquier otra restriccin, la tcnica utili-
zada en el ejemplo anterior no presenta muchas diferencias desde el punto de vista de la implementacin. Ambas soluciones
funcionan adecuadamente.
Sin embargo. si tenemos clases abstractas o concretas en lugar de interfaces, nos veremos obligados a utilizar clases inter-
nas si nuestra clase debe implementar de alguna forma las otras clases de las que se quiere heredar:
/1: innerclasses/Multilmplementation.java
II Con clases abstractas o concretas, las clases
II internas son la nica forma de producir el efecto
II de la "herencia de mltiples implementaciones".
package innerclasses
class D {}
abstract class E {}
class Z extends D {
E makeE () ( return new E l) {}; }
public class Multilmplementation
static void takesDID d ) {}
static void takesE(E e) {}
public static void main(String[] args ) {
Z z '" new Z () ;
takesD{z) ;
takesE(z.makeE{)) ;
Si no necesitramos resolver el problema de la '"herencia de mltiples implementaciones", podramos escribir el resto del
programa sin necesidad de utilizar clases internas. Pero con las clases internas tenemos. adems. las siguientes caractersti-
cas adicionales:
1. La clase interna puede tener mltiples instancias, cada una con su propia infonnacin de estado que es indepen-
diente de la infornlacin contenida en el objeto de la clase externa.
2. En una nica clase externa, podemos tener varias clases internas, cada una de las cuales implementa la misma
interfaz o hereda de la misma clase en una fornla diferente. En breve mostraremos un ejemplo de este caso.
3. El punto de creacin del objeto de la clase interna no est ligado a la creacin del objeto de la clase externa.
4. No existe ninguna relacin de tipo "es-un" potencialmente confusa con la clase interna, se trata de una entidad
separada.
Por ejemplo, si Sequence.java no utilizara clases internas, estaramos obligados a decir que "un objeto Sequence es un obje-
to Selector ", y slo podra existir un objeto Selector para cada objeto Sequence concreto. Sin embargo, podramos fcil-
mente pensar en definir un segundo mtodo, reverseSelect or( ), que produjera un objeto Selector que se desplazara en
sentido inverso a travs de la secuencia. Este tipo de flexibilidad slo est disponible con las clases internas.
Ejercicio 22: (2) Implemente reverseSelector ( ) en Sequence.j ava.
Ejercicio 23: (4) Cree una interfaz U con tres mtodos. Cree una clase A con un mtodo que genere una referencia a U_
definiendo una clase interna annima. Cree una segunda clase B que contenga una matriz de U. B debe
tener un mtodo que acepte y almacene una referencia a U en la matriz, un segundo mtodo que configu-
re una referencia en la matriz (especificada mediante el argumento del mtodo) con el valor null, y un ter-
cer mtodo que se desplace a travs de la matriz e invoque los mtodos de U. En main(), cree un grupo
de objetos A y un nico B. Rellene el objeto B con referencias a U generadas por los objetos A. Utilice el
objeto B para realizar llamadas a todos los objetos A. Elimine algunas de las referencias U del objeto 8.
10 Clases internas 229
Cierres y retrollamada
Un cierre (closure) es un objeto invocable que retiene infol1l13cin acerca del mbito en que fue creado. Teniendo en cuen-
ta esta definicin, podemos ver que una clase intem8 es un cierre orientado a objetos, porque no contiene simplemente cada
elemento de infonnacin del objeto de la clase externa ("el mbito en que fue creado"). sino que almacena de manera auto-
mtica una referencia que apunta al propio objeto de la clase externa, en el cual tiene penni so para manipular lodos los
mi embros. incluso aunque sea privados.
Uno de los argumentos ms slidos que se proporcionaron para incluir algn mecanismo de punteros en Java era el de per-
mitir las re/rol/amadas (callbacks). Con una retrollamada, se proporciona a algn otro objeto un elemento de infonnacin
que le pennite llamar al objeto ori ginal en un momento posterior. Se trata de un concepto muy potente, como veremos ms
adelante. Sin embargo, si se implementa una retrollamada utilizando un puntero, nos veremos forzados a confiar en que el
programador se comporte correctamente y no haga un mal uso del puntero. Como hemos visto hasta el momento, el lengua-
je Java tiende a ser bastante ms precavido. por lo que 110 se ban incluido punteros en el lenguaje.
El cierre proporcionado por la clase interna es una buena solucin, bastante ms flexible y segura que la basada en punte-
roS. Veamos un ejemplo:
11 : i nnerclasses/Callbacks .java
II Utili zac i n de clases internas para las retrollamadas
package innerclassesi
import static net . mindview . util .Print .*i
interface Incrementable
void increment() i
II Muy simple para limitarse a implementar la interfaz:
class Calleel implements I ncrementabl e {
private int i = Oi
public void increment () {
i++i
print (i) i
class Mylncrement {
public void increment () print ("Other operation") i
static void f(Mylncrement mi) { mi. increment () ; }
II Si nuestra clase debe implementar increment() de
II alguna otra forma, es necsario utilizar una clase interna:
class Callee2 extends Mylncrement
private int i = Oi
public void increment()
super.increment(} i
i++i
print (i) i
private class Closure implements Incrementable {
publ ic void increment () {
II Especifique un mtodo de la clase externa ; en caso
II contrario, se producira una recursin infinita:
Callee2 . this.increment() i
Incrementable getCallbackReference(}
return new Closure() i
230 Piensa en Java
elass Caller {
private Inerementable eallbaekReferenee;
Caller (Inerementable ebh) { eallbaekReferenee
void 90 () { eallbaekReference. inerement (); }
publie elass Callbaeks {
puble statc vod man (String [] args) {
Calleel el = new Calleel();
Callee2 e2 = new Callee2()
Mylnerement.f{c2)
Caller callerl new Caller{el)
cbh; }
Caller ealler2 = new Caller(e2.getCallbaekReferenee());
callerl. 90 ()
eallerl. 90 () ;
caller2 .90 () ;
caller2. 90 ()
/ * Output:
Other operation
1
1
2
Other operaton
2
Other operaton
3
* /// ,-
Esto muestra tambin una di stincin adicional entre el hecho de implementar una interfaz en una clase externa y el hecho
de hacerlo en una clase interna. Calleel es claramente la solucin ms simpl e en tnninos de cdigo. Callee2 hereda de
Mylncrement, que ya di spone de un mtodo increment( ) diferente que ll eva a cabo alguna tarea que no est relacionada
con la que la interfaz Incrementable espera. Cuando se hereda Mylncremcnt en Callee2, increment( ) no puede ser sus-
tituido para que lo utilice Incrementable. por lo que estamos obl igados a proporcionar una implementacin separada
mediante una clase interna. Observe tambin que cuando se crea una clase interna no se aade nada a la interfaz de la clase
externa ni se la modifica de ninguna manera.
Todo en Callee2 es privado salvo getCallbackReference(). Para permitir algn tipo de conexin con el mundo exterior, la
interfaz Incrementable resulta esencial. Con este ejemplo podemos ver que las interfaces penniten una completa separa-
cin cntre la interfaz y la implementacin.
La clase interna Closure implementa Incrementable para proporci onar un engarce con Callee2, pero un engarce que sea
lo sufici entemente seguro. Cualqui era que obtenga la referencia a Incrementable slo puede por supuesto invocar incre-
ment() y no tiene a su disposicin ninguna otra posibilidad (a diferencia de un puntero, que nos penni tira hacer cualquier
cosa).
Caller toma una referencia a Incrementable en su constructor (aunque la captura de la referencia de retrollamada podra
tener lugar en cualquier instante) y luego en algn momento posterior utiliza la referencia para efectuar una retro llamada a
la clase Callee.
El valor de las retrollamadas radica en su flexibil idad; podemos decidir de manera dinmica qu mtodos van a ser invoca-
do en tiempo de ejecucin. Las ventajas de esta manera de proceder resultarn evidentes en el Captul o 22, lntelfaces gr-
ficas de usuario, en el que emplearemos de manera intensiva las retrolJamadas para implementar la funcionalidad GUl
(Graphical User/I/te/face).
Clases internas y marcos de control
Un ejempl o ms concreto del uso de clases internas es el que puede encontrarse en lo que denominamos marco de control.
10 Clases internas 231
Un marco de lrabajo de una aplicacin es una clase o un conjunto de clases di seado para resolver un tipo concreto de pro-
blema. Para aplicar un marco de trabajo de una aplicacin, lo que nonnalmente se hace es heredar de una o ms clases y
sustituir algunos de los mtodos. El cdigo que escribamos en los mtodos sustituidos sirve para personalizar la solucin
general proporcionada por dicho marco de trabajo de la aplicacin, con el fin de resolver nuestros problemas especficos.
Se trata de un ejemplo del patrn de disclio basado en el mtodo de plantillas (vase Thinking in Pallerns (with Java) en
u'l\'w.A1indView.l1el). El mtodo basado en plantillas contiene la estrucntra bsica del algoritmo, e invoca uno o ms mto-
dos sustituibles con el fin de completar la accin que el algoritmo dictamina. Los patrones de diseo separan las cosas que
no cambian de las cosas que s que sufren modificacin yen este caso el mtodo basado en plantillas es la parte que penna-
nece invariable, mientras que los mtodos sustituibles son los elementos que se modifican.
Un marco de control es un tipo particular de marco de trabajo de apJjcacin, que est dominado por la necesidad de respon-
der a cierto suceso. Los sistemas que se dedican principalmente a responder a sucesos se denominan sistemas dirigidos por
s/lcesos. Un problema bastante comn en la programacin de aplicaciones es la interfaz grfica de usuario (GUI), que est
casi completamente dirigida por sucesos. Como veremos en el Captulo 22, me'laces grficas de usuario, la biblioteca
Swing de Java es un marco de control que resuelve de manera elegante el problema de las interfaces GUI y que utili za de
manera intensiva las clases internas.
Para ver la forma en que las clases internas permiten crear y utili zar de manera sencilla marcos de control , considere un
marco de control cuyo trabajo consista en ejecutar sucesos cada vez que dichos sucesos estn " listos". Aunque "listos"
podra significar cualquier cosa, en este caso nos basaremos en el dato de la hora actual. El ejemplo que sigue es un marco
de control que no contiene ninguna infonnacin especfica acerca de qu es aquello que est controlando. Dicha infonna-
cin se suministra mediante el mecanismo de herencia, cuando se implementa la parte del algoritmo correspondiente al
mtodo .ction().
En primer lugar, he aqu la interfaz que describe los sucesos de control. Se trata de una clase abstracta, en lugar de una ver-
dadera interfaz, porque el comportamiento predeterminado consiste en llevar a cabo el control dependiendo del instante
actual. Por tanto, parte de la implementacin se incluye aqu:
11 : innerclasses/controller/Event.java
II Los mtodos comunes para cualquier suceso de control.
package innerclasses.controller
public abstraet class Event {
private long eventTime
protected final long delayTime
publie Event (long delayTime) {
this.delayTime = delayTime
start () ;
public void start() { II Permite la reinicializacin
eventTime = System.nanoTime() + delayTime
public boolean ready () {
return System.nanoTime () >= eventTime
public abstraet void action()
///,-
El constructor captura el tiempo (medido desde el instante de creacin del objeto) cuando se quiere ejecutar el objeto Event,
y luego invoca start( ), que toma el instante actual y aade el retardo necesario, con el fin de generar el instante en el que
el suceso tendr lugar. En lugar de incluirlo en el constructor, start() es un mtodo independiente. De esta forma, se puede
reinicializar el temporizador despus de que el suceso haya caducado, de manera que el objeto Event puede reutil izarse. Por
ejemplo, si queremos un suceso repet iti vo, podemos invocar simplemente start() dentro del mtodo action().
rcady() nos dice cundo es el momento de ejecutar el mtodo .ction( ). Por supuesto, ready() puede ser sust ituido en una
clase derivada. con el fin de basar el suceso Event en alguna otra cosa distinta del tiempo.
El siguiente archivo contiene el marco de control concreto que gestiona y dispara los sucesos. Los objetos Event se alma-
cenan dentro de un objeto contenedor de tipo List<Event> (una lista de sucesos), que es un tipo de objeto que analizare-
232 Piensa en Java
mas en ms detalle en el Cap nllo 11 , Almacenamiento de objetos. Pero ahora lo nico que necesitamos saber es que add()
aade un objeto Event al final de la lista List, que size() devuelve el nmero de elementos de List , que la sintaxisforeach
penne extraer objetos Event sucesivos de List , y que remove() elimina el objeto Event especificado de List.
11 : innerclasses/ controller / Controller.java
II El marco de trabajo reutilizable para sistemas de control.
package innerclasses.controller
import java.util.*;
public class Controller
1I Una clase de java.util para almacenar los objetos Event:
private List<Event> eventList new ArrayList<Event> () i
public void addEvent (Event c ) ( eventList.add (c ) i }
public void run () (
while {eventList.size {) > O)
II Hacer una copia para no modificar la lista
II mientras se estn seleccionando sus elementos:
for {Event e : new ArrayList<Event>(eventList )
if ( e . ready ( )) (
System.out.println {e) ;
e.action {) ;
eventList.remove {e ) i
El mtodo run() recorre en bucle una copia de eventList, buscando un objeto Event que est listo para ser ejecutado. Para
cada uno que encuentra, imprime infonnacin utilizando el mtodo toString( ) del objeto, invoca el mtodo action( ) y
luego elimina el objeto Event de la lista.
Observe que en este diseo, hasta ahora, no sabemos nada acerca de qu es exactamente lo que un objeto Event hace. Y
ste es precisamente el aspecto fundamental del diseo: la manera en que "separa las cosas que cambian de las cosas que
permanecen iguales". O, por utilizar un tm1ino que a mi personalmente me gusta, el "vector de cambio" est compuesto
por las diferentes acciones de los objetos Event, y podemos expresar diferentes acciones creando distintas subclases de
Event.
Aqu es donde entran en juego las clases internas. Estas clases nos permiten dos cosas:
1. La implementacin completa de un marco de control se crea en una nica clase, encapsulando de esa forma lodas
aquellas caractersticas distintivas de dicha implementacin. Las clases internas se usan para expresar los mlti-
ples tipos distintos de acciones [actionOJ necesarias para resolver el problema.
2. Las clases internas evitan que esta implementacin sea demasiado confusa, ya que podemos acceder fcilmente
a cualquiera de los miembros de la clase externa. Sin esta capacidad, el cdigo podra llegar a ser tan complejo
que terminaramos tratando de buscar una alternativa.
Considere una implementacin concreta del marco de control di seilado para regular las funciones de un invernadero.
4
Cada
accin es totalmente distinta: encender y apagar las luces, iniciar y detener el riego, apagar y encender los termostatos, hacer
sonar alarn18s y reinicializar el sistema. Pero el marco de control est diseado de tal manera que se aslan fcilmente estas
distintas secciones del cdigo. Las clases internas permiten disponer de mltiples versiones derivadas de la misma clase
base, Event, dentro de una misma clase. Para cada tipo de accin, heredamos una nueva clase interna Event y escribimos
el cdigo de control en la implementacin de action().
Como puede suponer por los marcos de trabajo para aplicaciones, la clase GreenhouseControls hereda de ControUer:
11 : innerclasses/GreenhouseControls.java
11 Genera una aplicacin especfica del sistema
4 Por alguna razn, este problema siempre me ha resultado bastante grato de resolver; proviene de mi anterior libro C++/l1Side & Ow, pero Java permite
obtener una solucin ms elegante.
JI de control, dentro de una nica clase. Las clases
JI internas permiten encapsular diferente funcionalidad
1/ para cada tipo de suceso.
import innerclasses.concroller .* ;
public class GreenhouseControls extends Controller {
private boolean light = false;
public class LightOn extends Event
public LightOn(long delayTime) { super (delayTime) ;
public void aetian () {
/1 Poner cdigo de control del hardware aqu
1/ para encender fsicamente las luces.
light = true;
public String toString() { return "Lighe is on" ; }
public class LightOff extends Event
public LightOff (long delayTime) { super (delayTime);
public void aetian () {
JI Poner cdigo de control del hardware aqu
JI para apagar fsicamente las luces .
light = false
public String toString {} { return "Light is off" }
private boolean water = false
public c l ass WaterOn extends Event
public WaterOn (long delayTimel { super (delayTime)
public void action () {
II Poner el cdigo de control del hardware aqu.
water = true;
public String toString(}
return !1Greenhouse water is on" i
public class WaterOff extends Event
public WaterOff ( long delayTime) super(delayTimel
public void action () {
II Poner el cdigo de control del hardware aqu.
water = false
public String toString()
return "Greenhouse water is off" i
private String thermostat = "Day"
public class ThermostatNight extends Event
public ThermostatNight (long delayTime) {
super (delayTime) ;
public void action()
II Poner el cdigo de control del hardware aqu.
thermostat = " Night";
public String toString()
return uThermostat on night setting
lt

10 Clases internas 233
234 Piensa en Java
public class ThermostatDay extends Event
public ThermostatDay{long delayTime) {
super (delayTime ) i
}
public veid actien ()
// Poner el cdigo de control del hardware aqu.
thermostat = "Day"
public String toString{)
return IIThermostat on day setting" i
// Un ejemplo de action {) que inserta un
// nuevo ejemplar de 51 misma en la lnea de sucesos:
public class Bell extends Event {
public Bell (long delayTime) ( super (delayTime)
public void action () {
addEvent(new Bell(delayTime));
public String toString () { return "Bing!" i
public class Restart extends Event {
private Event[] eventList
public Restart (long delayTime, Event [] eventList) {
super {delayTimel ;
this . eventLis t = eventList
for{Event e : eventListl
addEvent (e) i
public void actien ()
fer(Event e : eventList )
e.start(); // Re-ejecutar cada suceso.
addEvent (el i
start(); // Re-ejecutar cada suceso
addEvent (t his ) i
public String toString()
return "Restarting system";
public static class Terminate extends Event {
public Terminate (long delayTime) { super (delayTime) ;
public void action () { System.exit(O) }
public String toString () { return "Terminating" i
}
///,-
Observe que light , water y thermostat pertenecen a la clase externa GreenhouseControls. a pesar de lo cual las clases
internas pueden acceder a dichos campos si n ninguna cualificacin y sin ningn penniso especial. Asimismo, los mtodo:t
action() suelen requerir algn tipo de control del hardware.
La mayoria de las clases Event parecen simi lares, pero BeJl y Restart son especiales. B.JI hace sonar una alarma y luego
aade un nuevo objeto SeU a la lista de sucesos, para que vuelva a sonar posteriormente. Observe cmo las clases internas
casi parecen un verdadero mecanismo de herencia mltiple. SeU y Restart tienen todos los mtodos de Event y tambin
parecen tener todos los mtodos de la clase externa GreenhouseControls.
A Restart se le proporciona una matri z de objetos Event y aqulla se encarga de aadirla al controlador. Puesto que
Restart() es simplemente otro objeto Event, tambin se puede aadir un objeto Restart dentro de R.start.action() para
que el sistema se reinicialice a s mismo de manera peridica.
10 Clases internas 235
La siguiente clase configura el sistema creando un objeto GreenhouseControls y aadiendo di versos tipos de objetos
Event. Esto es un ejemplo del patrn de diseo Command: cada objeto de eventList es una soli citud encapsulada en forma
de objeto:
1/ : innerclasses/ GreenhouseController.java
1/ Configurar y ejecutar el sistema de control de invernadero.
11 {Args, sooo }
i mport innerclasses.controller .*
public class GreenhouseController
public static void main (String[] args)
GreenhouseControls gc = new GreenhouseControls( ) i
/1 En lugar de fijar los va l ores, podramos analizar
JI informacin de conf iguracin incluida
JI en un archivo de texto:
gc . addEvent (gc.new Bell(900)) i
Event(] eventList = {
} ;
gc .new ThermostatNight(O),
gc.new LightOn(200),
gc .new LightOf f (400),
gc.new WaterOn(600),
gc . new WaterOff(800 ) ,
gc . new ThermostatDay(1400)
gc.addEvent(gc . new Restart(2000, eventList)) i
if(args . length == 1)
gc .addEvent(
new GreenhouseControls . Terminate(
new Integer(args[OI)) ) i
gc . run () i
1* Output :
Bing!
Thermostat on night setting
Light is on
Light is off
Greenhouse water is on
Greenhouse water is off
Thermostat on day setting
Restarting system
Terminating
*1// ,-
Esta clase inicializa el sistema, para que aada todos los sucesos apropiados. El suceso Restart se ejecuta repetidamente y
carga cada vez la lista eventList en el objeto GreenhouseControls. Si proporcionamos un argumento de lnea de coman-
dos que indique los mili segundos, Restart tennillar el programa despus de ese nmero de milisegundos especificado (esto
se usa para las pruebas).
Por supuesto, resulta ms flexible leer los sucesos de un arclvo en lugar de leerlos en el cdigo. Uno de los ejercicios del
Captulo 18, E/S, pide, precisamente, que modifiquemos este ejemplo para hacer eso.
Este ejemplo debera pemtir apreciar cul es el valor de las clases internas, especialmente cuando se las usa dentro de un
marco de control. Sin embargo. en el Captulo 22, me/faces grficas de usuario, veremos de qu [onna tan elegante se uti-
li zan las clases internas para definir las acciones de una interfaz grfica de usuario. Al tenninar ese captulo, espero haber-
le convencido de la utilidad de ese tipo de clases.
Ejercicio 24: (2) En GreenhouseControls.java, aada una serie de clases internas Event que pennitan encender y apa-
gar una serie de ventiladores. Configure GreenhouseController.java para utilizar estos nuevos objetos
Event.
236 Piensa en Java
Ejercicio 25: (3) Herede de GreenhouseControls en CreenhouseControls.jav3 para ai1adir clases internas Event
que pennit an encender y apagar una seri e de vapori zadores. Escriba una nueva versin de
GreenhouseController.java para util izar estos nuevos objetos Event.
Cmo heredar de clases internas
Puesto que el constructor de la clase interna debe efectuar la asociacin como una referencia al objeto de la clase contene
dora, las cosas se compli can li geramente cuando fratamos de heredar de Ulla clase interna. El problema es que la referencia
"secreta" al objeto de la clase contenedora debe inicializarse. a pesar de lo cual en la clase derivada no hay ningn objeto
predetenninado con el que asociarse. Es necesario utili zar una sinraxis especial para que dicha asociacin se haga de fonna
explcita:
// : innerclassesjlnheritlnner.java
jI Heredando de una clase interna.
class Withlnner
class Inner {}
public class Inheritlnner extends WithInner.Inner
II! Inheritlnner () {} lINo se compilar
Inheritlnner (Withlnner wi) {
wi. super ()
public static void main (String [) args) {
Withlnner wi = new Withlnner()
Inheritlnner ii = new Inheritlnner(wi) i
Puede ver que Inheritlnncr slo ampla la clase interna, no la externa. Pero cuando ll ega el momento de crear un construc
tor, el predeterminado no sirve y no podemos limitarnos a pasar una referencia a un objeto contenedor. Adems, es necesa
rio utihzar la si ntaxis:
enclosingClassReference.super()
dentro del const ructor. Esto proporciona la referencia necesaria y el programa podr as compilarse.
Ejercicio 26: (2) Cree una clase con una clase interna que tenga un constructor no predetenninado (uno que tome argu-
mentos). Cree una segunda clase con una clase interna que herede de la primera clase interna.
Pueden sustituirse las clases internas?
Qu sucede cuando creamos una clase interna, luego heredamos de la clase contenedora y redefinimos la clase interna? En
otras palabras, es posible "sustituir" la clase interna completa? Podra parecer que esta tcnica resultara muy til , pero el
"sustituir" una clase interna como si fuera otro mtodo cualquiera de la clase externa no tiene, en realidad, ningn efecto:
11 : innerclasses/BigEgg.java
II No se puede sustituir una clase interna como si fuera un mtodo.
import static net.mindview.util.Print .*
class E99 {
private Yolk y
protected class Yolk {
public Yolk() { print{"Egg Yolk() "); }
}
public Egg () {
print ("New Egg () " ) ;
y = new Yolk () ;
public class BigEgg extends Egg {
public class Yolk {
publ ic Yolk () { print (" BigEgg Yolk (1 ") ;
public static void main (String [J args) {
new BigEgg () i
/ * Output:
New Egg ()
Eg9. Yolk ()
*///,-
10 Clases internas 237
El compi lador sintetiza automticament e el constructor predeterminado, y ste invoca al constructor predeterminado de la
clase base. Podramos pensar que puesto que se est creando un objeto BigEgg, se utili zar la versin "sustituida" de Yolk,
pero esto no es as , como podemos ver ana lizando la salida.
Este ejemplo muestra que no hay ningn mecani smo mgi co adicional relacionado con las clases internas que entre en
accin al heredar de la clase externa. Las dos clases internas son entidades completamente separadas, cada una con su pro-
pio espacio de nombres. Sin embargo. lo que sigue siendo posible es heredar explci tamente de la clase interna:
JI: innerclassesjBigEgg2.java
JI Herencia correcta de una clase interna.
import static net . mindview . util.Print.*
class Egg2 {
protected class Yolk {
public Yolk () { print (" Egg2 . Yolk () "); }
public void fl) {print("Egg2.Yolk.f()");}
private Yolk y = new Yolk ();
public Egg2 () { print ( "New Egg2 () "); }
public void insertYolk{Yolk yy) { Y = yy
public void 9 () { y . f (); }
public class BigEgg2 extends Egg2 {
public class Yolk extends Egg2.Yolk
public Yolk () { print (" BigEgg2 . Yolk () "); }
public void f () { print ("BigEgg2. Yolk. f () ") ;
public BigEgg2 () { insertYolk (new Yolk () ) ;
public static void main (String [] args) {
Egg2 e2 = new BigEgg2();
e2.g() ;
1* Output:
Egg2 . Yolk ()
New Egg2 ()
Egg2 . Yolk ()
BigEgg2 . Yolk ()
BigEgg2 . Yolk.f()
*/ / / >
Allora, BigEgg2.Yolk ampla explcitamente extends Egg2.Yolk y sustituye sus mtodos. El mtodo insertYolk( l pennite
que BigEgg2 generalice uno de sus propios objetos Yolk a la referencia y en Egg2, por lo que g( l invoca y.f( l , se utili za
la versin sustituida de f() . La segunda llamada a Egg2.Yolk() es la llamada que el constructor de la clase base hace al
constructor de BigEgg2. Yolk. Como puede ver, cuando se llama a g( l se utili za la versin susti tuida de f( l
238 Piensa en Java
Clases internas locales
Corno hemos indicado anterionnente. tambin pueden crearse clases internas dentro de bloques de cdigo, nonnalmente
dentro del cuerpo de un mtodo. Una clase interna local no puede tener un especificador de acceso, porque no fonna pane
de la clase externa, pero si que tiene acceso a las variables finales del bloque de cdigo actual y a todos los miembros de la
clase contenedora. He aqu un ejemplo donde se compara la creacin de una clase interna local con la de una clase interna
annima:
11: innerclasses/LocallnnerClass.java
II Contiene una secuencia de objetos.
import static net.mindview.util.Print.*;
interface Counter
int next () ;
public class LocallnnerClass {
private int count = O;
Counter getCounter (final String name) {
II Una clase interna local:
class LocalCounter implements Counter
public LocalCounter () {
II La clase interna local puede tener un constructor
print{IILocaICounter(}11) ;
public int next () {
printnb(name); II Acceso a variable local final
return count++;
return new LocaICounter();
II Lo mismo con una clase interna annima :
Counter getCounter2 (final String name) {
return new Counter() {
II La clase interna annima no puede tener un constructor
II nominado , sino slo un inicializador de instancia:
{
print ( 11 Counter () 11 ) ;
public int next () {
)
) ;
printnb(name) II Acceso a una variable local final
return count++;
public static void main (String [] args) {
LocallnnerClass lic = new LocallnnerClass();
Counter
el = lic . getCounter(IILoeal inner "),
c2 = lic.getCounter2 ( IIAnonymous inner " );
for(int i = O; i < 5; i++)
print(el.next ()) ;
for(int i = O; i < 5; i++)
print (e2. next () ) ;
1* Output :
LocalCounter ()
Counter ()
10 Clases internas 239
Local inner O
Local inner 1
Local inner 2
Local inner 3
Local inner 4
Anonyrnous inner 5
Al1onyrnous inner 6
Anonymous inner 7
Anonymous inner 8
Anonymous inner 9
* /// ,-
Counter devuelve el siguiente valor de una secuencia. Est implementado como una clase local y como una clase interna
annima, teniendo ambas los mismos comportamientos y capacidades. Puesto que el nombre de la clase interna local no es
accesible fuera del mtodo, la nica justificacin para utilizar una clase interna local en lugar de una clase interna annima
es que necesitemos un constructor nominado y/o un constructor sobrecargado, ya que una clase interna annima slo puede
utilizar un mecanismo de inicializacin de instancia.
Otra razn para definir una clase interna local en lugar de una clase interna annima es que necesitemos construir ms de
un objeto de dicha clase.
Identificadores de una clase interna
Puesto que todas las clases generan un archivo .cl ass que almacena toda la infonnacin relativa a cmo crear objetos de
dicho tipo (esta infornlacin genera una "metaclase" denominada objeto Class), podemos imaginar fcilmente que las cIa-
ses internas tambin debern producir archivos .class para almacenar la infonnacin de sus objetos Class. Los nombres de
estos archivos Iclasses responden a una frnlUla estricta: el nombre de la clase contenedora, seguido de un signo ' $', segui-
do del nombre de la clase interna. Por ejemplo, los archivos .class creados por Locallnner Class.java incluyen:
Counter.class
LocallnnerClass$l.class
LocallnnerClass$lLocalCounter.class
LocallnnerClass.class
Si las clases internas son annimas, el compilador simplemente genera una serie de nmeros para que acten como identi-
fi cadores de las clases internas. Si las clases internas estn anidadas dentro de otras clases internas, sus nombres se aaden
simplemente despus de un '$' y del identificador o identificadores de las clases externas.
Aunque este esquema de generacin de nombres internos resulta simple y directo, tambin es bastante robusto y pennite tra-
Lar la mayora de las sittlaciones.
5
Puesto que se trata del esquema estndar de denominacin para Java, los archivos gene-
rados son automticamente independientes de la platafomla (tenga en cuenta que el compilador Java modifica las clases
internas de mltiples maneras para hacer que funcionen adecuadamente).
Resumen
Las interfaces y las clases internas son conceptos bastante ms sofisticados que los que se pueden encontrar en muchos len-
guajes de programacin orientada a objetos; por ejemplo, no podremos encontrar nada similar en C++. Ambos conceptos
resuelven, conjuntamente, el mismo problema que C++ trata de resolver con su mecanismo de herencia mltiple. Sin embar-
go, el mecanismo de herencia mltiple en C++ resulta bastante dificil de uti lizar, mientras que las interfaces y las clases
internas de Java son, por comparacin, mucho ms accesibles.
Aunque estas funcionalidades son, por s mismas, razonablemente senci ll as, el uso de las mismas es una de las cuestio-
nes fundamentales de diseo, de fonna simi lar a lo que ocurre con el polimorfismo. Con el tiempo, aprender a reconocer
S Por otro lado, '$' es un mctacarcter de la shell Unix, por lo que en ocasiones podemos encontramos con problemas a la hora de listar los archivos .class.
Resulta un tamo extrao este problema, dado que el lenguaje Java ha sido definido por Sun, una empresa volcada en el mercado Unix. Supongo que no
tuvieron en cuenta el problema, pensando en quc los programadores se centrarian principahneme en los archivos de cdigo fuente.
240 Piensa en Java
aquellas situaciones en las que debe utili zarse una interfaz o una clase interna. o ambas cosas. Pero almenas, en este pUnto
del libro, s que el lector debera sentirse cmodo con la sintaxis y la semntica aplicables. A medida que vaya viendo cmo
se aplican estas funcionalidades, tenninar por interiori zarlas.
Puede encontrm las soluciones a los ejercicios seleccionados en el documento electrnico rile Thinkil/g ;1/ )OI'Q Anllotaled SOllllioll GlIide, disponible para
la venta en II'ww.MilldView."el.
Al macenamiento
de objetos
Es un programa bastante simple que slo dispone de una cantidad de objetos con tiempos de
vida conocidos.
En general, los programas siempre crearn nuevos objetos basndose en algunos criterios que slo sern conocidos en tiem-
po de ejecucin. Antes de ese momento, no podemos saber la cantidad ni el tipo exacto de los objetos que necesitamos. Para
resolver cualquier problema general de programacin, necesitamos poder crear cualquier nmero de objetos en cualquier
momento y en cualquier lugar. Por tanto, no podemos limitarnos a crear una referencia nominada para almacenar cada uno
de los objetos:
MiTipo unaReferencia
ya que no podemos saber cuntas de estas referencias vamos a necesitar.
La mayora de los lenguajes proporciona alguna manera de resolver este importante problema. Java dispone de varias for-
mas para almacenar objetos (o ms bien, referencias a objetos). El tipo soportado por el compilador es la matriz, de la que
ya hemos hablado antes. Una matriz es la fonna ms eficiente de almacenar un gmpo de objetos, y recomendamos utilizar
esta opcin cada vez que se quiera almacenar un grupo de primitivas. Pero una matriz tiene un tamao fijo y. en el caso ms
genera l, no podemos saber en el momento de escribir el programa cuntos objetos vamos a necesitar o si har falta una fomla
ms sofisticada de almacenar los objetos, por lo que la restriccin relativa al tama'io fijo de una matriz es demasiado limi-
tanteo
La biblioteca j ava.util tiene un conjunto razonablemente completo de clases contenedoras para resolver este problema,
siendo los principales tipos bsicos List, Set, Queuc y Map (lista, conjunto. cola y mapa). Estos tipos de objetos tambin
se conocen COD el nombre de clases de coleccin, pero como la biblioteca Java utiliza el nombre Collection para hacer refe-
renci a a un subconjunto concreto de la biblioteca, en este texto utilizaremos el tnnino ms general de 'contenedor". Los
contenedores proporcionan fonnas sofi sticadas de almacenar los objetos, y con ellos podemos resolver un sorprendente
nmero de problemas.
Adems de tener otras caractersticas (Set, por ejemplo, slo almacena un objeto de cada valor mientras que Map es una
matriz asociativa que pennite asociar objetos con otros objetos), las clases contenedoras de Java tienen la funcionalidad de
cambiar automticamente de tamai'io. Por tanto, a diferencia de las matrices, podemos almacenar cualquier nmero de ob-
jetos y no tenemos que preocupamos, mientras estemos escribiendo el programa, del tamao que tenga que tener el conte-
nedor.
An cuando no tienen soporte directo mediante palabras clave de Java, I las clases contenedoras son herramientas funda-
mentales que incrementan de manera significativa nuestra potencia de programacin. En este captulo vamos a aprender los
aspectos bsicos de la biblioteca de contenedores de Java poniendo el nfasis en la ut ilizacin tpica de los contenedores.
Aqu, vamos a centramos en los contenedores que se uti lizan de manera cotidiana en las tareas de programacin.
Posterionnente, en el Captulo 17, Anlisis de/aliado de los cOnlenedores, veremos el resto de los contenedores y una serie
de detalles acerca de su funcionalidad y de cmo utilizarlos.
! Diversos lenguajes como PerL Payton y Ruby tienen soporte nativo para los contenedores.
242 Piensa en Java
Genricos y contenedores seguros respecto al tipo
Uno de los problemas de utili zar los contenedores anteriores a Java SE5 era que el compilador penl1ita insertar un tipo
rrecto dentro de un contenedor. Por ejemplo, consi dere un contenedor de objetos Apple que utilice el contenedor ms
co general, ArrayList. Por ahora. podemos considerar que ArrayList es "una matriz que se expande automticamente". La
utilizacin de una matTiz Ar rayList es bastante simple: basta con crear una, insertar objetos utilizando addQ y acceder a
ellos mediante getQ, utilizando un ndice: es lo mi smo que hacemos con las matrices, pero sin emplear corchetes. 2
ArrayList tambin dispone de un mtodo size( ) que nos pennite conocer cunros elementos se han aadido, para no
zar inadvertidamente ndices que estn ms all del contenedor que provoquen un error (generando una excepcin de
po de ejecucin; hablaremos de las excepciones en el Captulo 12, Tratamiento de errores medio1l1e excepciones).
En este ejemplo. insertamos en el contenedor objetos Apple y Orange y luego los extraemos. Nornlalmente, el compilador
Java proporcionar una advertencia, debido a que el ejemplo no lisa genricos. Aqu, empleamos una anotacin de Java SES
para suprimir esas advertencias del compilador. Las anotaciones comienzan con un signo de '@', y admiten un argumento;
esta anotacin en concreto es @Suppress\Varnings y el argumento indica que slo deben suprimirse las advertencias no
comprobadas ("lIl1checked'):
11: holding/ApplesAndOrangesWichoutGenerics.java
1I Ejemplo simple de contenedor (produce advertencias del compilador).
II {ThrowsException}
import java . util.*;
class Apple
private static long counter;
prvate final l ong id = counter++
public long id () { reCurn id; }
elass Orange {}
public class ApplesAndOrangesWithoutGenerics
@SuppressWarnngs ( "unchecked" )
public static void main(String[] args)
ArrayList apples = new ArrayList()
for(int i = O; i < 3; i++)
apples.add(new Apple());
II No se impide aadir un objeto Orange:
apples.add(new Orange(;
for(int i = O; i < apples.size() i i++)
( (Apple) apples .get (i) ) . id 11 ;
II Orange slo se detecta en tiempo de ejecucin
1* (Execute to see output) * 1 I 1:-
Hablaremos ms en detalle de las anotaciones Java SE5 en el Captulo 20, Anotaciones.
Las clases Apple y Orange son diferentes; no tienen nada en comn salvo que ambas heredan de Object (recuerde que si
no se indica explcitamente de qu clase se est heredando, se hereda automticamente de Object). Puesto que ArrayList
almacena objetos de tipo Objcct, no slo se pueden aliad ir objetos Apple a este contenedor utilizando el mtodo add() de
ArrayList, si no que tambin se pueden aadir objetos Orange sin que se produzca ningn tipo de advertencia ni en
po de compilacin ni en tiempo de ejecucin. Cuando luego tratamos de extraer lo que pensamos que son objetos Apple
li zando el mtodo gct() de ArrayList, obtenemos una referencia a un objeto de tipo Object que deberemos proyectar sobre
un objeto Apple. Entonces, necesitaremos encerrar toda la expresin entre parntesis para forzar la evaluacin de la
cin antes de invocar el mtodo id( ) de Apple; en caso contrario. se producir un error de sintaxis.
2 ste es uno de Jos casos en los que la sobrecarga de operadores resultara convenieOfe. Las clases contenedoras de C++ y C# producen una sintaxis [mIS
limpia utilizando la sobrecarga de operadores.
11 Almacenamiento de objetos 243
En tiempo de ejecucin, al intentar proyectar el obj eto Orange sobre un objeto Apple. obtendremos un error en la farnla de
la excepcin que antes hemos mencionado.
En el Capnilo 20, Genricos, veremos que la creacin de clases utilizando los genricos de Java puede resultar muy com-
pleja. Sin embargo, la aplicacin de clases genricas predefinidas suele resultar sencilla. Por ejemplo. para defmir un con-
tenedor ArrayList en el que almacenar objetos Apple, tenemos que indicar ArrayList<Apple> en lugar de slo Arra:yList.
Los corchetes angulares rodean los parmetros de lipo (puede haber ms de uno) , que especifican el tipo o tipos que pue-
den almacenarse en esa instancia del contenedor.
Con los genricos evitamos, en tiempo de compilacin, que se puedan introducir objetos de tipo incorrecto en un contene-
dor. } He aqu de nuevo el ejemplo utilizando genricos:
ji : holding/ApplesAndOrangesWithGenerics . java
import java.util. *
public class ApplesAndOrangesWithGenerics
public static void main (String [] args) {
ArrayList<Apple> apples = new ArrayList<Apple>();
for(int i = o; i < 3 i++)
apples.add{new Apple(;
1/ Error en tiempo de compilacin:
11 apples.add(new Orange (;
for{int i == O; i < apples.size(); i++)
System. out. println (apples. get (i) . id () )
1/ Utilizacin de foreach:
for(Apple c : apples)
System.out.println(c.id(
/ * Output:
Ahora el compilador evitar que introduzcamos un objeto Orange en apples, convirtiendo el error en tiempo de ejecucin
en un error de tiempo de compi lacin.
Observe tambin que la operacin de proyeccin ya no es necesaria a la hora de extraer los elementos de la lista. Puesto que
la li sta conoce qu tipos de objetos almacena, ella misma se encarga de realizar la proyeccin por nosotros cuando invoca-
mos get(). Por tanto. con los genricos no slo podemos estar seguros de que el compilador comprobar el tipo de los obje-
tos que introduzcamos en un contenedor, sino que tambin obtendremos una si ntaxi s ms limpia a la hora de utilizar los
objetos almacenados en el contenedor.
El ejemplo muestra tambin que, si no necesitamos utilizar el ndice de cada elemento, podemos utilizar la sintaxi sjoreach
para seleccionar cada elemento de la li sta.
No estamos limitados a almacenar el tipo exacto de objeto dentro de un contenedor cuando especificamos dicho tipo corno
un parmetro genrico. La operacin de generalizacin (upcasting) funciona de igual fonna con los genricos que con los
dems tipos:
11 : holding/ GenericsAndUpcasting.java
import java.util.*;
class GrannySmith extends Apple {}
class Gala extends Apple {}
) Al final del Capitulo 15. Gen/'icos, se incluye una explicacin sobre la gravedad de este problema. Sin embargo, dicho captulo tambin explica que los
genricos de Java resultan tiles para otras cosas adems de definir contenedores que sean seguros con respecto al tipo de los datos.
244 Piensa en Java
class Fuji extends Apple {}
class Braeburn extends Apple {}
public class GenericsAndUpcasting
public static void main(String[] args}
ArrayList<Apple> apples = new ArrayList<Apple>(};
apples.add(new GrannySmith());
apples.add(new Gala()};
apples.add(new Fuji()};
apples.add{new Braeburn(}};
for(Apple e : apples}
System.out.println(c) ;
1* Output: (Samplel
GrannySmith@7d772e
Gala@11b86e7
Fuji @35ce36
Braeburn@757aef
* /// ,-
Por tanto, podemos aiiadir un subtipo de Apple a un contenedor que hayamos especifi cado que va a almacenar objetos
Apple.
La salida se produce utilizando el mtodo toString() predetenninado de Object, que imprime el nombre de la clase segui-
do de una representacin hexadecimal sin signo del cdigo hash del objeto (generado por el mtodo hashCode( )l. Veremos
ms detalles acerca de los cdigos hash en el Captulo 17, Anlisis detallado de los contenedores.
Ejercicio 1: (2) Cree una nueva clase llamada Gerbil con una variable int gerbilNumber que se inicializa en el cons-
tructor. Aada un mtodo denominado hop( ) que muestre el valor almacenado en esa variable entera. Cree
una lista ArrayList y aada objetos Gerbil a la li sta. Ahora, utilice el mtodo get() para desplazarse a
travs de la li sta e invoque el mtodo hop( ) para cada objeto Gerbil.
Conceptos bsicos
La biblioteca de contenedores Java toma la idea de "almacenamiento de los objetos" y la divide en dos conceptos di stintos,
expresados mediante las interfaces bsicas de la biblioteca:
1. Collection: una secuencia de elementos individuales a los que se apli ca una o ms reglas. Un contenedor List
debe almacenar los elementos en la fom1a en la que fueron insertados, un contenedor Set no puede tener elemen-
tos duplicados y un contenedor Queue produce los elementos en el orden determinado por una disciplina de cofa
(que nonnalmente es el mismo orden en el que fueron insertados).
2. Map: un grupo de parejas de objetos clave-valor, que permite efectuar bsquedas de valores utilizando una clase.
Un contenedor ArrayList pennite buscar un objeto utilizando un nmero, por lo que en un cierto sentido sirve
para asociar nmeros con los objelOs. Un mapa permite buscar un objeto utilizando otro objeto. Tambin se le
denomina matriz asociativa, (porque asocia objetos con otros objetos) o diccionario (porque se utili za para bus-
car un objeto valor mediante un objeto clave. de la mi sma fonTIa que buscamos una definicin utili zando una
palabra). Los contenedores Map son herramientas de programacin muy potentes.
Aunque no siempre es posible. deberamos tratar de escribir la mayor parte del cdigo para que se comunique con estas
interfaces; asi mi smo, el nico lugar en el que deberamos especificar el tipo concreto que estemos usando es el lugar de la
creacin del contenedor. As, podemos crear un contenedor List de la fonna siguiente:
List<Apple> apples = new ArrayList<Apple> ();
Observe que el contenedor ArrayList ha sido general izado a un contenedor List, por contraste con la fonna en que lo haba-
mos tratado en los ejemplos anteriores. La idea de utilizar la interfaz es que, si posteriormente queremos cambiar la imple-
mentacin, lo ni co que tendremos que hacer es efectuar la modifi cacin en el punto de creacin, de la fomla siguiente:
List<Apple> apples = new LinkedList<Apple>(};
11 Almacenamiento de objetos 245
As, nonna lmente crearemos un objeto de una clase concreta. lo generalizaremos a la correspondiente interfaz y luego uti-
lizaremos la interfaz en el resto del cdigo.
Esta tcnica no siempre sirve. porque algunas clases di sponen de funcionalidad adicionaL Por ejemplo, LinkedList tiene
mtodos adicionales que no fonllan parte de la interfaz List mientras que TreeMap tiene mtodos que no se encuentran en
la interfaz Map. Si necesitamos usar esos mtodos, no podremos efectuar la operacin de generalizacin a la interfaz ms
generaL
La interfaz Collection generaliza la idea de secuencia: una forma de almacenar un grupo de objetos. He aqu un ejemplo
simple que rellena un contenedor CoUection (representado aqu mediante un contenedor ArrayList) con objetos Illteger y
luego imprime cada elemento en el contenedor resultante:
11 : holding/SimpleCollection java
import java . util. *
public class SimpleCollection
public static void main(String[] argsl (
COl lection<Integer> e = new ArrayList<Integer> ();
for (int i = O i < 10 i++)
c . add(il II Autoboxing
for(Integer i : el
System out.print(i + 11 );
1* Output:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
. /// ,-
Puesto que este ejemplo slo utiliza mtodos Collection, cualquier objeto que herede de una clase de Collection funciona-
r, pero ArrayList representa el tipo ms bsico de secuencia.
El nombre del mtodo add() sugiere que ese mtodo introduce un nuevo elemento en el contenedor Collection. Sin embar-
go, la documentacin indi ca expresamente que add() "garanti za que este objeto Collection contenga el elemento especifi-
cado". Esto es as para pennitir la existencia de contenedores Set, que aaden el elemento slo si ste no se encuentra ya en
el contenedor. Con un objeto ArrayList, o con cualquier tipo de List, add( ) siempre significa " insertar el elemento", por-
que las listas no se preocupan de si existen duplicados.
Todas las colecciones pueden recorrerse utilizando la sintaxi sforeach, como hemos mostrado aqu. Ms adelante en el cap-
tulo aprenderemos acerca de un concepto ms flexibl e denominado I!erador.
Ejercicio 2: (1) Modifique SimpleCollection.java para utilizar un contenedor Set para c.
Ejercicio 3: (2) Modifique innerclasses/Sequence.java de modo que se pueda aadir cualquier nmero de elementos.
Adicin de grupos de elementos
Existen mtodos de utilidad en las clases de matrices y colecciones de java.util que aaden grupos de elementos a una colec-
cin. El mtodo Arrays.asList( ) toma una matri z y una li sta de elementos separados por comas (utilizando varargs) y lo
transforma en un objeto List. Collections.addAII( ) loma un objeto Collection y una matriz o una li sta separada por comas
y aliade los elementos a la coleccin. He aqu un ejemplo donde se ilustran ambos mtodos, as como el mtodo addAII( )
ms convencional que forma parte de todos los tipos Collection:
11 : holding/AddingGroups . java
II Adicin de grupos de elementos a objetos Collection.
import java.util.*;
publie elass AddingGroups
public statie void main(String[] argsl {
Collection<Integer> eollection
new ArrayList<Integer:> (Arrays . asList (1, 2, 3,4,5;
Integer[l morelnts " { 6, 7, B, 9, 10 };
246 Piensa en Java
collection.addAll (Arrays.asList (morelnts )) ;
II Se ejecuta bastante ms rpido, pero no se puede
II construir una coleccin de esta forma:
Collections.addAll (collection, 11, 12, 13, 14, 15 )
Collections.addAll (collection, morelnts) i
II Produce una lista 11 respaldada 11 en una matriz:
Listclnteger> list = Arrays.asList ( 16, 17, 18, 19, 20) ;
list.set ( 1, 99 ) II OK - - modificar un elemento
II list.add(21 ) II Error de ejecucin porque la matriz
II subyacente no se puede cambiar de tamao.
El constructor de una coleccin puede aceptar otra coleccin con el fin de utilizarla para inicializarse a s misma, as que
podemos emplear Arrays.asLisl( ) para generar la entrada para el constructor. Sin embargo, Collections.addAII( ) se eje.
cuta mucho ms rpido y resulta igualmente sencillo construir el objeto Collection sin ningn elemento y luego invocar
Collections.addAII( ), por lo que sta es la tcnica que ms se suele utili zar.
El mtodo miembro Colleclion.addAII( ) slo puede tomar como argumento otro objeto Colleclion, por lo que no es tan
flexible como Arrays.asList( ) o Collections.addAII( J, que utili zan li stas de argumentos variables.
Tambin es posible utilizar directamente la salida de Arrays.asListO como una li sta, pero la representacin subyacente en
este caso es la matri z, que no se puede cambiar de tamao. Si tratamos de aadir o borrar elementos en dicha li sta, eso impli.
cara cambiar el tamao de la matriz, por lo que se obtiene un error "Unsupported Operation" en tiempo de ejecucin.
Una limitacin de Arrays.asList() es que dicho mtodo trata de adivinar cul es el tipo de lista resultante, sin prestar aten
cin a la variable a la que la estemos asignando. En ocasiones, esto puede causar un problema:
11 : holding/AsListlnference.java
I I Arrays.asList( ) determina el tipo en s mismo.
import java.util.*
elass Snow {}
class Powder extends Snow {}
class Light extends Powder {}
class Heavy extends Powder {}
class Crusty extends Snow { }
class Slush extends Snow {}
public class AsListlnference
public static void main {String(] args l
List c Snow> snow1 = Arrays.asList {
new Crusty {) , new Slush () , new Powder ()) i
II No se compilar:
11
11
11
11
11
ListcSnow> snow2 = Arrays.asList {
new Light{ ) , new Heavy ( ) ) ;
El compilador dir:
found java.util.List<Powder>
required: java.util.ListcSnow>
1I Collections .addAll () no se confunde :
ListcSnow> snow3 = new ArrayListcSnow> ()
Collections.addAll (snow3, new Light ( ), new Heavy {) i
II Proporcionar una indicacin utilizando una
II especificacin explcita del tipo del argumento:
ListcSnow> snow4 = Arrays.cSnow>asList {
new Light{), new Heavy {));
11 Almacenamiento de objetos 247
Al tratar de crear snow2, Arrays.asList( ) slo di spone del tipo Powder, por lo que crea un objeto List<Powder> en lugar
de List<Snow>, mientras que Collections.addAII() funciona correctamente, porque sabe, a partir del primer argumento,
cul es el tipo de destino.
Como puede ver por la creacin de snow4, es posibl e insertar una "indicacin" en mitad de Arrays.asList(), para decirle
al compilador cul debera ser el tipo de destino real para el objeto List producido por Arrays.asList(). Esto se denomina
especificacin explcita del tipo de argumento.
Los mapas son ms compl ejos como tendremos oportunidad de ver, y la biblioteca estndar de Java no proporciona ningu-
na fonna para iniciali zarlos de manera automtica. salvo a partir de los contenidos de otro objeto Map.
Impresin de contenedores
Es necesario utilizar Arrays.toString( ) para generar una representacin imprimible de una matri z, pero los contenedores
se imprimen adecuadamente sin ninguna medida especial. He aqu un ejemplo que tambin nos va a pennitir presentar los
contenedores bsicos de Java:
11: holding/PrintingContainer s.java
II Los contenedores se imprimen automticamente.
import java.util. * ;
import static net.mindview. util.Print.*
public class PrintingContainers {
static Collection fill(Collection<String> collection) {
collection.add ( urat
U
) ;
collection.add(Ucat
U
) ;
col lection. add ( " dog 11) ;
collection. add(Udog
U
) ;
return collection
static Map fill (Map<St ring,String> map) {
map.put(IIrat", "Fuzzyll);
map .put ( "cat
U
, "Rags");
map .put ( IIdog", "Bosco")
map.put("dog", "Spot");
return map;
public static void main{String[1 args)
print(fill(new ArrayList<String>()));
print(fill(new LinkedList<String>()));
print(fill(new HashSet<String>()));
print(fill(new TreeSet<String>()));
print(fill(new LinkedHashSet<String>()));
print(fill(new HashMap<String,String>()));
print(fill(new TreeMap<String,String>()));
print{fill(new LinkedHashMap<String,String>())) i
1* Output:
[rat, cat, dog, dog]
[rat, cat, dog, dog]
[dog, cat, rat]
[cat, dog, rat]
[rat, cat, dog]
cat=Rags,
dog=Spot,
dog=Spot}
*///,-
Este ejemplo muestra las dos categoras principales en la biblioteca de contenedores de Java. La distincin entre ambas se
basa en el nmero de elementos que se almacenan en cada "posicin" del contenedor. La categora Collection slo almace-
248 Piensa en Java
na un elemento en cada posicin; esta categora incluye el objeto List, que almacena un gnlpo de elementos en una secuen.
cia especificada, el objeto Set, que no pemlite ailadir un elemento idntico a otro que ya se encuentre dentro del conjunto y
el objeto Queue. que slo pennite insertar objetos en un "extremo" del contenedor y extraer los objclOs del arra "extremo"
(en lo que a este ejemplo respecta se tratara simplemente de otra forma de manipular una secuencia, por lo que no lo hemos
incluido). Un objeto Map, por su parte, almacena dos objetos, una clave y un valor asociado, en cada posicin.
A la salida, podemos ver que el comportamiento de impresin predeterminado (que se implementa mediante el mtodo
toString() de cada contenedor) genera resultados razonablemente legibles. Una coleccin se imprime rodeada de corche.
tes, estando cada elemento separado por una coma. Un mapa estar rodeado de llaves, asocindose las claves y los valores
mediante un signo igual (las claves a la izquierda y los valores a la derecha).
El primer mtodo fill() funciona con todos los tipos de coleccin, cada uno de los cuales se encarga de implementar el meto.
do add() para incluir nuevos elementos.
ArrayList y LinkedList son tipos de li stas y, como puede ver a la salida, ambos almacenan los elementos en el mi smo orden
en el que fueron insertados. La diferencia entre estos dos tipos de objeto no est slo en la velocidad de ciertos tipos de ope.
raciones. sino tambin en que la clase LinkedList contiene ms operaciones que ArrayList. Hablaremos ms en detalle de
estas operaciones posterionnente en el captulo.
HashSet, TreeSet y Linkedl-lashSet son tipos de conjuntos. La salida muestra que los objetos Set slo pemliten almace-
nar una copia de cada elemento (no se puede introducir dos veces el mismo elemento), pero tambin muestra que las dife
rentes implementaciones de Set almacenan los elementos de manera distinta. HashSet almacena los elementos utilizando
una tcnica bastante compleja que ana li zaremos en el Captulo 17, Anlisis detallado de los cOlllenedores, lo nico que nece
sitamos saber en este momento es que dicha tcnica representa la fonlla ms rpida de extraer elementos y, como resultado,
el orden de almacenamiento puede parecer bastante extrao (a menudo, lo nico que nos preocupa es si un cierto objeto
fom13 parte de un conjunto, y no el orden en que aparecen los objetos). Si el orden de almacenamiento fuera impar
tante, podemos utilizar un objeto TreeSet , que mantiene los objetos en orden de comparacin ascendente, o un objeto
LinkedHashSet, que mantiene los objetos en el orden en que fueron "'adidos.
Un mapa (tambin denominado matriz asociativa) pennite buscar un objeto utilizando una clave, como si fuera una base de
datos simple. El objeto asociado se denomina va/Dr. Si tenemos un mapa que asocia los estados ameri canos con sus capita-
les y queremos saber la capital de Ohio, podemos buscarla utilizando "Ohio" como clave, de fonna muy similar al proceso
de acceso a una matriz uti li zando un ndice. Debido a este comportamiento, un objeto mapa slo admite una copia de cada
clave (no se puede introducir dos veces la misma clave).
Map.put(key, val"e) aade un valor (el elemento deseado) y lo asocia con una clave (aquello con lo que buscaremos el
elemento). Map.get(key) devuelve el valor asociado con una clave. En el ejemplo anterior slo se aaden parejas de clave-
valor, sin real izar ninguna bsqueda. Ilustraremos el proceso de bsqueda ms adelante.
Observe que no es necesario especificar (ni preocuparse por ello) el tamao del mapa, porque ste cambia de tamao auto-
mticamente. Asimismo, los mapas saben cmo imprimirse, mostrando la asociacin existente entre claves y valores. En el
orden en que se malllienen las claves y va lores dentro de un objeto Map no es el orden de insercin, porque la implemen
lacin HashMap utiliza un algoritmo muy rpido que se encarga de controlar dicho orden.
En el ejemplo se utilizan las tres versiones bsicas de Map: HashMap, TreeMap y LinkedHashMap. Al igual que
HashSet, HashMap proporciona la tcnica de bsqueda ms rpida, no almacenando los elementos en ningn orden apa-
rente. Un objeto Treel\1ap mantiene las claves en un orden de comparacin ascendente, mientras que LinkedHashMap tiene
las claves en orden de insercin sin dejar, por ello, de ser igual de rpido que HashMap a la hora de realizar bsquedas.
Ejercicio 4:
List
(3) Cree una clase generadora que devuelva nombres de personajes (como objetos String) de su pelicula
favorita cada vez que invoque next( ), y que vuelva al principio de la lista de personajes una vez que haya
acabado con todos los nombres. Uti li ce este generador para rellenar una matriz, un ArrayList, un
LinkedList, un HashSet, un LinkedHashSet y un TreeSet, y luego imprima cada contenedor.
Las li stas garantizan que los elementos se mantengan en una secuenci a concreta. La interfaz List aade varios mtodos a
Collection que penniten la insercin y la eliminacin de elementos en mitad de una lista.
11 Almacenamiento de objetos 249
Existen dos tipos de objetos Lis!:
El objeto bsico ArrayList, que es el que mejor permite acceder a los elementos de fanna aleatoria, pero que
resulta ms Icnto a la hora de insertar y eliminar elementos en mitad de una lista.
El objeto LinkedList, que proporciona un acceso secuencial ptimo. siendo las inserciones y borrados en mitad
de una lista enormemente rpidos. LinkedList resulta relati vamente lento para los accesos aleatorios. pero tiene
muchas ms func ionalidades que Ar r ayList.
El siguiente ejemplo se adelanta un poco dentro de la estructura del libro, utilizando UDa biblioteca del Captulo 14,
Informacin de tipos para importar t)' peinfo.pets. Se trata de una biblioteca que contiene una jerarqua de clases Pet (mas-
cota), junto algunas herramientas para generar aleatoriamente objetos Pet. No es necesario entender todos los detalles en
este momento, si no que basta con saber que existe: (1) una clase Pet y varios subtipos de Pet y (2) que el mtodo
Pets.arrayList() esttico devuelve un mtodo ArrayList lleno de objetos Pet aleatoriamente seleccionados:
11: hOlding/ListFeatures.java
import typeinfo.pets.*
import java.util.*;
import static net.mindview.util.Print.*
public class ListFeatures {
public static void main(String[] args)
Random rand = new Random(47)
List<Pet> pets = Pets.arrayList{7);
print ("1 : " + pets)
Hamster h = new Hamster{)
pets.add(hl II Cambio de tamao automtico
print ("2 : " + pets) i
print("3: " + pets.contains(h;
pets.remove(h) II Eliminar objeto a objeto
Pet p = pets.get(2);
print ("4 : "+ P + " " + pets. indexOf (p) )
Pet cymric = new Cymric()
print ( "5: !l + pets. indexOf (cymric
print ("6 : " + pets. remove (cymric i
II Debe ser el objeto exacto:
print (" 7: "
print (118 :
pets.add(3,
+ pets.remove(p i
+ petsl
new Mouse() II Insertar
print (" 9: " + pets)
List<Pet> sub = pets.subList{l, 4);
print ("subList: " + sub);
print ( " 10: " + pets. containsAll (sub) ) ;
en un determinado ndice
Collections . sort (sub) ; II Ordenacin de la coleccin
print ("sorted subList: " + sub) i
II El orden no es importante en containsAll():
print("ll: " + pets.containsAll(sub;
Collections.shuffle(sub, rand) i II Mezclar los elementos
print ("shuffled subList: " + sub)
print("12: " + pets.containsAll(sub);
List<Pet> copy = new ArrayList<Pet> (pets) ;
sub = Arrays.asList{pets . get(1), pets.get(4;
print("sub: " + sub);
copy.retainAll(sub) ;
print("13: " .. copy) i
copy = new ArrayList<Pet> (pets) i II Obtener una nueva copia
copy.remove(2); II Eliminar segn un ndice
print("14: " + copy) i
copy.removeAll(sub); II Slo elimina objetos exactos
print (" 15: " .. copy);
250 Piensa en Java
copy.set(l, new MauSe()); JI Sustituir un elemento
print("16: " + copy);
copy.addAll(2, sub), // Insertar una lista en el medio
print("17: " + copy);
print ("18: " + pets. isEmpty () ) ;
pets . clear() i /1 Eliminar todos los elementos
print("19: " + pets);
print("20: " + pets.isEmpty())
pets.addAll(Pets.arrayList{4)) i
print(I'21: + pets);
Object[] o = pets.toArray();
print("22: " + 0[3]);
Pet[] pa pets . toArray(new Pet[O]);
print("23, " + pa[3] .id());
/ * OUtput:
1: [Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug)
2: (Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Hamster]
3, true
4 , Cymric
5, -1
6, false
7 , true
B, [Rat,
9, [Rat,
subList:
10: true
2
Manx, Mutt, Pug, Cymric, Pug]
Manx, Mutt, Mouse, Pug, Cymric, Pug]
[Manx, Mutt, Mouse)
sorted subList: [Manx, Mouse, Mutt]
11: true
shuffled subList: [Mouse, Manx, Mutt1
12: true
sub: (Mouse, Pug]
13, [Mouse, Pug]
14, [Rat,
15, [Rat,
16, [Rat,
17, [Rat,
lB, false
19, []
20, true
Mouse, Mutt, Pug, Cymric, Pug1
Mutt, Cymric, Pug)
Mouse, Cymric, Pug}
Mouse, Mouse, Pug, Cymric, PugJ
21, [Manx, Cymric, Rat, EgyptianMau]
22: EgyptianMau
23, 14
* ///,-
Hemos numerado las lineas de impresin para poder establ ecer la relacin de la salida con el cdigo fuente. La primera lnea
de salida muestra la li sta original de objetos Peto A diferencia de las matrices. un objeto List pennite aadir o eliminar ele-
mentos despus de haberlo creado y el objeto cambia automt icamente de tamao. sa es su caracterstica fundamental: se
trata de una secuencia modificable. En la linea de salida 2 podemos ver el resultado de arladir un objeto Hamster. El obje-
to se ha aadido al final de la lista.
Podemos averi guar si un objeto se encuentra dentro de la li sta ut ilizando el mtodo contains(). Si queremos eliminar un
objeto. podemos pasar la referencia a dicho objeto al mtodo remove( j . Asimismo, si di sponemos de una referencia a
un objeto, podemos ver en qu nmero de indice est al macenado ese objeto dentro de la lista utili zando indexOf( j , como
puede verse en la linea de salida 4.
A la hora de determinar si un elemento fonlla parte de una li sta, a la hora de descubrir el ndice de un elemento y a la hora
de eliminar un elemento de una lista a partir de su referencia, se utili za el mtodo equals() (que forma parte de la clase raz
Objectj. Cada objeto Pet se define como un objeto nico, por lo que, aunque existan dos objetos Cymric en la li sta, si crea-
mos un nuevo objeto Cymri c y lo pasamos a indexOf( j, el resultado ser -1 (indicando que no se ha encontrando el obje-
11 Almacenamiento de objetos 251
10): asimismo. si tratamos de eliminar el ObjclO con remove( ), el va lor devuelto ser false. Para otras clases, el mtodo
equals( ) puede estar definido de forma diferente; dos objetos Stri ng. por ejemplo, sern iguales si los contenidos de las
cadenas de caracteres son idnti cos. As que, para evi tarnos sorpresas, es importante ser consc iente de que el comportami en-
to de un objeto List vara dependiendo del comportamient o del mtodo equals().
En las lneas de sa lida 7 y 8, podemos ver que se puede eliminar perfectamente un objeto que se corresponda exactamente
con otro objeto de la lista.
Tambin resulta posibl e insertar un elemento en mitad de la li sta, C0l110 puede verse en la lnea de salida 9 y en el cdigo
que la precede. pero esta operacin nos pemlite resaltar un potencial problema de rendimi ento: para un objeto LinkedList,
la insercin y eliminacin en mitad de una lista son operaciones muy poco costosas (salvo por, en este caso, el propio acce-
so aleatorio en mitad de la lista), mientras que para un objeto ArrayList se trata de una operacin bastante costosa. Quiere
esto decir que nunca deberamos insertar elementos en mitad de una lista ArrayList , y que por el contrario. deberamos
emplear un objeto LinkedList en caso de tener que llevar a cabo esa operacin? De ninguna manera: simplemente signi fi-
ca que debemos tener en cuenta el potencial problema, y que si comenzamos a hacer numerosas inserciones en mitad de un
objeto ArrayList y nuestro programa comien=a a ralenti:orse, podernos sospechar que el posible culpable es la implemen-
tacin de la lista concreta que hemos elegido (la mejor fomla de descubrir uno de estos cuellos de botella, como podr ver
en el suplemento http://MindVielt:net/ Books/BetterJO\'lI, consiste en utili zar un perfilador). El de la optimi zacin es un pro-
blema bastante complicado, y lo mejor es no preocuparse por l hasta que veamos que es absolutamente necesario (aunque
comprender los posibles probl emas siempre resulta til).
El mtodo subList() pem1te crear fcilmente una sublista a partir de otra li sta de mayor tamao, y esto produce de fonna
natural un resultado true cuando se pasa la subli sta a containsAIl( ) para ver si los elementos se encuentran en esa li sta de
mayor lamal10. Tambin merece la pena recalcar que el orden no es importante: puede ver en las lneas de salida 11 y 12
que al invocar los mtodos CoUections.sort() y Collections,shuftle() (que ordenan y aleatorizan, respecti vamente, los ele-
mentos) con la subli sta sub, el resultado de containsAII() no se ve afectado. subList() genera una li sta respaldada por la
li sta origi nal. Por tanto, los cambios efectuados en la lista devue lta se vern reflejados en la li sta origi nal , y viceversa.
El mtodo retainAII() es, en la prctica, una operaci n de " interseccin de conjuntos", que en este caso conserva todos los
elementos de copy que se encuentren tambi n en sub. De nuevo, el comportamiento resultante depender del mtodo
equals( ).
La lnea de salida 14 muestra el resultado de eliminar un elemento utili zando su nmero ndice, lo cual resulta bastante ms
directo que elimi narl o mediante la referencia al objelO, ya que no es necesario preocuparse acerca del comportamiento de
equals( ) cuando se utilizan ndices.
El mtodo removeAII() tambin opera de manera distinta dependiendo del mtodo equals(). Como su propio nombre
sugiere. se encarga de elimi nar de la Lista todos los objetos que estn en el argumento de tipo List.
El nombre del mtodo set( ) no resulta muy adecuado, debido a la posible confusin con la clase Set, un mejor nombre
habra sido "replace" (sustituir) porque este mtodo se encarga de sustituir el elemento situado en el ndice indicado (el pri-
mer argumento) con el segundo argumento.
La lnea de salida 17 muestra que, para las listas. existe un mtodo addAII() sobrecargado que nos permite insertar la nueva
lista en mitad de la li sta ori gi nal , en lugar de limitamos a aadirla al final con el mtodo addAII() incluido en Collection.
Las lineas de salida 18-20 muestran el efecto de los mtodos isEmpty( ) y eloar( ).
Las lneas de salida 22 y 23 muestran cmo puede convertirse cualqui er objeto Collection en una matri z utili zando
tOArray( ). Se trata de un mtodo sobrecargado, la versin sin argumentos devuelve una matriz de Object. pero si se pasa
una matriz del tipo de destino a la versin sobrecargada, se generar una matri z del tipo especificado (suponi endo que los
mecani smos de comprobacin de tipos no detecten ningn error). Si la matriz utilizada como argumento resulta demasiado
pequea para al macenar todos los objetos de la lista List (como sucede en este ejemplo), toArray() crear una nueva matriz
del tamaI10 apropiado. Los objetos Pet ti enen un mtodo id(), pudiendo ver en el ejemplo cmo se invoca di cho mtodo
para uno de los objetos de la matriz resultante.
Ejercicio 5;
Ejercicio 6;
Modifique ListFeatures.java para luili zar enteros (recuerde la caracterstica de o/iloboxing) en lugar de
objetos Pet , y explique las diferencias que haya en los resultados.
(2) Modifique ListFeatures,java para utili zar cadenas de caracteres en lugar de objetos Pet , y explique
las diferencias que haya en los resultados.
252 Piensa en Java
Ejercicio 7: (3) Cree una clase y construya luego una matriz inicializada de objetos de dicha clase. Rell ene una liSta a
partir de la matriz. Cree un subconj unto de la lista uti lizando subList(), y luego elimine dicho Subconjun.
to de la li sta.
Iterator
En cualquier contenedor. tenemos que tener una forma de insertar elementos y de volver a extraerlos. Despus de todo, esa
es la funcin princ ipal de un contenedor: almacenar cosas. En una lista, add( ) es una de las fo rmas de insertar elementos y
gct( ) es una de las fonnas de extraerlos.
Si queremos razonar a UD nivel ms alto, nos encontramos con un problema: necesitamos desarrollar el programa con el tipo
exacto de contenedor para poder utilizarlo. Esto puede parecer una desventaja a primera vista. pero qu sucede si escribi.
mas cdigo para una lista y posterionnente descubrimos que sera conveniente apli car ese mi smo cdigo a un conjunto?
Suponga que quisiramos escribir desde el principio, un fragmento de cdigo de propsi to general, que no supiera con que
tipo de contenedor est trabajando, para poderlo ut il izar con diferentes tipos de contenedores sin reescribir dicho cdigo.
Cmo podramos hacer esto?
Podemos util izar el concepto de Iterador (otro patrn de di seo) para conseguir este grado de abstraccin. Un iterador es
un objeto cuyo trabajo consiste en desplazarse a travs de una secuencia y seleccionar cada UIlO de los objetos que la como
ponen, sin que el programa cliente tenga que preocuparse acerca de la estmctura subyacente a dicha secuencia. Adems, un
iterador es lo que usualmente se denomina un objeto ligero: un objeto que resulta barato crear. Por esa razn, a menudo nos
encont ramos con rest ricciones aparentemente extraas que afectan a los iterado res; por ejemplo, un objeto Iterator de Java
slo se puede desplazar en una direccin. No son muchas las cosas que podemos hacer con un objeto Iterator salvo:
1. Pedi r a una coleccin que nos devuelva un iterador utili zando un mtodo iterator(). Dicho iterador estar pre-
parado para devolver el primer elemento de la secuencia.
2. Obtener el siguiente objeto de la secuencia mediante nexl().
3. Ver si hay ms objetos en la secuencia utilizando el mtodo hasNexl().
4. Eliminar el ltimo elemento devuelto por el ilerador mediante remove( ).
Para ver cmo funciona, podemos volver a utilizar las herramientas de la clase Pet que hemos tomado prestadas del Capitulo
14, Informacin de tipos.
11 : holding/ Simplelteration.java
import typeinfo.pets.*;
import java.util.*;
public class Simplelteration
public static void main{String(] args )
List<Pet> pets = Pets.arrayList (12 ) i
Iterator<Pet> it = pets.iterator () ;
while (i t . hasNext () I (
Pet p = it.next()i
System.out.print(p.id () +
System.out.println() ;
" .11
+ p + " ti ) i
1I Un enfoque ms simple, siempre que sea posible:
for {Pet p : pets)
System. out . print (p. id ( l + It: ti + P + "l ;
System.out.println() ;
I1 Un iterador tambin permite eliminar elementos:
it = pets.iterator {);
for(int i = Di i < 6; i++l {
it.next() ;
it.remove() ;
System.out .println{pets) i
11 Almacenamiento de objetos 253
/ * Output:
O:Rat l:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 8:Cymric 9:Rat lO:EgyptianMau
11:Hamster
O:Rat l:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 8:Cymric 9:Rat lO:EgyptianMau
11 :Hamster
[Pug, Manx, Cymric, Rat, EgyptianMau, Hamster]
* // /,-
Con un objeto Iterator, no necesitamos preocuparnos acerca del nmero de elementos que haya en el contenedor. Los mto-
dos hasNoxt( ) y next() se encargan de dicha tarea por nosotros.
Si simplemente nos estamos desplazando hacia adelante por la li sta y no pretendemos modificar el propio objeto List, la
sintaxis/oreach resulta ms sucinta.
Un teTador pennite tambin eliminar el ltimo elemento generado por next( ), lo que quiere decir que es necesario invocar
a next( ) antes de llamar a remove(
Esta idea de tomar un contenedor de objetos y recorrerlo para realizar una cierta operacin con cada uno resulta muy poten-
te y haremos un extenso uso de ella a lo largo de todo el libro.
Ahora consideremos la creacin de un metodo display() que sea neutral con respecto al contenedor utilizado:
11 : holding/CrossContainerlteration.java
import typeinfo.pets.*
import java . util .*
public class CrossContainerlteration
public static void display(Iterator<Pet> it) {
whilelit.hasNext()) (
Pet p = it.next()
System.out . print (p.id() +
System.out.println () i
". " + p + 11 " ) ;
public static void main (String[] args ) {
ArrayList<Pet> pets = Pets.arrayList ( B) ;
LinkedList<Pet> petsLL = new LinkedList<Pet> (pets l i
HashSet<Pet> petsHS = new HashSet<Pet> (pets ) ;
TreeSet<Pet> petsTS = new TreeSet<Pet>(pets ) ;
display(pets.iterator ( ;
display(petsLL.iterator( i
display (petsHS.iterator( i
display(petsTS . iterator ( i
1* Output:
O:Rat l:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
O:Rat l:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
4: Pug 6:Pug 3:Mutt l:Manx 5:Cymri c 7:Manx 2:Cymric O:Rat
5 :Cymric 2:Cymric 7:Manx l:Manx 3:Mutt 6:Pug 4:Pug O:Rat
* /// ,-
Observe que display() no contiene ninguna infonnacin acerca del tipo de secuencia que est recorriendo. lo cual nos mues-
tra la verdadera potencia del objeto 1 tcrator: la capacidad de separar la operacin de recorrer una secuencia de la estructu-
ra subyacente de recorrer dicha secuencia. Por esta razn, decimos en ocasiones que los teradores unifican el acceso a los
contenedores.
4 remo\C() es un mtodo "opcional" (existen otros mtodos tambin opcionales), lo que significa que no todas las implementaciones de It eralor deben
implementarlo. Este tema se trata en el Capitulo 17, detallado de los cOIl/elledo/'es. Los contenedores de la biblioteca estndar de Java si imple-
mentan cl mtodo rcmove{ l. por lo que no es necesario preocuparse de este lema hasta que lleguemos a este capitulo.
254 Piensa en Java
Ejercicio 8: (1) Modifique el Ejercicio l para que utilice un iterador para recorrer la lista mientras se invoca hop().
Ejercicio 9: (4) Modifique innercl asses/Sequcnce.j ava para que Sequence funcione con un objeto !terator en lugar
de un objeto Sel ect or.
Ejercicio 10: (2) Modifique el Ejercicio 9 del Capitulo 8, PolimO/jismo para utili zar un objeto Ar r ayList para almace_
nar los objetos Rodent y un iterador para recorrer la secuencia de objetos Rodent.
Ejercicio 11: (2) Escriba un mtodo que uti lice un iterador para recorrer una coleccin e imprima el resultado de
toString() para cada objeto del contenedor. Rellene lodos los diferentes tipos de colecciones con una serie
de objetos y aplique el mtodo que haya di seliado a cada contenedor.
Listlterator
Li stIterat or es un subtipo ms potente de Iterator que slo se genera mediante las clases List. Mientras que Iterator slo
se puede desplazar hacia adelante, Listlterator es bidireccional. Tambin puede generar los ndices de los elementos
siguiente y anterior, en relacin al lugar de la lista hacia el que el terador est apuntando, permite sustituir el ltimo ele
mento listado uti li zando el mtodo set( ). Podemos generar un iterador Listlterator que apunte al principio de la lista iuvo
cando listIterator(), y tambin podemos crear un iterador Listlterator que comience apuntando a un ndice n de la lista
invocando Iistlterator(n). He aqu un ejemplo que ilustra estas capacidades:
// : holding/Listlteration.java
import typeinfo.pets. *
import java.util. * ;
public class Listlteration
public static void main (String [] args) {
List<Pet> pets = Pets.arrayList(S)
Listlterator<Pet > it = pets.listlterator{)
while (it .hasNext ())
System.out.print(it .next() + ", " + it.nextlndex{) +
" , " + it.previouslndex( ) + " ti )
System.out.println() ;
1/ Hacia atrs:
while(it.hasPrevious())
System.out.print(it.previous() .id() + " " )
System.out.println() i
System.out.println(pets) ;
it = pets.listlterator(3)
while (it . hasNext () I {
it . next () ;
it.set(Pets . randomPet(
System.out.println(pets)
1* Output:
Rat, 1, O Manx, 2, 1; Cymric, 3, 2 Mutt, 4, 3 Pug, 5, 4
Cymric, 6, 5; Pug, 7, 6 Manx, S, 7;
76543210
[Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug, Manx)
[Rat, Manx, Cymr ic, Cymric, Rat, EgyptianMau, Hamster, EgyptianMau]
El mtodo Pets.randomPet() se utili za para sustimir todos los objetos Pet de la lista desde la posicin 3 en adelante.
Ejercicio 12: (3) Cree y rellene un objeto Li st<Integer>. Cree un segundo objeto Li st<Integer> del mi smo tamao que
el primero y utilice sendos objetos Listlterator para leer los elementos de la primera li sta e insertarlos en
la segunda en orden inverso (pruebe a explorar varias fonnas distintas de resolver este problema).
11 Almacenamienlo de objetos 255
LinkedList
LinkedList tambin implementa la interfaz List bsica, como ArrayList , pero realiza ciertas operaciones de famla ms efi-
ciente que Ar rayList (la insercin y la eliminacin en la mitad de la li sta). A la inversa, resulta menos eficiente para las
operaciones de acceso aleatorio.
LinkcdList tambin incluye mtodos que pemliten lIsar este tipo de objetos como una pila, como una cola o como una cola
bidireccional.
Algunos de estos mtodos son alias o li geramente variantes de los otros, con el fin de disponer de nombres que resulten ms
familiares dentro del contexto de un uso especfico (en particular en el caso de los objetos Queue, que se lI san para imple-
mentar colas). Por ejemplo, getFirst() y element() son idnticos: devuelven la cabecera (primer elememo) de la lista sin
eliminarlo Y generan la excepcin NoSuchElementException si la lista est vaCa. peek() es una variante de estos mto-
dos que devuelve nuJl si la lista est vaca.
removeFi rst() y remove() tambin son idnticos: eliminan y devuel ven la cabecera de la li sta, generando la excepcin
NoSuchElement Exccption para una lista vaca; poll() es una variante que devuelve nuJl si la lista est vaca.
addFirst() inserta un elemento al principio de la lista.
offer() es el mismo mtodo que add() y addLast(). Todos ellos aaden un elemento al final de la li sta.
removeLast() elimina y devuelve el ltimo elemento de la li sta.
He aqu un ejemplo que muestra las similitudes y diferencias bsicas entre todas estas funcionalidades. Este ejemplo no repi-
te aquellos comportamientos que ya han sido ilustrados en ListFcatures.java:
1/: holding/LinkedListFeatures.java
import typeinfo.pets.*
import java.util.*
import static net.mindview.util.Print.*
public class LinkedListFeatures (
public static void main(String[] args) {
LinkedList<Pet> pets =
new LinkedList<Pet>(Pets.arrayList(S
print (pets)
II Idnticos :
print ("pets. getFirst (): " + pets. getFirst () )
print ( "pets. element () : " + pets. element () )
II Slo difiere en el comportamiento con las listas vacas:
print(Upets.peek(}; 11 + pets.peek();
II Idnticos; elimina y devuelve el primer elemento:
print ( "pet s. remove () : + pets.remove{
print ("pets. removeFirst (): " + pets. removeFirst () ) ;
11 Slo difiere en el comportamiento con las listas vacas:
print("pets.poll(), " + pets.poll());
print (pets)
pets.addFirst(new Rat(;
print("After addFirst(): " + pets);
pets.offer(Pets.randomPet( i
print("After offer(): ti + pets);
pets.add(Pets.randomPet(
print (" After add () : 11 + pets)
pets.addLast(new Hamster(}} i
print("After addLast(}: It + petsl;
print ("pets. removeLast () : 11 + pets. removeLast () ) ;
1* Output:
[Rat, Manx, Cymric, Mutt, Pug)
pets.getFirst(): Rat
256 Piensa en Java
pets.element(): Rat
pets.peek(): Rat
pets.remove(): Rat
pets.removeFirst(): Manx
pets.poll(): Cymric
[Mutt, PugJ
After addFirst (): (Rat, Mutt, PugJ
After offer(): [Rat, Mutt, Pug, CymricJ
After add(): [Rat, Mutt, Pug, Cymric, Pug]
After addLast(): [Rat, Mutt, Pug, Cymric, Pug, HamsterJ
pets.removeLast(): Hamster
* ///>
El resultado de Pets . rr.yList( ) se entrega al constructor de LinkedList con el fID de rellenar la li sta enlazada. Si analiza
la interfaz Queue, podr ver los mtodos element(), offer(), peek( l , poll() y remove() que han sido aadidos a
LinkedList para poder di sponer de la implementacin de una cola. Ms adelante en el captulo se incluyen ejemplos Com-
pletos del manejo de colas.
Ejercicio 13: (3) En el ejempl o innerclasscs/GreenhouseController.java, la clase Controllcr utiliza un objeto
ArrayList. Cambie el cdigo para util izar en su lugar un objeto LinkedList y emplee un iterador para
recorrer el conjunto de sucesos.
Ejercicio 14: (3) Cree un objeto vaco LinkedList<lnteger>. Ut ili zando un iterador ListIterator. aada valores ente-
ros a la lista insel1ndolos siempre en mitad de la misma.
Stack
Una pila (sTack) se denomina en ocasiones "contenedor de tipo LLFO" (Iasl-in,first-olll, el ltimo en entrar es el primero en
salir). El ltimo elemento que pongamos en la "parte superior" de la pila ser el primero que tengamos que sacar de la
misma, como si se tratara de una pila de platos en una cafetera.
LinkedList tiene mtodos que implementan de fonna directa la funcionalidad de pila, por lo que tambin podramos usar
una lista enlazada LinkcdList en lugar de definir una clase con las caracterst icas de una pi la. Sin embargo, definir una clase
a propsito permite en ocasiones clarificar las cosas:
11: net/mindview/util/Stack . java
II Definicin de una pila a partir de una lista enlazada.
package net.mindview.util;
import java.util . LinkedList;
public class Stack<T> {
private LinkedList<T> storage = new LinkedList<T>();
public void push (T v) { storage. addFirst (v); }
public T peek() { return storage.getFirst(}; }
public T pop () { return storage. removeFirst () ;
public boolean empty () { return storage. isEmpty () i }
public String toString() { return storage.toString();
///,-
Esto nos pennite introducir el ejemplo ms simple posible de definicin de una clase mediante genricos. La <T> que sigue
al nombre de la clase le dice al compi lador que se trata de un tipo paramelrizado y que el parmetro de ti po (que ser u ~
tituido por un tipo real cuando se utili ce la clase) es T. Bsicamente, lo que estamos diciendo es: "Estamos definiendo una
pila Stack que almacena objetos de tipo TOO. La pila se implementa utili zando un objeto LinkedList, y tambin se define
dicho objeto LinkedList para que almacene el tipo T. Observe que push() (el mtodo que introduce objetos en la pila) toma
un objeto de tipo T, mientras que peek() y pop() devuel ven el objeto de tipo T. El mtodo peek( ) devuelve el elemento
superior de la pila sin eliminarlo, mientras que pop() extrae y devuelve dicho elemento superior.
Si lo nico que queremos es disponer del comportamiento de pila, el mecani smo de herencia resulta inapropiado, porque
generara una clase que incluira el resto de los mtodos de LinkedList (en el Captulo 17, Anlisis derallado de los conte-
nedores, podr ver que los diseadores de Java 1.0 cometi eron este error al crear java.utiI.Stack).
He aqu una sencilla demostracin de esta nueva clase
1/: holding/StackTest.java
import net.mindview.util.*
public class StackTest {
public static void main(String[] args) {
Stack<String> stack = new Stack<String>() i
for(String s "My dog has fleas".split(" lO
stack.push(s) ;
while(!stack . empty() )
System.out.print(stack.pop() + " ") i
/ * Output :
fleas has dog My
, /// ,-
11 Almacenamiento de objetos 257
Si quiere utilizar esta clase Stack en su propio cdigo, tendr que especificar completamente el paquete (o cambiar el nOI11-
bre de la clase) cuando cree una pila: en caso contrario, probablemente entre en colisin con la clase Stack del paquete
java.lItil. Por ejemplo, si importamos java.util.* en el ejemplo anterior. deberemos usar los nombres de los paquetes para
evit ar las coli siones.
11: holding/StackCollision . java
import net.mindview. util.*
public class StackCollision
public static void main (St ring [] args) (
net . mindview.util.Stack<String> stack =
new net.mindview.util.Stack<String>() i
for(String s : "My dog has fleas".split("
stack.push(s) i
while(!stack.empty() )
System.out . print(stack . pop() + " lO )
System. out . println() i
java .util.Stack<String> stack2
new java.util.Stack<String>() i
for(String s "My dog has fleas".split(II ti
stack2.push(s)
while(!stack2.empty() )
System.out.print(stack2.pop() + " lO) i
1* Output:
fleas has dog My
f leas has dog My
' / / / >
Las dos clases Stack tienen la mi sma interfaz. pero no existe ninguna interfaz comn Stack en java.util, probabl emente
porque la clase original java.utiI.Stack. que estaba di seada de una fonna inadecuada, ya tena ocupado el nombre. Aunque
java.utilStack existe, LinkedList permite obtener una clase Stack mejor, por lo que resulta preferible la tcnica basada en
net.mindview.utiI.Stack.
Tambin podemos controlar la seleccin de la implementacin Stack Hpreferida" utili zando una instmccin de importacin
explcita:
import net.mindview.util.Stack
Ahora cualquier referenci a a Stack har que se selecc ione la versin de net.mindview.util , mientras que para seleccionar
java.util.Stack es necesario emplear una cualificacin completa.
Ejercicio 15: (4) Las pilas se utilizan a menudo para evaluar expresiones en lenguajes de programacin. Utili zando
net.mindview.utiI.Stack, evale la siguiente expresin, donde '+' significa "introducir la letra siguiente
en la pila" mientras que '-' significa "extraer la parte superior de la fila e imprimirlo":
"+U+n+c---+e+r+t---+a-+i-+n+t+y---+ -+r+u--+I+e+s---"
258 Piensa en Java
Set
Los objetos de tipo Set (conjuntos) no penniten almacenar ms de una instancia de cada objeto. Si tratamos de aadir ms
de una instancia de un mismo objeto, Set impide la duplicacin. El uso ms comn de Set cons iste en comprobar la p n ~
llencia, para poder preguntar de una manera sencilla si un detenninado objeto se encuentra dentro de un conjunto. Debido
a esto, la operacin ms importante de un conjunto suele ser la de bsqueda, as que resulta habinJaI seleccionar la i m p l ~
mentacin HashSet, que est optimizada para realizar bsquedas rpidamente.
Set tiene la misma interfaz que Collection, por lo que no existe ninguna funcionalidad adicional, a diferencia de los dos
tipos distintos de List. En lugar de ello, Set es exactamente un objeto ColleetioD, salvo porque tiene un comportamiento
distinto (ste es un ejemplo ideal del uso de los mecanismos de herencia y de polimorfismo: penniten expresar diferentes
comportamientos). Un objeto Set detemlina la pertenencia basndose en el "valor" de un objeto, lo cual constituye un tema
relativamente complejo del que hablaremos en el Captulo 17, Anlisis detallado de los conrenedores.
He aqu un ejemplo que utiliza un conjunto HashSet con objetos Integer:
11: holding/SetOflnteger.java
import java . util. *i
public class SetOflnteger
public static void main{String [] argsJ {
Random rand = new Random(47);
Set<Integer> intset = new HashSet<Integer>();
for(int i = Di i < 10000; i++)
intset.add(rand.nextlnt(30)) ;
System.out.println(intset ) ;
1* Out put:
[15, 8, 23, 16, 7, 22, 9, 21, 6, 1, 29, 14, 24, 4, 19, 26, 11, 18, 3, 12, 27, 17, 2, 13,
28, 20, 25, 10, 5, O]
,/ // ,-
En el ejemplo, se aaden diez mil nmeros aleatorios entre O y 29 al conjunto, por lo que cabe imaginar que cada valor ten-
dr muchos duplicados. A pesar de ello, podemos ver que slo aparece una instancia de cada valor en los resultados.
Observar tambin que la salida no tiene ningn orden especfico. Esto se debe a que HashSet utiliza el mecanismo de hash
para acelerar las operaciones; este mecanismo se analiza en detalle en el Captulo 17, Anlisis detallado de los contenedo-
res. El orden mantenido por un conjunto HashSet es diferente del que se mantiene en un TreeSet o en un LinkedHashSet,
ya que cada implementacin almacena los elementos de forma distinta. TreeSet mantiene los elementos ordenados en una
estructura de datos de tipo de rbol rojo-negro, mientras que HashSet utiliza una funcin de hasll. LinkedHashSet tambin
emplea una funcin hash para acelerar las bsquedas, pero parece mantener los elementos en orden de insercin utilizando
una li sta enlazada.
Si queremos que los resultados estn ordenados, una posible tcnica consiste en utilizar un conjunto TreeSet en lugar de
HasbSet:
11: holding/SortedSetOflnteger.java
import java.util. *;
public class SortedSetOflnteger
public static void main(String[] args) {
Random rand = new Random (47);
SortedSet<Integer> intset = new TreeSet<Integer>() i
for(int i = o; i < 10000; i++)
intset.add(rand.nextlnt(30)} ;
System.out.println(intset) ;
1* Output:
[O, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
, /// ,-
11 Almacenamiento de objetos 259
Una de las operaciones ms comunes que tendremos que realizar es comprobar la pertenencia al conjunto de un determina-
do miembro usando contains( ). pero hay otras operaciones que nos recuerdan a los diagramas Venn que ensean en el cole-
gio:
1/ : holdingfSetOperations.java
import java.util. * ;
import static net.mindview.util.Print.*
public class SetOperations {
public stacic void main(String(] args) {
Set<String> setl = new HashSec <String> () ;
Collections.addAll(setl,
ti A BCD E F G H 1 J K L". spl i t (" 11)) i
setl . add ( "M" ) i
print ("H: " + setl. contains (" H" ) ) ;
print ("N: It + setl. contains (UN") ) ;
Set<String> set2 = new HashSet<String>();
Collections.addAll(set2, "H 1 J K L".split {1I ")i
print{"set2 in setl: " + setl.containsAll {set2 ;
setl. remove ( "H" ) ;
print("setl: " + setl ) ;
print ( "set2 in setl: " + setl.containsAll(set2;
setl.removeAll{set2) ;
print(lIset2 removed fram setl: " + setl);
eallections.addAll(setl, "X y Z".split(" 11;
print("'X Y Z' added to setl: " + setl);
/ * Output :
H: true
N: false
set2 in setl: true
seU, ID, K, e, B, L, G, 1, M, A, F, J, El
set2 in setl: false
set2 removed from setl:
'X y Z' added to setl:
*1110-
[D, C, a, G, M, A, F, El
[Z, D, C, a, G, M, A, F, Y, X, El
Los nombres de los mtodos resultan bastante descriptivos, y existen unos cuantos mtodos adicionales que podr encon-
trar en el JDK.
Generar ulla lista de elementos diferentes puede resultar muy til en detenninadas ocasiones. Por ejemplo, suponga que qui-
siera enumerar todas las palabras contenidas en el archivo SetOperations.java anterior. Con la utilidad net.mindview.
Text File que presentaremos ms adelante en el libro, podremos abrir un archivo y almacenar su contenido en un objeto Set:
11: holding/UniqueWords.java
import java.util .*;
import net.mindview.util.*;
public class OniqueWords {
public static void main (String [] args) {
Set<String> words = new TreeSet<String>(
new TextFile ("SetOperations.java" , " \\w+" ;
System.out.println(wordsl i
1* Output:
(A, S, e, eollections, D, E, F, G, H, HashSet, I, J, K, L, M, N, Output, Print, Set,
SetOperations, String, X, Y, Z, add, addAII, added, args, class, contains, containsAll,
false, frem, holding, impert, in, java, main, mindview, net, new, print, public, remove,
removeAll , removed, set!, set2, split, static, to, true, util, voidl
* 111 ,-
260 Piensa en Java
TextFile hereda de List<String>. El constnlctor de TextFile abre el archivo y lo descompone en palabras de acuerdo Con
la expresin regular "\\ W+", que significa "una o ms letras" (las expresiones regulares se presentan en el Captul o !J.
Cadenas de caracteres). El resultado se entrega al constructor de TreeSet. que aii.ade el contenido del objelO Li st al conjun.
lo. Puesto que se lIata de un objeto TreeSet. el resultado est ordenado. En este caso, la reordenacin se realiza /exicogra .
. fi comente de modo que las letras maysculas y minsculas se encuentran en gnlpos separados. Si desea reali zar una
ordenacin alfabtica, puede pasar el comparador Slring,CASE_INSENSITIVE_ORDER (un comparador es un objeto
que establ ece un orden) al constructor TreeSet:
/1 : holding/ UniqueWordsAlphabetic . java
11 Generacin de un listado alfa btico.
import java . util .* ;
import net . mindview.util .* ;
public class UniqueWordsAlphabe tic
public static void main (String[) args )
Set<String> word s =
new Tr eeSet<String> (String.CASE_I NSENSITIVE_ORDER) ;
words . addAll (
new Text File ( ISetOperations . java" , n\\w+ ,, i
System. out . pr i n tln( words) i
1* Output :
(A, add, addAll, added, args, B, C, c l ass, Collections, contai ns, containsAll, D, E, F,
f alse, f rom, G, H, HashSe t, holding, I , i mport , i n , J , java, K, L, M, ma i n, mi n dview, N,
net, new, Output, Pr int, public, remove, removeAll, removed , Set, set1 , set2,
SetOperations, split, static, String , to, true , util, void, X, Y, Z)
* /// ,-
Los comparadores se anali zarn en el Captulo 16, Mafl;ces.
Ejercicio 16: (5) Cree un obj eto Sel con todas las vocales, Ut il izando el archivo UniqueWords,java, cuente y muestre
el nmero de vocales en cada palabra de entrada, y muestre tambin el nmero total de vocales en el archi
va de entrada.
Map
La posibilidad de establecer correspondencias entre unos objetos y otros puede ser enonnemente potente a la hora de resol
ver ciertos problemas de programacin. Por ejemplo, consideremos un programa que pemlite examinar la aleator iedad de
la clase Random. Idealmente. Random debera producir la distribucin perfectamente aleatoria de nmeros, pero para com
probar si esto es as debera generar numerosos nmeros aleatorios y llevar la cuenta de cules caen dentro de cada uno de
los rangos definidos. Un obj eto Map nos pennite resolver fc ilmente el probl ema; en este caso, la cl ase ser el nmero gene
rada por Random y el valor ser el nmero de veces que ese nmero ha aparecido:
1/ : holding/ Statistics . java
11 Ejemplo simple de HashMap .
import java.util . *;
public class Statistics
public static void main (String [) argsl {
Random rand = new Random (47);
Map<Integer,Integer> m =
new HashMap<Integer,Integer>( ) ;
for(int i = O; i < 1 0000; i ++) {
11 Generar un numero ent r e O y 20:
int r = rand.nextlnt(20);
Integer freq = m. get(r ) ;
m. put {r, f req == null ? 1
System. out . println (m) ;
freq + 1);
11 Almacenamiento de objetos 261
/ * Output:
{15=497, 4=481, 19=464, 8=468, 11=531, 16=533, 18=478, 3=508, 7=471, 12=521, 17=509,
2=489, 13=506, 9=549, 6=519, 1=502, 14=477, 10=513, 5=503, 0=481)
' 111 ,-
En main(), la caracter sti ca de oUloboxing convierte el valor int aleatoriamente generado en una referencia a Integer que
puede utilizarse con el mapa HashMap (no pueden utili zarse primiti vas en los contenedores). El mtodo get() devuelve
null si la clave no se encuentra ya en el contenedor (lo que quiere decir que es la primera vez que se ha encontrado ese
nmero concreto) . En caso contrario, el mtodo gct() devuelve el valor Intcger asociado con esa clave. el cual tendremos
que incrementar (de nuevo, la caracterstica de auroboxing simplifica la expresin, pero en la prctica se llevan a cabo las
necesarias conversiones hacia y desde Integer).
He aqu un ejemplo que nos pennite utilizar la descripcin de String para buscar objelOs Peto Tambin nos muestra cmo
podemos comprobar si un determinado objelO Map contiene una clave o un va lor utilizando los mtodos containsKey( ) y
containsValue( ):
11 , holding/PetMap.java
import typeinfo.pets.
import java.util.;
import static net.mindview.util.Print.*
public class PetMap {
public static void main(String[] args) {
Map<String,Pet> petMap = new HashMap<String,Pet>() i
petMap. put ("My Cat", new Cat ("Molly") ) i
petMap. put ( "My Dog", new 009 ("Ginger " ) ) ;
petMap.put(ltMy Hamster", new Hamster ( ltBosco" ;
print (petMap) i
Pet dog = petMap.get {"My 009");
print (dog)
print {petMap. containsKey ("My Dog" i
print(petMap.containsValue(dogJ) ;
/ * Output:
{My Cat=Cat Molly, My Hamster=Hamster Bosco, My Dog=Oog Ginger}
Dog Ginger
true
true
' 111 ,-
Los mapas, al igual que las matrices y las colecciones, pueden cxpandirse fcilmente para que sean multidimcnsional es:
basta con definir un obj cto Map cuyos va lores sean tambin mapas (y los valores de esos olros mapas pueden ser, a su vez,
otros contenedores o inc luso otros mapas). As, resulta bastante fcil combinar los contenedores para generar estmctliras de
datos muy potentes. Por ejemplo, suponga que queremos llevar una li sta de personas que tienen mltiples mascotas, en ese
caso. lo nico que necesitaremos es un objeto Map<Person, List<Pet:
11 , holding/ MapOfList.java
package holding;
import typeinfo.pets.*
import java.util. *
import static net.mindview. util.Print.
public class MapOfList {
public static Map<Person, List<? extends Pet
petPeople = new HashMap<Person, List<? extends Pet{);
static {
petPeople.put(new Person{"Dawn
n
),
Arrays. asList (new Cymric ("Molly") ,new Mutt ( "Spot" J ) )
petPeople.put(new Person("Kate"),
262 Piensa en Java
Arrays. asList {new Cat (" Shackleton") ,
new Cat (" Elsie May" ) , new Dog ("Margrett" ) ) ) ;
petpeople.put(new Person("Marilyn"),
Arrays.asList (
new Pug{"Louie aka Louis Snorkelstein Dupree"),
new Cat ( "Stanford aka Stinky el Negro" ),
new Cat("Pinkola");
petPeople.put (new Person(IILuke"),
Arrays.asList {new Rat ( "Fuzzy" ) , new Rat ( IFizzy" )
pet Peopl e . put (new Person ( 11 Isaac ") ,
Arrays.asList {new Rat("Freckly" )})
public static void main{String [] args ) {
print ( " People: " + petPeople. keySet () } i
print (" Pets: " + petPeople. values () ) i
for(Person person : petPeople.keySet(
print (person + " has: ") i
for ( Pet pet petPeople.get {person
print(li 11 + pet) i
/* Output:
People: [Person Luke, Person Marilyn, Person Isaac, Person Dawn, Person Kate]
Pets: [[Rat Fuzzy, Rat Fizzy], (Pug Louie aka Louis Snorkelstein Dupree, Cat Stanford aka
Stinky el Negro, Cat Pinkolal, [Rat Freckly], [Cymric MOlly, Mutt Spot] , [Cat Shackleton,
Cat Elsie May, 009 Margrett]]
Person Luke has:
Rat Fuzzy
Rat Fizzy
Person Marilyn has:
Pug Louie aka Louis Snorkelstein Dupree
Cat Stanford aka Stinky el Negro
Cat Pinkola
Person Isaac has:
Rat Freckly
Person Dawn has:
Cymric Molly
Mutt Spot
Person Kate has:
Cat Shackleton
Cat Elsie May
Dog Margrett
* ///,-
Un mapa puede devolver un objeto Set con sus claves, un objeto CoUection con sus valores o un objeto Set con las parejas
clave-valor que tiene almacenadas. El mtodo keySct() genera un conjunto de todas las claves de petPeople, que se utili-
za en la instruccin foreach para iterar a travs del mapa.
Ejercicio 17: (2) Tome la clase Gcrbil del Ejercicio I y cambie el ejemplo para incluirla en su lugar en un objeto Map,
asociando el nombre de cada objeto Gerbil (por ejemplo, "Fuzzy" o "Spot") como si fuera una cadena de
caracteres (la clave) de cada objeto Gerbil (el valor) que incluyamos en la tabl a. Genere un iterador para
el conjunto de claves keySet() y utilicelo para desplazarse a lo largo del mapa, buscando el objeto Gerbil
correspondi ente a cada clave, imprimiendo la clave e invocando el mtodo hop() del objeto Gerbil.
Ejercicio 18: (3) Rellene un mapa HashMap con parejas clave-valor. Imprima los resultados para mostrar la ordena-
cin segn el cdigo hash. Ordene las parejas, extraiga la clave e introduzca el resultado en un mapa
LinkedHashMap. Demuestre que se mantiene el orden de insercin.
Ejercicio 19: (2) Repita el ejercicio anterior con sendos conjuntos HashSet y LinkedHashSet.
Ejercicio 20: (3) Modifique el Ejercicio 16 para llevar la cuenta de cuntas veces ha aparecido cada vocal.
11 Almacenamiento de objetos 263
Ejercici o 21 : (3) Utilizando Map<String,lnteger >, siga el ejemplo de Uni queWords.java para crear un programa que
lleve la cuenta del nmero de apariciones de cada palabra en un archi vo. Ordene los resultados uti lizando
Collecti ons.sort() proporcionando como segundo argumento String.CASE_INSENSITI VE_ORDER
(para obtener una ordenacin alfabt ica), y muestre los resultados.
Ej ercici o 22: (5) Modifique el ejercicio anterior para que ut il ice una clase que contenga un campo de tipo Stri ng y un
campo contador para almacenar cada una de las di ferentes palabras, as como un conjunto Set de estos
objetos con el fin de mantener la li sta de palabras.
Ejercici o 23: (4) Part iendo de Statistics.j ava, cree un programa que ejecute la prueba repetidamente y compruebe si hay
algn nmero que tienda a aparecer ms que los otros en los resultados .
Ej ercici o 24: (2) Rellene un mapa LinkedHashMap con claves de tipo String y objetos del tipo que prefiera. Ahora
extraiga las parejas, ordnelas segn las claves y vuelva a insertarlas en el mapa.
Ej ercicio 25: (3) Cree un objeto Map<String,ArrayList<lntcger. Uti lice net.mindview.Text File para abri r un
archivo de texto y lea el archivo de palabra en palabra (utilice "\\W+" como segundo argumento del cons-
tructor Te.tFile). Cuente las palabras a medida que lee el archivo, y para cada palabra de un archivo, anote
en la matriz ArrayList<lnteger> el contador asociado con dicha palabra; esto es, en la prctica, la ubica-
cin dentro del archivo en la que encontr dicha palabra.
Ejercici o 26: (4) Tome el mapa resultante del ejercicio anterior y ordene de nuevo las palabras, tal como aparecan en
el archivo original.
Queue
Una cola (queue) es normalmente un contenedor de tipo FIFO (jirst-in,first-out, el primero en entrar es el primero en sa lir).
En otras palabras, lo que hacemos es insertar elementos por uno de los extremos y extraerlos por el otro, y el orden en que
insertemos los elementos coincidir con el orden en que estos sern extrados. Las colas se utilizan comnmente como un
mecanismo fiable para transferir objetos desde un rea de un programa a otro. Las colas son especialmente importantes en
la programacin concurrente, corno veremos en el Captulo 21, Concurrencia, porque permen transferir objetos con segu-
ridad de una a otra tarea.
LinkedList di spone de mtodos para soportar el comportamiento de una cola e implementa la interfaz Queue, por lo que
un objeto Li nkcd List puede utilizarse como implementacin de Queu . Generalizando un objeto LinkedList a Queue, este
ejemplo uti liza los mtodos especficos de gestin de colas de la interfaz Queue:
11: holding/QueueDemo .java
II Generalizacin de un objeto LinkedList a un objeto Queue .
import java.util. *
public class QueueDemo
public static void printQ (Queue queue) {
while(queue . peek() != null)
System.out.print(queue.remove() + 11 11);
System.out.println(}
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<Integer>();
Random rand = new Random(47);
for(int i :: O; i < 10; i++)
queue.offer(rand.nextlnt(i + ID});
printQ(queue) ;
Queue<Character> qc = new LinkedList <Character>();
for (char e : 11 Brontosaurus" . toCharArray () )
qc . offer(c) ;
printQ (qc) ;
1* Output:
264 Piensa en Java
8 1 1 1 5 14 3 1 O 1
B ron t o s a u r u s
* ///,-
offe .... () es uno de los mtodos especficos de Queue; este mtodo inserta un elemento al final de la cola, siempre que sea
posible. o bien devuehe el valor fa lse. Tanto peek() como element() devuelven la cabecera de la cola sin eliminarla, pero
peek() devuel ve null si la cola est vacia y element() genera NoSuch Element Exccption. Tanto poll() como remOVe()
eliminan y devuelven la cabecera de la cola, pero poll () devuelve null si la cola est vaca, mientras que removc() genera
NoSueh Element Excepli on.
La caracterstica de allloboxil1g convierte automticamente el resultado int de nextlnt () en el objeto Int eger requerido por
q ueue. y el valor ehar e en el objeto Char aeter requerido por qc. La interfaz Queue limita el acceso a los mtodos de
Li nkedLi st de modo que slo estn di sponibles los mtodos apropiados. con lo que estaremos menos tentados de utilizar
los mtodos de LinkedLi st (aqui, podramos proyectar de nuevo queue para obtener un objeto LinkedLi st, pero al menos
nos resultar bastante ms complicado uti lizar esos mtodos).
Observe que los mtodos especficos de Queue proporcionan una funcionalidad completa y autnoma. Es decir, podemos dis-
poner de una cola ut ilizable sin ninguno de los mtodos que se encuentran en Coll ection. que es de donde se ha heredado.
Ejercicio 27: (2) Escri ba una clase denominada Command que contenga un objeto String y que tenga un mtodo ope-
r ati on( ) que imprima la cadena de caracteres. Escriba una segunda clase con un mtodo que rellene un
objeto Queue con objetos Command y devuelva la cola rellena. Pase el objeto Queue relleno a un mto-
do de una tercera clase que consuma los objetos de la cola e invoque sus mtodos oper ation( ).
PriorityQueue
El mecanismo FIFO (Firsl-ill . firsl-out) describe la disciplina de gestin de colas ms comn. La disciplina de gestin de
colas es lo que decide. dado un grupo de elementos existentes en la cola. cul es el que va a continuacin. La di sciplina
HFO dice que el siguiente elemento es aquel que haya estado esperando durante ms tiempo.
Por el contrario. una cola COI1 prioridad implica que el elemento que va a continuacin ser aquel que tenga una necesidad
mayor (la prioridad ms alta). Por ejemplo, en un aeropuerto, puede que un cliente que est en medio de la cola pase a ser
atendido inmediatamente si su avin est a punto de salir. Si const ruimos un sistema de mensajera, algunos mensajes sern
ms importantes que otros y ser necesario tratar esos mensajes antes, independientemente de cundo hayan llegado. El con-
tenedor Pr iori tyQueue ha sido ailadido en Java SES para proporcionar una implementacin automtica de este tipo de com-
portamiento.
Cuando ofrecemos. como mtodo offer( ). un objeto a una cola de tipo Priorit)'Queue, dicho objeto se ordena dentro de la
cola.
5
El mecani smo de ordenacin predetcnninado utili za el orden lIatural de los objetos de la cola, pero podemos modi-
ficar dicho elemento proporcionando nuestro propio objeto Compar ator. La clase Pri ori tyQueue garantiza que cuando se
invoquen los mtodos peek(), poll ( ) o ,'emove(), el elemento que se obtenga ser aqul que tenga la prioridad ms alta.
Resulta trivial implementar una cola de tipo Priori tyQueue que funcione con tipos predefinidos como Illt eger. Str ing o
Cha r aeter. En el siguiente ejemplo, el primer conjunto de valores son los valores aleatorios del ejemplo anterior. con lo
cual podemos ver cmo se los extrae de manera diferente de la cola PriorityQueue:
11 : holding/PriorityQueueDemo.java
impore java.util.*;
public class PriorityQueueDemo
public static void main(String[] args)
priorityQueue
new
Random rand = new Random(47)
Esto depli!ndc. de hecho. de ta implemcntacin. Lo:>. algoritmos de colas con prioridad suelcn realizar la ordenacin durantc la insercin
una estnJctura de memoria quc se conoce con el nombre de cmulo). pcro tambin puedc perfectamente seleccionarse el elemento ms impol1ante C'n
momento de la extraccin. La eleccin del algoritmo podra tener su importancia si la prioridad de los objeto:>. puede modificarse mientras estos C:>.tn pe-
rando en la cola.
for ( fnt i = o; i <:: 10; i++ )
priorityQueue , offer {rand.nextlnt ( i + 10 ;
QueueDemo. printQ {priorityQueue} ;
List<Integer> ints = Arrays . asList (25, 22, 20 I
18, 14, 9, 3, 1, 1, 2, 3, 9, 14, 18, 21, 23, 25) ;
priorityQueue = new PriorityQueue<Integer> (ints ) ;
QueueDemo. printQ (priorityQueue ) i
priorityQueue = new PriorityQueue<Integer> (
ints. s i ze (), Collections. reverseOrder () ) i
priorityQueue.addAll(intsJ i
QueueDemo.printQ(priorityQueuel i
String fact = "EDUCATION SHOULD ESCHEW OBFUSCATION" i
ListcString> serings = Arrays.asList {fact . split("" )} ;
PriorityQueue<String> stringPQ =
new PriorityQueue<String> (strings) ;
QueueDemo .printQ(stringPQ) ;
stringPQ = new PriorityQueue<String> (
serings. si ze () , Collections . reverseOrder ( ) ) i
seringPQ. addAll ( serings ) ;
QueueDemo . prineQ(stringPQ) ;
Set<Character> charSet = new HashSet<Character>();
for (char e fact.toCharArray(
charSet.add ( c ) ; II Autoboxing
PriorityQueue<Character > characterPQ
new PriorityQueue< Character> (charSet) i
QueueDemo.printQ(characterPQ) i
1* Output :
O 1 1 1 1 1 3 5 8 14
1 1 2 3 3 9 9 14 14 18 18 20 21 22 23 25 25
25 25 23 22 21 20 18 18 14 14 9 9 3 3 2 1 1
11 Almacenamiento de objetos 265
A A B e e e D D E E E F H H 1 1 L N N o o o o s s S T T U U U W
w U U U T T S S S o o o o N N L 1 1 H H F E E E D o e e e B A A
A B e o E F H 1 L N o S T U W
, /// ,-
Como puede ver, se permiten los duplicados. los valores menores tienen la prioridad ms alta (en el caso de String.
los espacios tambin cuentan como va lores y tienen una prioridad superi or a la de las letras). Para ver cmo podemos
verificar la ordenac in proporcionando nuestro propio objeto comparador, la tercera ll amada al conS(nl clor de
PriorityQueue<lnteger> y la segunda llamada a PriortyQueue<String> utilizan el comparador de orden inverso genera-
do por Collectiol1s,revcrseOrder( ) (aadido en Java SE5).
La ltima seccin aiiadc un conjunto HashSet para el iminar los objetos Character duplicados, simpl emente con el fin de
hacer las cosas un poco ms interesantes.
Int eger. String y Charader funcionan con PriorityQuclIc porque estas clases ya tienen un orden natural predefinido. Si
queremos utilizar nuestra propia clase en una cola PriorityQueue. deberemos incluir una funcionalidad adicional para gene-
rar una ordenacin natural. o bien proporcionar nuestro objeto Comparator. En el Caphl lo 17. Anlisis de/allado de los
cOllfenedores se proporciona un ejemplo ms sofi sticado en el que se ilustra este mecani smo.
Ejercicio 28: (2) Rell ene la cola PriorityQueue (ut ili zando offer()) con valores de tipo Double creados utilizando
java.utiI.Random. y luego elimine los elementos con poll( ) y visualcelos.
Ejercicio 29: (2) Cree una clase simple que herede de Object y que no contenga ningn nombre y demuestre que se
pueden aiiadir mltipl es elementos de di cha clase a una cola PriorityQuclIe. Este tema se explicar en
detalle en el Capintlo 17. Anlisis detallado de los contenedores.
266 Piensa en Java
Comparacin entre Collection e Iterator
CoUection es la interfaz raz que describe las cosas que ti enen en comn todos los contenedores de secuencias. Podramos
considerarla como una especie de " interfaz incidental", que surgi debido a la existencia de aspectos comunes entre las otras
interfaces. Adems, la clase java.util.AbstractCollection proporciona una implementacin predeterminada de Collection,
para poder crear un nuevo subtipo de AbstractCollection sin duplicar innecesariamente el cdigo.
Un argumento en favor de disponer de una interfaz es que sta nos permite crear cdigo genrico. Escribiendo como una
interfaz en lugar de como una implementaci n, nuestro cdigo puede aplicarse a ms tipos de objetos.
6
Por tanto, si escri.
bi mos un mtodo que admita un mtodo Collection, dicho mtodo podr aplicarse a cualquier tipo que implemente
CoUection, y esto permite implementar Collection en cualqui er clase nueva para poderla usar con el mtodo que hayamos
escri to. Resulta interesante resaltar, sin embargo, que la bibl ioteca estndar e++ no di spone de ninguna clase base comn
para sus contenedores: los aspectos comunes entre los contenedores se consiguen utili zando iteradores. En Java, podra pare
cer adecuado seguir la tcnica utilizada en e++ y expresar los aspectos comunes de los contenedores utilizando un iterador
en lugar de una coleccin. Sin embargo, ambos enfoques estn entrelazados, ya que impl ementar Collection tambin impli.
ca que deberemos proporcionar un mtodo iterator() :
11 : holding/lnterfaceVs l terator . java
import typeinfo.pets.*;
import java .util.*;
public class InterfaceVslterator
public static void display(Iterator<Pet::> it) {
while(it.hasNext()) (
Pet p = it.next();
System. out.print (p .id () +
System.out.println() ;
11.11
+ p + 11 11 );
public static void display(Collection<Pet> pets)
for(Pet p : pets)
System.out.print(p.id() + 11. 11 + P + 11 " )i
System.out.println() ;
public static void main (String [] args) {
List<Pet> petList = Pets.arrayList(B);
Set<Pet::> petSet = new HashSet<Pet ::> (petList ) ;
Map<String,Pet::> petMap =
new LinkedHashMap<String,Pet::>{);
String [] names = ("Ralph, Eric, Robn, Lacey, ti +
"Britney, Sam, Spot, Fluffy" ) .split( " , 11 );
for(int i = O; i < names.length; i++)
petMap.put (names [i], petList.get (i));
display{petList) ;
display(petSet) ;
display(petList.iterator()) ;
display(petSet . iterator()) i
System.out.println (petMap) ;
System.out.println(petMap.keySet()) ;
display(petMap.values()) ;
display(petMap . values() .iterator()) i
1* Output:
O:Rat l:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx
4:Pug 6:Pug 3:Mutt l:Manx 5:Cymric 7:Manx 2:Cymrc O:Rat
fi Algunas personas defienden la creacin automtica de una interfaz para toda posible combi nacin de mtodos en una clase: en ocasiones, defienden que
se haga esto para lodas las clases. En mi opinin. una interfaz debera tener un significado mayor que una mera duplicacin mecnica de combi naciones
de mtodos, por lo que suelo preferir esperar y ver qu valor aadira una interfaz antes de crearla.
O:Rat l:Manx 2:Cymric 3 : Mutt 4:Pug 5:Cymric 6 : Pug 7:Manx
4:PU9 6:Pug 3:Mutt l:Manx 5:Cymric 7:Manx 2:Cymric O:Rat
11 Almacenamiento de objetos 267
Eric=Manx, Robin=Cymric, Lacey=Mutt, Britney=Pug, Sam=Cymric, Spot=Pug,
Fluffy=Manx}
[Ralph, Eric, Robin, Lacey, Britney, Sam, Spot, Fluffyl
O:Rat l: Manx 2:Cymric 3:Mutt 4:Pug S:Cymric 6:Pug 7 :Manx
O:Rat l:Manx 2:Cymric 3:Mutt 4:Pug 5 : Cymric 6:Pug 7:Manx
- /// ,-
Ambas versiones de display() funci onan con objetos Map y con subtipos de Collection, y tanto la interfaz Collection como
lterator penniten desacoplar los mtodos display(). sin forzarles a conocer ningn detalle acerca de la implementacin
concreta del contenedor subyacente.
En este caso, los dos enfoques se combinan bien. De hecho, Colleetion lleva el concepto un paso ms all porque es de tipo
Iterable, Y por tanto, en la implementacin de display(Collection) podemos usar la estmcturajreac/, lo que hace que el
cdigo sea algo ms limpi o.
El uso de Iterator resul ta muy recomendable cuando se implementa una clase externa, es decir, una que no sea de tipo
Coll ection, ya que en ese caso resultara dificil o incmodo hacer que esa clase impl ementara la interfaz Collection. Por
ejemplo. si creamos una implementacin de Collection heredando de una clase que almacene objetos Pet, deberemos impl e-
mentar todos los mtodos de Collection, incluso aunque no necesitemos utilizarlos dentro del mtodo display( ). Aunque
esto puede llevarse a cabo fcilmente heredando de AbstractCollection. estaremos forzados a implementar iterator( ) de
todas fomlas,j unto con size(), para proporcionar los mtodos que no estn implementados en AbstractCollcction. pero son
uti lizados por los otros mtodos de AbstraetColleetion:
jI : holdingfCollectionSequence.java
import typeinfo.pets.*;
import java.util.*;
public class ColleccionSequence
extends AbstractCollection<Pet>
private Pet(] pets = Pets . createArray(B);
public int size( ) { return pets.length; }
public Iterator<Pet> iterator () {
return new Iterator<Pet> () {
private int index = o;
public boolean hasNext ( )
return index < pets.length
public Pet next () { return pets [index++J i
public void remove () { // No implementado
throw new UnsupportedOperationException (} i
}
} ;
public stati c void main (String (] args ) {
Col lectionSequence e = new CollectionSequence ( );
Int erfaceVslterator.display (c ) i
InterfaceVslterator.display(c.iterator( )) ;
1* Output:
O:Rat l:Manx 2:Cymric 3:Mutt 4:Pug S: Cymric 6:Pug 7:Manx
O:Rat l : Manx 2:Cymric 3 : Mutt 4 : Pug S:Cymric 6:Pug 7 : Manx
- /// , -
El mtodo remo\'c() es una "operacin opcional", de la que hablaremos ms delante en el Captulo 17, Anlisis detallado de
los conrenedores. Aqu , no es necesario implementarlo y, si lo invocamos, generar una excepcin.
En este ejemplo, podemos ver que si implementamos Collection. tambin implementamos iterator(); adems, vemos que
implementar nicamente iterator() slo requiere un esfuerzo ligeramente menor que heredar de AbstractCollection. Sin
embargo, si nuestra clase ya hereda de otra clase, no podemos heredar de AbstractCollcction. En tal caso, para implemen-
268 Piensa en Java
tar Collection sera necesario implementar todos los mtodos de la interfaz. En este caso. resultara mucho ms senci ll o
heredar y aadir la posibili dad de crear un terador:
11 : ho lding/ NonCollectionSequenee.java
import typeinfo.pets.*
import j ava.util.*;
class PetSequence
protected Pet(] pets Pets.createArray (8)
publie class NonCollectionSequenee extends PetSequence {
public Iterator<Pet> iterator () {
return new Iterator<Pet> () {
};
private int index = O;
public boolean hasNext ( )
return index < pets . length
publ ic Pet next () { return pets [index++]
publ ic void remove () { / I No implementado
throw new UnsupportedOperationException() i
publie static void main (String[) args) {
NonCollectionSequenee ne = new NonCollectionSequence () i
InterfaceVslterator.display(nc.iterator {) ;
1* Output:
O:Rat l:Manx 2:Cymric 3 : Mutt 4:Pug 5:Cymric 6 : Pug 7:Manx
*/ // ,-
Generar un objeto Iterator es la forma con acoplami ento ms dbil para conectar una secuencia con un mtodo que consu-
ma esa secuencia; adems, se imponen muchas menos restricciones a la clase correspondi ente a la secuencia que si imple-
mentamos Coll ection.
Ejercicio 30: (5) Modifique CollectionSequence.j.va para que no herede de AbstractCollection, sino que implemen-
te Collection.
La estructura foreach y los iteradores
Hasta ahora, hemos utili zado principalment e la si ntaxis foreach con las matrices, pero dicha simaxis tambi n fUll ciona con
cualquier objeto de tipo ColJection. Hemos visto algunos ejempl os en los que empleaba ArrayList, pero he aqu una demos-
tracin general:
/ 1 : holding/ ForEachCollections.java
II All eollections work with foreach.
import java.util.*;
public elass ForEachCollections
publie static void main{String() args ) {
Collection<String> es = new LinkedList<String> {)
Collections.addAll {es,
"Take the long way home" . split {" " ) i
for (String s : es)
System. out. print (" '" + s + '" ,,) i
/ * Output:
' Take' 'the' ' long' 'way' 'home '
* /// ,-
11 Almacenamiento de objetos 269
Como es es una coleccin, este cdigo demuestra que todos los objetos Collectioll permiten emplear la estmcturajoreach.
La razn de que este mtodo funcione es que en Java SES se ha introducido una nueva interfaz denominada Iterable que
contiene un mtodo iterator( ) para generar un objeto Iterator. y la interfaz Iterable es 10 que la cstrucrura foreach util
za para desplazarse a travs de una secuencia. Por tanto, si creamos cualqui er clase que implemente Iterable, dicha clase
podr ser utilizada en una instmccinJoreach:
// : holding/ lterableClass. j ava
/1 Anything Iterable works with foreach.
i mport java.util.*;
public class IterableClass implements Iterable<String>
protected String [] words = ( "And that is how " +
"we know the Earth to be banana-shaped . " ) . spIit ( 11 " )
pubIic Iterator<String> i terator () {
return new Iterator<String> () {
private int index = O;
public boolean hasNext ( ) {
return index < words.length
public String next () { return words [index++ 1
public void remove () { / / No implementado
throw new UnsupportedOperationException{) ;
}
} ;
public static void main (String[] args )
for {String s : new IterabIeClass ()
System. out. print (s + 11 ")
/ * Output:
And that is how we know the Earth to be banana-shaped.
* /// ,-
El mtodo iterator() devuelve una instancia de una implementacin interna annima de Iterator<String> que devuelve
cada palabra de la matri z. En main(), podemos ver que lterableClass funciona perfectamente en una instruccinforeach.
En Java SES, hay varias clases que se han definido como Iterable, principalment e todas las clases de tipo Collection (pero
no las de tipo Map). Por ejemplo, este cdi go muestra todas las variables del entorno del sistema operativo:
// : holding/ EnvironmentVariables. j ava
i mport java.util.*
public class EnvironmentVariables
public static void main (String[] args )
for {Map.Entry entry: System.getenv () .entrySet {
System. out. printIn (entry. getKey () + ": 11 +
entry.getVaIue ()}
/ * (Execute to see output ) */// :-
System.getenv()' devuelve un objeto Map, entrySet() produce un conjunto de elementos Map.Entry y un conjunto (Set)
de tipo Iterable. por lo que se le puede usar en un bucleforeach.
La instruccin.loreach funciona con una matri z o cualqui er cosa de tipo Iterable, pero esto no quiere decir que una matri z
sea automti camente de tipo Iterable, ni tampoco que se produzca ningn tipo de mecanismo de autoboxing:
7 Esto no estaba disponible antes de Java SE5. porque se pensaba que estaba acoplado de una manera demasiado estrecha con el sistema operativo, lo que
violaba la regla dc "cscribir los programas una vez y ejecutarlos en cualquier lugac'. El hecho de que sc haya incluido ahora sugiere que los diseadores
de Java han dccidido ser ms pragmticos.
270 Piensa en Java
11: holding/ArrayIsNot I terable . java
import java.util. * ;
publie elass ArrayIsNotIterabl e
statie <T> void test (Iterable<T> ib) {
for(T t , i b)
System. out.print(t + 11 " );
public static void main (String [] args) {
test (Arrays .asList(l, 2, 3));
String [] strings = { "A", " BII , "e" };
II Una matri z f unciona eon f oreac h, pero no e s de tipo Iterable :
I I ! test (strings) i
II Ha y que convert irl a exp lci t a me nte al t ipo Iterable:
test{Arrays . asList(st r i ng s )) i
1* Out put :
1 2 3 A B e
* /// , -
Al tratar de pasar una matriz como argumento de tipo Iter able el programa falla. No hay ningn tipo de conversin auto-
mtica a Iterable; sino que debe realizarse de forma manual.
Ejercicio 3\: (3) Modifique polyrnorphism/shapelRandomShapeGenerator.java para hacerlo de tipo Iterable.
Tendr que aadir un constructor que admita el nmero de elementos que queremos que el iterador gene-
re antes de pararse. Verifique que el programa funciona.
El mtodo basado en adaptadores
Qu sucede si tenemos una clase exi stente que sea de tipo Iterable, y queremos aadir una o ms formas nuevas de uti -
li zar esta clase en una instruccin foreach? Por ejemplo, suponga que queremos poder decidir si hay que iterar a travs
de una li sta de palabras en direccin directa o inversa. Si nos limitamos a heredar de la clase y sustituimos el mtodo itera-
tor(), estaremos sustituyendo el mtodo existente y no dispondremos de la capacidad de opcin.
Una solucin es utili zar lo que yo denomino Mtodo basado en adaptadores. Cuando se dispone de una interfaz y nos hace
falta otra, podemos resol ver el problema escribiendo un adaptador. Aqui , lo que queremos es aadir la capacidad de ge-
nerar un iterador inverso, pero sin perder el iterador directo predetenninado. as que no podemos limitarnos a sustituir el
mtodo. En su lugar, lo que hacemos es aadir un mtodo que genere un objeto Iterable que pueda entonces utilizarse en
la instruccin foreach. Como puede ver en el ejemplo siguiente. esto nos pennite proporcionar mltiples fonnas de usar
joreac/ :
11 : holding/ AdapterMethodIdiom.java
II El mtodo basado en adap tadores permite uti l izar
II foreaeh con tipos adicional es de objetos i terables .
import java.util .* ;
elass ReversibleArrayList<T> extends ArrayList<T> {
public ReversibleArrayList (Collection<T> e) { super (e) i
public Iterable<T> reve rsed () {
return new Ite rable<T> () {
publie Iterator<T> iterator ()
return new Iterator<T> () {
int eurrent = size () - 1;
public boolean hasNext () { return cur rent > - 1 ; }
public T next() { return get(eurrent- - ); }
public void remove ( ) { liNo impl e ment a do
)
};
throw new UnsupportedOperationExcep tion () ;
)
) ;
public class AdapterMethodldiom {
public static void main(String[] args)
ReversibleArrayList<String> ral =
new ReversibleArrayList<String>(
Arrays.asList(OITo be or not ta be".split(" ")) ) i
// Toma el iterador normal va iterator():
ter (String s : ral)
System.out.print (s + 11 " ) i
System.out.println () ;
JI Entregar el objeto Iterable de nuestra eleccin
for{String s : ral.reversed() )
System.out.print (s + 11 ");
; * Output:
To be or not ta be
be ta not or be To
* /1/ ,-
11 Almacenamiento de objetos 271
,i nos limitamos a poner el objeto ral dentro de la instmccin/oreac/, obtenemos el iterador directo (predeterminado). Pero
i invocamos reverscd() sobre el objeto, el comportamiento ser distinto.
Jtilizando esta tcnica, podemos aadir dos mtodos adaptadores al ejemplo lterableClass.java:
/1: holding/ MultilterableClass.java
/1 Adicin de varios mtodos adaptadores.
import java.util. *
public class MultilterableClass extends IterableClass (
public Iterable<String> reversed () (
return new Iterable<String> () (
public Iterator<String> iterator ()
)
};
return new Iterator<String> () (
int current = words.length - 1;
public boolean hasNext () { return current > -1; }
public String next () { return words (current- -] }
public void remove () { // No implementado
)
};
throw new UnsupportedOperationException()
public Iterable<String> randomized () (
return new Iterable<String> () (
public Iterator<String> iterator ()
List<String> shuffled =
)
) ;
new ArrayList<String> (Arrays.asList (words ;
Collections.shuffle(shuffled, new Random(47
return shuffled.iterator ()
public static void main (String(] args ) {
MultilterableClass mic = new MultilterableClass( ) ;
for(String s : mic.reversed(
Systern.out.print (s + 11 " )
272 Piensa en Java
System.out.println () i
for (String s mic.randomized (
System.out.print (s + 11 11 ) i
System. out.println () i
f or (String s : mie l
System.out.print (s + 11 11 ) i
/ * Output:
banana-shaped. be to Earth the know we how lS that And
is banana-shaped. Earth that how the be And we know to
And that lS how we know the Earth to be banana-shaped.
* /// ,-
Observe que el segundo mtodo, random(). no crea su propi o It erator sino que se limita a devol ver el correspondi ente a
la li sta List mezclada.
Puede ver a la salida que el mtodo Collcctions.shuffle() no afecta a la matri z original , sino que slo mezcla las referen-
cias en shuffled. Esto es as porque el mtodo randomized() empaqueta en el nuevo objeto Arra)' List el resultado de
Arrays.asList(). Si mezclramos directamente la li sta generada por Arrays.asList() se modificara la matri z subyacente.
como puede ver a continuacin:
/1 : holding/ ModifyingArraysAsList.java
import java.util .*
publie elass ModifyingArraysAsList
publie stat i c void main(Stri ng[] a r gs)
Random rand = new Random(47) i
Integer[) ia = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
List<Integer> listl =
new ArrayList<Integer> (Arrays . asList (ia;
System.out.println ( IIBefore shuf f ling: 11 + listl ) i
Colleetions.shuffle(listl, rand) i
System.out.println("After shuff ling : " + listl)
System. out . prin tln ( " array : + Ar rays . toSt ring(ia ;
List<Integer> list2 = Arrays . asList{ia ) i
System.out.println {"Before shuffling: " + list2 ) i
Colleetions.shuffle (list2, rand) i
System.out.println ( "After shuffling: " + list2 ) i
System. out. println ("array: " + Arrays. toString (ia) )
1* Output:
Before shuffling:
After shuffling:
(1, 2, 3, 4, 5, 6, 7, 8, 9, la]
[4, 6, 3, 1, 8, 7, 2, 5, 10, 9)
array: [1, 2 , 3, 4, 5, 6, 7, 8, 9, la]
Before shuffling: (1, 2, 3, 4, 5, 6, 7, 8, 9, la]
After shuffling: (9, 1, 6, 3 , 7, 2, 5, ID, 4, 8]
array' [9, 1, 6, 3, 7, 2, 5, 10, 4, 8)
* /// ,-
En el primer caso, la salida de Arrays,asList( j se entrega al constructor de ArrayList( j , y esto crea una li sta ArrayList
que hace referencia a los elementos de ia. Mezclar estas referencias no modifica la matri z. Sin embargo, si uti li zamos direc-
tamente el resultado de Arrays.asList(ia), el mezclado moditica el orden de ia. Es importante tener en clIenta que
Arrays.asList() genera un objeto List que utili za la matri z subyacente corno su implememacin fi sica. Si hacemos algo a
ese objeto List que lo modifique y no queremos que la matri z ori ginal se vea afectada, entonces ser necesario reali zar una
copia en otro contenedor.
Ejercicio 32: (2) Si guiendo el ejemplo de MultilterableClass, a"ada mtodos reversed() y randomized() a
NonCollcctionSequencc,java. Haga tambin que NonCollectionSequence implemente Iterable y mues-
tre que las distintas tcnicas funcionan en las instnlcciones foreach.
11 Almacenamiento de objetos 273
Resumen
Java proporciona varias fannas de almacenar objetos:
1. Las matrices asocian ndices numricos con los objetos. Almacenan objetos de un tipo conocido, as que no es
necesario proyectar el resultado sobre ningn otro tipo a la hora de buscar un objeto. Pueden ser multidimcnsio-
nales y tambin almacenar tipos primitivos. Sin embargo, su tamaiio no puede modificarse despus de haberlas
creado.
2. Las colecciones (Collection) almacenan elementos independientes, mientras que los mapas (Map) almacenan
parejas asociadas. Utilizando los genricos de Java, especificamos el tipo de objeto que hay que almacenar en los
contenedores, con el fin de no introduci r un tipo incorrecto en un contenedor y tambin para no tener que efec-
tuar una proyeccin de los elementos en el momento de extraerlos de un contenedor. Tanto las colecciones como
los mapas cambian automticamente de tamao a medida que aadimos ms elementos. Los contenedores no per-
miten almacenar tipos primilivos, pero el mecanjsmo de autoboxillg se encarga de producir las primitivas a los
tipos envoltorio almacenados en el contenedor.
3. Al igual que una matriz, una lista (List) tambin asocia ndices numricos con objetos; por tanto, las matrices y
las listas son contenedores ordenados.
4. Ut ili ce una li sta ArrayList si tiene que realizar numerosos accesos aleatorios; pero, si lo que va a hacer es un
gran nmero de inserciones y de borrados en mitad de la lista, utili ce LinkedList.
S. El comportamiento de las colas (Queue) y de las pilas se obtiene mediante LinkedList.
6. Un mapa (Map) es una fonna de asociar los objetos no con valores enteros. sino con olros objelos. Los mapas
HashMap estn diseados para un acceso rpido, mientras que un mapa TreeMap mantiene sus claves ordena-
das y no es tan rpido como HashMap. Un mapa LinkedHashMap mantiene sus elementos en orden de inser-
cin, pero proporciona un acceso rpido con mecanismos de lCIS/.
7. Los conjuntos (S el) slo aceplan objetos no duplicados. Los conjuntos Hash.Sel proporcionan las bsquedas ms
rpidas. miemras que TreeSet mantiene los elementos en orden. LinkedHashSet mantiene los elementos en
orden de insercin.
8. No hay necesidad de utilizar las clases antiguas Vector, Hashtable y Stack en los nuevos programas.
Resulta til examinar un diagrama simplificado de los contenedores Java (sin las clases abstractas ni los componentes anti-
guos). En este diagrama se incluyen nicamente las interfaces y clases que podemos enCOlHrar de fonna habitual.
r-------. r-------. .-------.
: Jterator :-. ------------: Collection :-. ------------: Map :
Genera Genera '---Lf---"
,
r-----,..----, , ____ L _____ ,
: I I
" _______ " Gene,. ..
,..------------1 ,..--- ,-----1
1,' I
,
PriorityQueue
,
I i - I
Utilidades
Collections
: Comparable : ......... : Comparator :
.----------" r LinkedHashSet I
Arrays
Taxonoma simple de los contenedores
Como puede ver, slo hay realmente cuatro componentes contenedores bsicos: Map, List, Set y Queue, y slo dos o tres
implementaciones de cada uno (las implementaciones de java.util.concurrent para Queue no estn incluidas en este dia-
grama). Los contenedores que ms habinlalmente se utili zan son los que tienen lneas gmesas de color negro a su alrededor.
274 Piensa en Java
Los recuadros punteados representan interfaces, mientras que los recuadros de lnea continua son clases normales (concre.
tas). Las lneas de puntos con flechas huecas indican que una clase concreta est implementando una interfaz. Las flechas
rellenas muestran que una clase puede generar objetos de la clase a la que apunta la flecha. Por ejemplo, cualquier objeto
Collection puede generar un objeto Iterator, y un objeto List puede generar un objeto Li,lIterator (adems de un objeto
Iterator nonma!. ya que List hereda de Collection).
He aqu un ejemplo que muestra la diferencia en mtodos entre las di stintas clases. El cdigo concreto se ha tomado del
Captulo 15. Genricos; simplemente lo incluimos aqu para poder generar la correspondiente salida. La salida tambin
muestra las interfaces que se implementan en cada clase o interfaz.
jj: holdingjContainerMethods.java
import net.rnindview.util.*;
public class ContainerMethods
public static void main{String[] args) {
ContainerMethodDifferences.rnain{args} i
j * Output: (Sample)
Collection: {add, addAII, clear, contains, containsAII, equals, hashCode, isEmpty, itera-
tor, remove, removeAII, retainAII, size, toArray]
Interfaces in Collection: [Iterable]
Set extends Collection , adds: []
Interfaces in Set: [Collectionl
HashSet extends Set, adds: [J
Interfaces in HashSet: [Set, Cloneable, Serializablel
LinkedHashSet extends HashSet, adds: []
Interfaces in LinkedHashSet: (Set, Cloneable, Serializablel
TreeSet extends Set, adds: [pollLast, navigableHeadSet, descendingIterator, lower,
headSet, ceiling, pollFirst, subSet, navigableTailSet, comparator, first, floor, last,
navigableSubSet, higher, tailSet]
Interfaces in TreeSet: [NavigableSet, Cloneable, SerializableJ
List extends Collection, adds: [listlterator, indexOf, get, subList, set, lastIndexOf]
Interfaces in List: (Collection]
ArrayList extends List, adds: [ensureCapacity, trirnToSize]
Interfaces in ArrayList: [List, RandomAccess, Cloneable, Serializable]
LinkedList extends List, adds: [pollLast, offer, descendingIterator, addFirst, peekLast,
removeFirst, peekFirst, removeLast, getLast, pollPirst, pop, polI, addLast,
removeFirstOccurrence, getFirst, element, peek, offerLast, push, offerFirst,
removeLastOccurrence]
Interfaces in LinkedList: [List, Deque, Cloneable, Serializable]
Queue extends Collection, adds: [offer, element, peek, polI]
Interfaces in Queue: (Collectionl
PriorityQueue extends Queue, adds: [comparator]
Interfaces in PriorityQueue: [Serializablel
Map: (clear, containsKey, containsValue, entrySet, equals, get, hashCode, isEmpty,
keySet, put, putAII, remove, size, valuesl
HashMap eXi:.ends Map, adds: [J
Interfaces in HashMap: [Map, Cloneable, Serializable]
LinkedHashMap extends H a s ~ ~ a p adds : []
Interfaces in LinkedHashMap: [Map]
SortedMap extends Map, adds: [subMap, comparator, firstKey, last Key, headMap, tailMapJ
Interfaces in SortedMap: [Map]
TreeMap extends Map, adds: [descendingEntrySet, subMap, pollLastEntry, lastKey,
floorEntry, lastEntry, lowerKey, navigableHeadMap, navigableTailMap, descendingKeySet,
tailMap, ceilingEntry, higherKey, pollFirstEntry, comparator, firstKey, fl oorKey,
higherEntry, firstEntry, navigableSubMap, headMap, lowerEntry, ceilingKey]
Interfaces in TreeMap: [NavigableMap, Cloneable, Serializable]
. /// ,-
11 Almacenamiento de objetos 275
Como puede ver. todos los conjuntos, excepto TreeSet, tienen exactamente la mi sma interfaz que Collection. List y
Collection difieren significativamente, aunque List requiere mtodos que se encuentran en Collection. Por otro lado, los
mtodos de la interfaz Queue son autnomos; los mtodos de Collection no son necesarios para crear una implementacin
de Queue funcional. Finalmente, la nica interseccin entre Map y Collection es el hecho de que un mapa puede generar
colecciones uti lizando los mtodos entrySet() y values( ).
Observe la interfaz de marcado java.util.RandomAccess, que est asociada a ArrayList perno no a LinkedList. Esta inter-
faz proporciona informacin para aquellos algoritmos que quieran modificar dinmicamente su comportamiento dependien-
do del uso de un objeto List concreto.
Es verdad que esta organizacin parece un poco extraa en lo que respecta a las jerarquas orientadas a objetos. Sin embar-
go, a medida que conozca ms detalles acerca de los contenedores en java.util (en particular, en el Capitulo 17, Anlisis
detallado de los contenedores), ver que exi sten otros problemas ms importantes que esa estructura de herencia ligeramen-
te extraa. Las bibliotecas de contenedores han constituido siempre un problema de diseo realmente dificil ; resol ver estos
problemas implica tratar de satisfacer un conjunto de fuerzas que a menudo se oponen entre s. Como consecuencia, debe-
mos preparamos para llegar a cienos compromisos en algunos momentos.
A pesar de estos problemas, los contenedores de Java son helTamientas fundamentales que se pueden utilizar de fonna coti-
diana para hacer nuestros programas ms simples, ms potentes y ms efectivos. Puede que tardemos un poco en aCOSUlnl -
bramas a algunos aspectos de la biblioteca, pero lo ms probable es que el lector comience rpidamente a adquirir y utilizar
las clases que componen esta biblioteca.
Puede encontrar las solucioncs a los ejercicios en el documento electrnico rile Thinlng in JaI'tl AnnOfafed So/utioll Guide, disponible para
la venta en WII'lI'.Alindr'iew.net.
Tratamiento de
errores mediante

excepciones
La fi losofia bsica de Java es que "el cdigo errneo no ser ejecutado".
El momento ideal de detectar un error es en tiempo de compilacin, antes incluso de poder ejecutar el programa. Sin embar-
go, no IOdos los errores pueden detectarse en tiempo de compilacin. El resto de los problemas debern ser gestionados en
tiempo de ejecucin, utilizando algn lipo de fanna lidad que pemlita que quien ha originado el error pase la infonnacin
apropiada a un receptor que sabr cmo hacerse cargo de las dificultades apropiadamente.
Una de las fannas ms potentes de incrementar la robustez del cdigo es disponer de un mecanismo avanzado de recupera-
cin de errores. La recuperacin de errores es una de las preocupaciones principales de todos los programas que escribimos,
pero resulta especialmente importante en Java. donde lino de los objetivos principales consiste en crear componentes de pro-
gramas para que otros los utilicen. Para crear lIn s;sfema robusfo, cada componente f;ene que ser robusto. Al proporcionar
un modelo coherente de informe de elTores utilizando excepciones, Java pennite que los componentes comuniquen los pro-
blemas de manera fiable al cdigo cliente.
Los objetivos del tratamiento de excepciones en Java son simplificar la creacin de programas fiables de gran envergadura
utili zando menos cdigo de lo habitual y llevar esto a cabo con una mayor confianza en que nuestra apl icacin no va a
encontrarse con errores no probados. El tema de las excepciones no resulta demasiado difici l de aprender y el mecanismo
de excepciones es una de esas caractersticas que proporcionan beneficios inmediatos y de gran importancia a cualquier pro-
yecto.
Puesto que el tratamiento de excepciones es la nica forma oficial en la que Java infonna acerca de los CITO res, y dado que
dicho mecanismo de tratamiento de excepciones es impuesto por el compi lador Java, no son demasiados los ejemplos
que podramos escribir en este libro sin antes estudiar el tratamiento de excepciones. En este captulo, se presenta el cdi-
go que es necesario escribir para gestionar las excepciones adecuadamente, mostrndose tambin cmo podemos generar
nuestras propias excepciones si alguno de nuestros mtodos se mete en problemas.
Conceptos
El lenguaje e y otros lenguaj es anteriores di sponan a menudo de mltiples esquemas de tralamiento de elTores. y dichos
esquemas se solan establecer por convenio y no como parte del lenguaje de programacin. NOITna(mente, lo que se haca
era devolver un valor especial o configurar una variable indicadora, y se supona que el receptor deba examinar el valor o
la variable y detennmar que algo iba mal. Sin embargo, a medida que fueron pasando los aos. se descubri que los progra-
madores que uti lizaban una biblioteca tendan a pensar en s mismos como si fueran inmunes al error; era casi como si dije-
ran: "S, puede que a otros se les presenten elTores, pero en mi cdigo no hay errores". Por tanto, de f0IT118 bastante natura l,
los programadores tendan a no comprobar las condiciones del elTor (y adems, en ocasiones, esas condiciones de error eran
demasiado tontas como para comprobarlas ]). Si furamos tan exhaustivos como para comprobar si se ha producido un error
cada vez que invocamos un mtodo, el cdigo se convertira en una pesadilla il egible. Puesto que los programadores
! El programador en e puede. por ejemplo. consultar el valor de retomo de printfO.
278 Piensa en Java
podan, a pesar de todo, construir sus sistemas con estos lenguajes. se resistan a admitir la realidad: que esta tcnica de ges-
tin de errores representaba una limitacin importante a la hora de crear programas de gran envergadura que fueran robus_
tos y mantenibles.
La solucin consiste en eliminar la naturaleza casual del tratamiento de errores e imponer una cierta fonnalidad. Este modo
de proceder tiene, en la prctica, una larga hi storia, porque las implementaciones de los mecanismos del tratamiento de
excepciones se remontan a los sistemas operativos de la dcada de 1960, e incluso a la instmcci6n "on error goto" de
BASIe. Pero el mecanismo de tratamiento de excepciones de C++ estaba basado en Ada, y el de Java se basa principalmen_
te en C++ (aunque se asemeja ms al de Object Pascal).
La palabra "excepcin" hace referencia a algo que no tiene lugar de la forma acostumbrada. En el lugar donde aparece un
problema, puede que no sepamos qu hacer con l, pero de lo que s podemos estar seguros es de que no podemos conti.
nuar como si no hubiera pasado nada; es necesario pararse y alguien, en algn lugar, tiene que saber cmo responder al error.
Sin embargo. no disponemos de suficiente infonnacin en el contexto actual como para poder corregir el problema, as que
lo que hacemos es pasar dicho problema a otro contexto superior en el que haya alguien cualificado para tomar la decisin
adecuada.
El otro beneficio importante derivado de las excepciones es que tienden a reducir la complejidad del cdigo de gestin de
errores. Sin las excepciones, es necesario comprobar si se ha producido un error concreto y resolverlo en mltiples lugares
del programa. Sin embargo, con las excepciones ya no hace falta comprobar los errores en el punto donde se produce la lla-
mada a un mtodo, ya que la excepcin garantiza que alguien detecte el error. Adems, slo hace falta tratar el problema en
un nico sit io, en lo que se denomina la rutina de Iratamiento de excepciones. Esto nos ahorra cdigo y permite tambin
separar el cdigo que describe lo que queremos hacer durante la ejecucin normal, de ese otro cdigo que se ejecuta cuan-
do las cosas van mal. En general, la lectura, la escritura y la depuracin del cdigo se bacen mucho ms claras con las excep-
ciones que cuando se utiliza la antigua fonna de tratar los errores.
Excepciones bsicas
Una condicin excepcional es un problema que impide la continuacin del mtodo o mbito actuales. Resulta importante
di stinguir las condiciones excepcionales de los problemas nonnales, en los que disponemos de la suficiente informaci n
dentro del contexto actual, como para poder resolver de alguna manera la dificultad con que nos hayamos encontrado. Con
una condicin excepcional, no podemos continuar con el procesamiento, porque no disponemos de la infonnacin necesa-
ria para tratar con el problema en el contexto actual. Lo nico que podemos hacer es saltar fuera del contexto actual y pasar
dicho problema a otro contexto de orden superior. Esto es lo que sucede cuando generamos una excepcin.
La divi sin representa un ejemplo sencillo: si estamos a punto de dividir por cero, merece la pena comprobar dicha condi-
cin, pero qu implica que el denominador sea cero? Puede que sepamos, en el contexto del problema que estemos inten-
tando resolver en ese mtodo concreto, cmo tratar con un denominador cero. Pero si se trata de un valor inesperado, no
podremos tratar con l, y deberemos por tanto generar una excepcin en lugar de continuar con la ruta de ejecucin en la
que estuviramos.
Cuando generamos una excepcin suceden varias cosas. En primer lugar, se crea el objeto excepcin de la misma fonna que
cualquier otro objeto Java: en el cmulo utilizando la instruccin new. A continuacin. se detiene la ruta actual de ejecucin
(aquella que ya no podemos continuar) y se extrae del contexto actual la referencia al objeto excepcin. En este punto, el
mecanismo de tratamiento de excepciones se hace cargo del problema y comienza a buscar un lugar apropiado donde con
tinuar ejecutando el programa. Dicho lugar apropiado es la rutina de Iratamiento de excepciones, cuya tarea consiste en
recuperarse del problema de modo que el programa pueda intentar hacer otra cosa o simplemente continuar con lo que estu-
viera haciendo.
Como ejemplo simple de generacin de excepciones vamos a considerar una referencia a un objeto denominada t. Es posi
ble que nos pasen una referencia que no haya sido inicializada, as que podramos tratar de comprobarlo antes de invocar
un mtodo en el que se utilice dicha referencia al objeto. Podemos enviar la infomlacin acerca del error a otro contexto de
orden superior, creando un objeto que represente dicha informacin y extrayndolo del contexto actual. Este proceso se
denomina generar una e.xcepcin. He aqu el aspecto que tendra en el cdigo:
Hit :: null)
throw new NullPointerException();
12 Tratamiento de errores mediante excepciones 279
Esto genera la excepcin, lo que nos pemlite, en el contexto actual , olvidarnos del problema: dicho problema ser gestiona-
do de manera transparente en algn otro lugar. En breve veremos dnde exactamente se gestiona la excepcin.
Las excepciones nos penniten pensar en cada cosa que hagamos como si se tratara de una transaccin, encargndose las
excepciones de proteger esas transacciones: " .. .la premisa fundamental de las transacciones es que haca falta un mecanis-
mo de tratamiento de excepciones en la informtica distribuida. Las transacciones son el equivalente informtico de los con-
tratoS legales. Si algo va mal, detenemos todos los clculos"2. Tambin podemos pensar en las transacciones como si se
tratara de un sistema integrado para deshacer acciones, porque (con un cierto cuidado) podemos establecer varios puntos de
recuperacin en cualquier programa. Si una parte del programa falla, la excepcin se encarga de "deshacer" las operaciones
basta un punto estable conocido dentro del programa.
Uno de los aspectos ms importantes de las excepciones es, que si sucede algo realmente grave, no penniten que el progra-
ma contine con la ruta de ejecucin ordinaria. Este tema ha constituido un grave problema en lenguajes como e y C++;
especialmente en C, donde no haba ninguna fonna de impedir que el programa continuara por una ruta de ejecucin en caso
de aparecer un problema, de modo que era posible ignorar los problemas durante un largo tiempo y acabar en un estado
totalmente inadecuado. Las excepciones nos penniten (entre otras cosas) obligar al programa a detenerse y a infonnamos
de qu es lo que ha pasado o, idealmente, obligar al programa a resolver el problema y volver a un estado estable.
Argumentos de las excepciones
Al igual que sucede con cualquier objeto en Java, las excepciones siempre se crean en el cmulo de memoria utilizando
new, lo que asigna el correspondiente espacio de almacenamiento e invoca un constructor. Existen dos constructores en
todas las excepciones estndar. El primero es el constructor predetenninado y el segundo toma un argumento de cadena de
caracteres para poder aportar la informacin pertinente a la excepcin:
throw new NullPointerException(tlt :::o null
ll
) i
Esta cadena de caracteres puede posteriormente extraerse utilizando varios mtodos, como tendremos oportunidad de ver.
La palabra clave throw produce una serie de resultados interesantes. Despus de crear un objeto excepcin mediante new,
le proporcionamos la referencia resultante a throw. En la prctica, el objeto es "devuelto" desde el mtodo, aun cuando
dicho tipo de objeto no es normalmente lo que estaba diseado que el mtodo devolviera. Una fonna bastante simplificado-
ra de considerar el mecanismo de tratamiento de excepciones es como si fuera un tipo distinto de mecanismo de retomo,
aunque no debemos caer en la tentacin de llevar esa analoga demasiado lejos, porque podramos meternos en problemas.
Tambin podemos salir de mbitos de ejecucin ordinarios generando una excepcin. En cualquiera de los casos, se devuel-
ve un objeto excepcin y se sale del mtodo o mbito donde la excepcin se haya producido.
Las similitudes con el proceso normal de devolucin de resultados por parte de un mtodo tenninan aqu, porque e/lugar
a/ que se vuelve es completamente distinto de aquel al que volvemos en una llamada normal a Ull mtodo (volvemos a una
rutina apropiada de tratamiento de excepciones que puede estar alejada muchos niveles dentro de la pila del lugar en el que
se ha generado la excepcin).
Adems, podemos generar cualquier tipo de objeto Throwable, que es la clase raz de las excepciones. Normalmente, lo
que haremos ser generar una clase de excepcin distinta para cada tipo diferente de error. La informacin acerca del error
est representada tanto dentro del objeto excepcin como, implcitamente, en el nombre de la clase de excepcin, por lo
que alguien en el contexto de orden superior puede determinar qu es lo que hay que hacer con la excepcin (a menudo,
la nica infonuacin es el tipo de excepcin, no almacenndose ninguna informacin significativa dentro del objeto ex-
cepcin).
Deteccin de una excepcin
Para ver cmo se detecta una excepcin, primero es necesario entender el concepto de regin protegida. Se trata de una sec-
cin de cdigo que puede generar excepciones que est seguida por el cdigo necesario para tratar dichas excepciones.
2 Jim Gray. ganador del premio Tunng Award por las contribuciones que su equipo ha realizado al tema de las transacciones. Las palabras esln tomadas
de una entrevista publicada en www.acmq/leue.org.
280 Piensa en Java
El bloque try
Si nos encontramos dentro de un mtodo y generamos una excepcin (o si otro mtodo al que invoquemos dentro de ste
genera una excepcin), dicho mtodo temlinar al generarse la excepcin. Si no queremos generar (con throw) la excep_
cin para sal ir del mtodo. podemos definir un bloque especial denrro de dicho mtodo para capturar la excepcin. Este blo-
que se denomina Moque Iry (probar) porque lo que hacemos en la prctica es "probar" dentro de ese bloque las diversas
ll amadas a mtodos. El bloque try es un mbito de ejecucin ordinari o precedido por la palabra clave try:
try {
JI Cdigo que podra generar excepciones
Si qui siramos comprobar cuidadosamente los errores en un lenguaje de programacin que no tuviera mecani smos de trata-
miento de excepciones, tendramos que rodear todas las llamadas a mtodos con cdigo de preparacin y de comprobacin
de errores, incluso si invocramos el mismo mtodo en varias ocasiones. Con el tratamiento de excepciones incluimos todo
dentro del bloque t ry y capturamos lOdas las excepciones en un nico lugar. Esto significa que el cdigo es mucho ms fcil
de escribir y de leer, porque el objetivo del cdigo no se ve confundido con los mecanismos de comprobacin de errores.
Rutinas de tratamiento de excepciones
Por supuesto, la excepcin generada debe acabar siendo tratada en algn lugar. Dicho "lugar" es la rutina de tratamiento de
excepciones. existiendo una de dichas rutinas por cada tipo de excepcin que queramos capturar. Las rutinas de tratamien-
lO de excepciones estn situadas inmediatamente a continuacin del bloque try y se denotan mediante la palabra clave
eateh:
try {
II Cdigo que podra generar excepciones
catch(Tipol idl) {
// Tratamiento de las excepciones de Tipol
catch (Tipo2 id2) {
// Tratamiento de las excepciones de Tipo2
catch (Tipo3 id3 ) {
// Tratamiento de las excepciones de Tipo3
/ / etc",
Cada clusula catch (nuina de tratamiento de excepciones) es como un pequei'io mtodo que toma un nico argumento de
un tipo concreto. El identifi cador (idl , id2, etc.) puede utilizarse dentro de la mtina de tratamiento de excepciones exacta-
mente igual que un argumento de un mtodo. En ocasiones. nunca utilizamos el identificador, porque el tipo de la excep-
cin nos proporciona ya suficiente infomlacin como para poder tratar con ell a, pero a pesar de todo el identificador debe
estar preseme.
Las rutinas de tratamiento de excepciones deben aparecer inmediatamente despus del bloque try. Si se genera una excep-
cin, el mecanismo de tratamiento de excepciones trata de buscar la primera mtina de tratamiento cuyo argumento se ajus-
te al tipo de la excepcin. A continuacin, entra en la clusul a catch, y la excepcin se considera tratada. La bsqueda de
rutinas de tratamiento se detiene en cuanto finali zada la clusula catch. Slo se ejecutar la clusula catch que se ajuste al
tipo de la excepcin; estas clusulas no son como las instmcciones switch, en las que hace fa lta una instmccin break des-
pus de cada clusula case para impedir que las restantes clusul as se ejecuten.
Observe que, dentro del bloque try, puede haber di stintas llamadas a metodos que generen la mi sma excepcin, pero slo
hace falta una ni ca rutina de tratamiento.
Terminacin y reanudacin
Existen dos modelos bsicos en la teora de tratamiento de excepciones. Java sopona el modelo denominado de termina-
cin,3 en el que asumimos que el error es tan crtico que no hay forma de volver al lugar en el que se gener la excepcin.
J Como la mayoria de los lenguajes. incluyendo C+---. C#. Python, O, etc.
12 Tratamiento de errores mediante excepciones 281
Quienquiera que generara la excepcin, decidi que 110 haba ninguna manera de salvar la situacin, por lo que no desea que
volvamos a ese punto en el que la excepcin fue generada.
La alternativa se denomina reanudacin. Esta alternativa implica que la mtina de tratamiento de excepciones debe hacer
algo para rectificar la si tuacin, despus de 10 cual se vuelve a intentar ejecutar el mtodo fallido, presumiendo que esa
segunda vez tendr xito. Si queremos utili zar la tcnica de reanudacin. quiere decir que esperamos podemos continuar con
la ejecucin despus de que la excepcin sea tratada.
Si quiere disponer de un comportamiento de reanudacin en Java, no genere una excepcin cuando se encuentre con error.
En lugar de ello. invoque un mtodo que canija el problema. Alternativamente, coloque el bhJque tr)' dentro de un bucle
while que continlle volviendo a entrar en el bloque t ry hasta que el resultado sea sarisfactorio.
Hi stricamente, los programadores que utilizaban sistemas operativos donde se admita el tratamiento de excepciones con
reanudacin terminaron uti lizando cdigo basado en el mecani smo de tenllinacin y evitando emplear la reanudacin. Por
tanto. aunque la reanudacin pueda parecer atractiva a primera vista, no resulta demasiado til en la prctica. La razn prin-
cipal es. probablemente, el acoplamiento resultante: las mtinas de tratamiento con reanudacin necesitan saber dnde se ha
generado la excepcin, y necesitan tambin contener cdigo no genrico especfico de cada ubicacin donde la excepcin
se genere. Esto hace que el cdigo sea difcil de escribir y de mantener. especialmente en sistemas grandes en los que las
excepciones puedan generarse en muchos puntos di stintos.
Creacin de nuestras propias excepciones
No tenemos porqu limitamos a utili zar las excepciones Java existentes. La jerarqua de excepciones de Java no puede pre-
ver todos los errores de los que vayamos a querer nfonnar, as que podemos crear nuestros propios errores para indicar un
probl ema especial con el que nuestra biblioteca pueda encontrarse.
Para crear nuestra propia clase de excepcin, debemos heredar de una clase de excepcin existente, preferiblemente de una
cuyo significado est prximo al de nuestra propia excepcin (aunque a menudo esto no es posible). La fonl1a ms tri vi al
de crear un nuevo tipo de excepcin consiste, simplemente, en permitir que el compilador cree el constructor predetenllina-
do por nOSOLTOS, por lo que no hace fa lta prcticamente ningn cdigo:
//: exceptions/InheritingExceptions.java
// Creacin de nuestras propias excepciones.
class SimpleExceptian extends Exception {}
public class InheritingExceptions {
public void f() throws SimpleException
System.out.println( "Throw SimpleException from f() " ) i
throw new SimpleExcepti on();
public sta tic void main (String [] args) {
InheritingExceptions sed = new InheritingExceptions() i
try {
sed. f (1;
catch(SimpleException e)
System. out. println ( "Caught i t! ") i
/ * Out put:
Throw SimpleException fram f()
Caught it!
' /// ,-
El compilador crea un constmctor predetemlinado, que de forma automtica (e invisible) invoca al constructor predetermi-
nado de la clase base. Por supuesto, en este caso no obtenemos un constmctor Simpl eExcepti on(String), pero en la prcti-
ca dicho constructor 110 se usa demasiado. Como veremos, lo ms importante acerca de una excepcin es el nombre de la
clase, por lo que la mayor parte de las veces ulla excepcin como la que aqu se muestra ser perfectamente adecuada.
282 Piensa en Java
Aqui, el resultado se imprime en la consola, donde se captura y se comprueba automticamente utili zando el sistema de
visualizacin de la sa lida de programas de este libro. Sin embargo. tambin podramos enviar la infomlacin de error a la
salida de error estndar escribi endo en System. er r . Nomlalmentc, suele ser mejor enviar aqu la infonnacin de error que
a que puede estar redirigido. Si se enva la salida a System.err, no sera redirigida junto con System.out. por
lo que es ms probable que el usuario vea la infonnacin.
Tambi n podemos crear una clase de excepcin que tenga un constructor con un argumento St ri ng:
JI : exceptions/FullConstructors.java
class MyException extends Exception {
public MyException () {}
public MyException (String msg) { super (msg) ;
public cI ass FullConstructors (
public statie void f() throws MyException {
System. out. println (
II
Throwing MyException fram f () 11 ) ;
throw new MyException() j
public statie void g() throws MyException (
System.out.println( IIThrowing MyException fram g{) " );
throw new MyException ( "Originat ed in 9 () 11 ) ;
public statie void main(String[) args )
try {
() ;
}
catch(MyException el (
e.printStackTrace(System.out) i
try {
g();
catch (MyException el {
e.printStackTrace{System. out) ;
/* Output:
Throwing MyException fram f()
MyException
at FullConstl"uctors. f (FullConstructors . java: 11)
at FullConstructors.main(FullConstructors.java:19)
Throwing MyException from g()
MyExcepeion: Originated in g()
ae FullConstructors.g(FullConstructors.java:15)
at FullConstructors main(FullConstructors .java:24)
El cdigo aadido es de pequeo tamano: do; eonstmetores qoe definen la fomla en que se crea MyException. En el segun-
do const ructor, se invoca explcil amente el constructor de la clase base con un argumento String utilizando la palabra cla\e
super.
En las rutinas de tratamiento, se invoca uno de los metodos Throwable (de donde se hereda Explion): prinIStackTrace() .
Como puede \'er a la salida. esto genera infonnacin acerca de la secuencia de Intodos que fueron mocados hasta llegar
al punt o en que se gener la excepcin. Aqu, la informacin se enva a System.out. siendo automticamente capturada y
mostrada a la salida. Sin embargo, si invocamos la versin predetenninada:
e.printStackTrace() ;
la nfonnacin va a la salida de error estndar.
12 Tratamiento de errores mediante excepciones 283
Ej ercicio 1: (2) Cree una clase con un mtodo main() que genere un objeto de la clase Exception dentro de un blo-
que tr)' . Proporcione al constmctor de Exception un argumento String. Capture la excepcin dentro de
una clusula catch e imprima el argumento String. Aii.ada ulla clusula finaUy e imprima un mensaje para
demostrar que pas por alli.
Ejercicio 2:
(1) Defina una referencia a un objeto e inicialcela con nul!. Trate de invocar un mtodo a travs de esta
referencia. Ahora rodee el cdigo con una clusula try-catch para capturar la excepcin.
Ejercicio 3: (1) Escriba cdigo para generar y capnlfar Ulla excepcin ArraylndexOutOffioundsException (indice de
matri z fuera de lmites).
Ejercicio 4: (2) Cree su propia clase de excepcin utilizando la palabra clave extends. Escriba un constructor para
dicha clase que tome un argumento String y lo almacene dentro del objeto como una referencia de tipo
String. Escriba un mtodo que muestre la cadena de caracteres almacenada. Cree una clusula try-catch
para probar la nueva excepcin.
Ejercicio 5: (3) Defina un comportamiento de tipo reanudacin utilizando un bucle while que se repita hasta que se
dej e de generar una excepcin.
Excepci ones y registro
Tambin podemos registrar la sa lida utili zando java.util.logging. Aunque los detalles completos acerca de los mecanismos
de registro se presentan en el suplemento que puede encontrarse en la direccin htfp:/IMindView. netIBooksIBetterJava, los
mecanismos de registro bsicos son lo suficientemente sencillos como para poder utilizarlos aqu.
11 : exceptions / LoggingExceptions.java
II Una excepcin que proporciona la informacin a travs de un registro .
import java.util.logging .*;
import java.io.*;
class LoggingException extends Exception {
private static Logger logger =
Logger .getLogger("LoggingException
U
) ;
public LoggingException () (
StringWr iter trace = new StringWriter();
printStackTrace(new PrintWriter(trace;
logger . severe(trace.toString( ;
public class LoggingExceptions {
public static void main(String[] args)
try {
}
throw new LoggingException () i
catch (LoggingException e ) {
System.err.println ( "Caught " + el;
try {
throw new LoggingException();
catch (LoggingException el {
System.err.println(UCaught " + el i
/ * Output: (85% match)
Aug 30, 2005 4:02:31 PM LoggingException <init>
SEVERE : LoggingException
at LoggingExceptions.main(LoggingExceptions.java :19 )
Caught LoggingException
284 Piensa en Java
Aug 30, 2005 4:02:31 PM LoggingException <init>
SEVERE: LoggingException
at LoggingExceptions.main{LoggingExceptions.java:24)
Caught LoggingException
*///,-
El mtodo esttico Logger.getLoggcr() crea un objeto Logger asociado con el argumento String (usualment e, el nombre
del paqucle y la clase a la que se refieren los errores) que enva su salida a System.err. La forma ms fcil de escribir en
un objeto Logger consiste simplemente en invocar el mtodo asociado con el nivel de mensaj e de regi stro: aqu utilizamos
severe(). Para producir el objeto String para el mensaje de registro. convendra di sponer de la traza de la pila correspon
diente al lugar donde se gener la excepcin, pero printStackTrace() no genera un objeto String de fonna predetennina_
da. Para obtener un objeto String, necesit amos usar el mtodo sobrecargado printStackTrace() que toma un objeto
java.io.PrintWriter como argumento (todo esto se explicar con detalle en el Captulo 18, E/S). Si entregamos al construc
tor de Print\Vriter un objeto java.io.StringWritcr, la salida puede extraerse como un objeto String invocando toString().
Aunque la tcnica utili zada por LoggingException resulta muy cmoda. porque integra toda la infraestructura de registro
dentro de la propia excepcin, y funciona por tamo automticamente sin intervencin del programador de clientes, lo ms
comn es que nos veamos en la situacin de capturar y regi strar la excepcin generada por algn otro. por lo que es nece
sario generar el mensaje de regi stro en la propia mtina de tratami ento de excepciones:
11: exceptions/LoggingExceptions2.java
/1 Registro de l as excepciones capturadas .
import java .util.logging.*
import java.io. *
public class LoggingExceptions2
private static Logger logger =
Logger.getLogger(ULoggingExceptions2") ;
static void logException (Except i on e) {
StringWriter trace = new StringWriter()
e.printStackTrace{new PrintWriter(trace));
logger.severe(trace.toString()) ;
public static void main(String(] args)
try {
throw new NullPointerException();
catch (NullPointerException e) {
logException(e) ;
1* Output: (90% match)
Aug 30, 2005 4:07:54 PM LoggingExceptions2 logException
SEVERE: java.lang .NullPointerException
at LoggingExceptions2.main{LoggingExceptions2.java:16)
* ///,-
El proceso de creacin de nuestras propias excepciones puede ll evarse un paso ms all . Podemos ai'ladir constructores y
mi embros adi cionales:
11: exceptions/ExtraFeatures.java
JI Mejora de las clases de excepcin.
import static net.mindview.util.Print.*;
class MyException2 extends Exception {
private int x
public MyException2 () {)
public MyException2 (String msg) { super (msg)
public MyException2 {String msg, int x l {
super (msg) ;
12 Tratamiento de errores mediante excepciones 285
this.x = Xi
public int val () { return Xi
public String getMessage () {
return "Detail Message: "+ X + " "+ super.getMessage();
public class ExtraFeacures {
public static void f(} throws MyException2
print ("Throwi ng MyException2 from f () ,,) i
throw new MyException2() i
public static void g() throws MyException2 {
print ("Throwing MyException2 from 9 () ") i
chrow new MyExcepcion2 ("Originated in 9 () ") i
public static void h{) throws MyException2 {
print ("Throwing MyException2 from h () ") i
throw new MyException2 ( "Originated in h (l ", 4 7) ;
public static void main (String [] args) {
try {
f () ;
catch (MyException2 e) {
e.printStackTrace(System.out) i
try
g();
}
catch (MyException2 el {
e.printStackTrace(System.out) i
try {
h();
catch (MyException2 el {
e.printStackTrace(System.out) i
System.out.println{ue.valO = " + e.val( i
/* Output:
Throwing MyException2 from f()
MyException2: Detail Message: O null
at ExtraFeatures.f(ExtraFeatures.java:22)
at ExtraFeatures.main(ExtraFeatures.java:34)
Throwing MyException2 from g()
MyException2 : Detail Message: O Originated in g()
at ExtraFeatures.g(ExtraFeatures.java:26l
at ExtraFeatures.main(ExtraFeatures.java:39)
Throwing MyException2 from h()
MyException2: Detail Message: 47 Originated in h()
at ExtraFeatures.h(ExtraFeatures.java:30l
at ExtraFeatures.main(ExtraFeatures.java:44)
e. val () = 47
*///,-
Se ha aadido un campo x junto con un mlOdo que lee di cho valor y un constructor adicional que lo iniciali za. Adems,
Throwable.getMessagc( ) ha sido sustituido para generar un mensaje de detalle ms interesante. getMcssage() es un mto-
do simi lar a toString() que se utiliza para las clases de excepcin.
286 Piensa en Java
Puesto que una excepcin no es ms que otro tipo de obj eto, podemos continuar este proceso de mejora de nuestras clases
de extensin. Recuerde, sin embargo. que todo este trabajo adicional puede no servir para nada en los programas de
te que utili cen nuestros paquetes, ya que dichos programas puede que simplemente miren cul es la excepcin que se ha
generado y nada ms (sa es la forma en la que se utili zan la mayora de las excepciones de la bibli oteca Java).
Ejercicio 6:
Ejercicio 7:
( 1) Cree dos clases de excepcin, cada una de las cuales realice su propia tarea de registro
te. Demuestre que dichas clases funcionan.
(1) Modifique el Ejercicio 3 para que la clusula ealeh registre los resultados.
La especificacin de la excepcin
En Java, debemos tratar de informar al programador de clientes, que invoque nuestros mtodos, acerca de las excepciones
que puedan ser generadas por cada mtodo. Esto resulta conveni ente porque quien realiza la invocacin puede saber as
exactamente qu cdigo necesita escribir para caphlrar todas las excepciones potenciales. Por supuesto, si el cdigo fuente
est disponibl e, el programador de clientes podra examinarlo y buscar las instrucciones throw, pero puede que una biblio-
teca se di stribuya sin el correspondiente cdigo fuente. Para evitar que esto constituya un problema, Java proporciona una
si ntaxis (y nos obliga a usar esa sintaxis) para permitirnos informar al programador de clientes acerca de cul es son las
excepciones que el mtodo genera, de modo que el programador pueda tratarlas. Se trata de la especificacin de excepcio-
nes que forma parte de la declaracin del mtodo y que aparece despus de la li sta de argumentos.
La especificacin de excepciones utili za una palabra clave adicional , throws, seguida de todos los tipos potencial es de
excepciones. Por tanto, la definicin de un mtodo podra tener el aspecto siguiente:
void f() throws TooBig. TooSmall, DivZero { jj ...
Sin embargo, si decimos
void f () { 11
signifi ca que el mtodo no genera ninguna excepcin (salvo por las excepciones heredadas de RuntimeException, que
den generarse en cualquier lugar sin necesidad de que exista una especi ficaci n de excepciones; habl aremos de estas
ciones ms adelante) .
No podemos proporcionar informacin falsa acerca de la especificacin de excepciones. Si el cdigo dentro del mtodo pro-
voca excepciones y ese mtodo no las trata, el compilador lo detectar y nos infonnar de que debemos tratar la excepcin
o indicar, mediante una especificacin de excepcin, que dicha excepcin puede ser devuelta por el mtodo. Al imponer que
se utilicen especificaciones de excepcin en todos los lugares, Java garanti za en tiempo de compilacin que exista una cier-
ta correccin en la definicin de las excepciones.
Slo hay una manera de que podamos proporcionar infonnacin falsa: podemos declarar que generamos una excepcin que
en realidad no vayamos a generar. El compilador aceptar lo que le digamos y forzar a los usuarios de nuestro mtodo a
tratar esa excepcin como si realmente fuera a ser generada. La nica ventaja de hacer esto es di sponer por adelantado de
una forma de aadi r posteriormente la excepcin; si ms adelante decidimos comenzar a generar esa excepcin, no tendre-
mos que real izar cambios en el cdigo existente. Tambin resulta importante esta caractersti ca para crear interfaces y cIa-
ses bases abstractas cuyas clases derivadas o impl ementaciones puedan necesitar generar excepciones.
Las excepciones que se comprueban y se imponen en tiempo de compilacin se denominan excepciones comprobadas.
Ejercicio 8: (1) Escriba una clase con un mtodo que genere una excepcin del tipo creado en el Ejercicio 4. Trate de
compilarlo si n incluir una especificacin de excepcin para ver qu es lo que di ce el compilador. Aada
la especificacin de excepcin apropiada. Pruebe la clase y su correspondiente excepcin dentro de una
clusula try-ealeh.
Cmo capturar una excepcin
Resulta posible crear una rutina de tratamiento que capture cualquier tipo de excepcin. Para hacer esto, podemos capturar
el tipo de excepcin de la clase base Exception (existen otros tipo de excepciones base, pero Exception es la base que resul-
ta apropiada para casi todas las acti vidades de programacin):
c atch ( Exception e ) (
System.out.println ( "Caught an exception");
12 Tratamiento de errores mediante excepciones 287
Esto pennitir capturar cualquier excepcin, por lo que si usamos este mecanismo convendr ponerlo al final de la lista de
rutinas de tratamiento, con el fin de evi tar que se impida entrar en accin a otras rutinas de tratamiento de excepciones que
puedan estar situadas a continuacin.
puesto que la clase Exception es la base de todas las clases de excepcin que tienen importancia para el programador, no
vamos a obtener demasiada infonnacin especfica acerca de la excepcin concreta que hayamos capturado, pero podemos
invocar los mtodos incluidos en su lipo base Throwable:
String getMessage()
String gctLocalizedMessage()
Obtiene el mensaje de detalle o un mensaje ajustado para la configuracin de idioma uti lizada.
String toString()
Devuelve una descripcin corta del objeto Throwable, incluyendo el mensaje de detalle, si es que existe uno.
voi d printStackTrace( )
void printStackTracc(PrintStream)
void pri ntStackTraceGava.io.PrintWriter)
Imprime el objeto Throwable y la traza de pila de llamadas de dicho objeto. La pila de llamadas muestra la secuencia de
ll amadas a mtodos que nos han conducido hasta el lugar donde la excepcin fue generada. La primera versin imprime en
la salida de error estndar, mientras que la segunda y la tercera imprimen en cualquier salida que elijamos (en el Capitulo
18, E/S, veremos por qu existen dos tipos de !lujos de salida).
Throwable filllnStackTrace()
Registra infomlacin dentro de este objeto Throwable acerca del estado actual de los marcos de la pila. Resulta til cuan-
do una aplicacin est volviendo a generar un error o una excepcin (hablaremos de esto en breve).
Adems, tambin tenemos acceso a otros mtodos del tipo base de Throwablc que es Object (que es el tipo base de todos
los objetos). El que ms til puede resultar para las excepciones es getClass(), que devuelve un objeto que representa la
clase de este objeto. A su vez, podemos consultar este objeto Class para obtener su nombre mediante getNarnc(), que inclu-
ye infonnacin o getSimpleName(), que slo devuelve el nombre de la clase.
He aqui un ejemplo que muestra el uso de los mtodos bsicos de Exception:
// : exceptions / ExceptionMethods.java
/1 Ilustracin de los mtodos de Exception.
import static net.mindview.util.Print.*;
public class ExceptionMethods {
public static void main {String[] args ) {
try {
throw new Exception ( "My Exceptio n" ) ;
catch (Exception e) {
print ("Caught Exception");
print ( "getMessage ( ) :" + e. getMessage ( ) ) ;
print ( "getLocalizedMessage () : 11 +
e.getLocalizedMessage( )) ;
print ("toString () :" + e ) ;
print ( "printStackTrace () : " ) ;
e.printStackTrace(System.out) ;
1* Output:
Caught Exception
g etMessage() :My Exception
getLocalizedMessage () : My Exception
toString() :java.lang.Exception: My Exception
288 Piensa en Java
printStackTrace() :
java . lang.Exception: My Exception
at ExceptionMethods.main(ExceptionMethods.java:8)
Podemos ver que los mtodos proporcionan sucesivamente ms infomlacin. de hecho, cada uno de ellos es un supercon_
junto del anterior.
Ejercicio 9: (2) Cree tres nuevos tipos de excepciones. Escriba una clase con un mtodo que genera las tres. En
main(), llame al mtodo pero utilice una nica clusula catch que penllita capturar los tres tipos de excep_
ciones.
la traza de la pila
Tambin puede accederse directamente a la informacin proporcionada por printStackTrace() , utilizando
getStackTrace() . Este mtodo devuelve una matriz de elementos de traza de la pila, cada uno de los cuales representa un
marco de pila. El elemento cero es la parte superior de la pila y se corresponde con la ltima invocac in de mtodo de la
secuencia (el punto en que este objeto Throwable fue generado). El ltimo elemento de la matriz (la parte inferior de la
pila) es la primera invocacin de mtodo de la secuencia. Este programa proporciona una ilustracin simple:
// : exceptions/WhoCalled.java
// Acceso mediante programa a la informacin de traza de la pila.
public class WhoCalled {
static void f () {
f
/1 Generar una excepcin para rellenar la traza de pila.
try {
throw new Exception();
catch (Exception e) {
for(StackTraceElement ste : e.getStackTrace())
System. out.println{ste.getMethodName{)) ;
static void g() { fl); )
static void h () { g (); )
public static void main (String [] args) {
f();
System. out .println ( " - - -- - - - - - --- - -- - - - - -- - - - - - - - - - - -" ) ;
g();
System.out.println("--------------------------------") ;
h();
/ * Output:
main
f
g
main
Aqu, nos limitamos a Impnmir el nombre del mtodo, pero tambin podramos imprimir el objeto completo
StackTraceElement , que cont iene informacin adiciona l.
12 Tratamiento de errores mediante excepciones 289
Regeneracin de una excepcin
En ocas iones. conviene regenerar una excepcin que hayamos capturado, particularmente cuando se utiliza Exception para
capturar todas las excepciones. Puesto que ya di sponemos de la referencia a la excepcin actual , podemos simplemente vol-
ver a generar dicha referencia :
cacch(Exception e) {
System. out . println(IIAn exception was thrown
lt
);
throw e;
La regeneracin de una excepcin hace que sta pase a las rutinas de tratamiento de excepciones del siguiente contexto de
ni vel superior. Las clusulas catch adicional es incluidas en el mi smo bloque try seguirn ignorndose. Adems, se preser-
va toda la infonnacin acerca del objeto de excepcin, por lo que la nnina de tratamiento en el contexto de ni vel superior
que capnlre ese tipo excepcin especfico podr extraer toda la informacin de dicho objeto.
Si nos limitamos a regenerar la excepcin actual. la informacin que imprimamos acerca de dicha excepcin en
pri ntStackTrace() ser la relativa al origen de la excepcin, no la relati va al lugar donde la hayamos regenerado. Si que-
remos insertar nueva infonnacin de traza de la pila podemos hacerlo invocando fillInStackTrace(), que devuelve un obje-
to Throwable que habr generado insertando la infonnacin de pila actual en el antiguo objeto de excepcin. He aqu un
ejemplo:
jj : exceptions/Ret hr owing . java
11 Ilustracin de fil l InStackTra ce()
publ ic class Rethrowing {
public s t atic voi d f{) t hrows Excep tion {
System. out . println( lI ori ginating the exception i n f() " );
throw new Exception ( " t hrown from f () " ) i
public static void g() throws Exception {
try {
f II ;
catch (Exc eption e l {
System. out . print ln (" Inside 9 () ,e . printStackTrace () ") ;
e . printStackTr ace(System.out ) ;
throw e
public static void h() throws Exception {
try {
fll;
catch(Exception e)
System. out. println (" Inside h () , e . printStackTrace () ") ;
e . printStackTrace(System.out)
throw (Excepti on) e. f i ll I nStackTrace();
public static void ma i n (String [] argsl {
try {
gil;
cat ch(Exception e)
Sys t em. out . println (IImain: printStackTrace () 11) i
e . printStackTrace(System. outl;
try
hll;
catch(Exception el
Sys t em.out . println(lImain : printStackTrace() 11) i
e . printStackTrace(System. out) ;
290 Piensa en Java
/ * Output:
originating the exception in f ()
Inside g {) ,e.printStackTrace ()
j ava.lang.Exception: thrown froID f ()
at Rethrowing.f {Rethrowing.java:7 )
at Rethrowing.g (Rethrowing.java:ll )
at Rethrowing.main(Rethrowing.java:29)
main: printStackTrace ()
java .1an9. Exception: thrown froID f ()
at Rethrowing.f (Rethrowing.java:7 )
at Rethrowing.g (Rethrowing . java:ll l
at Rethrowing.main (Rethrowing. java:29)
originating che exception in f ()
Inside h () ,e.printStackTrace ()
java.lang.Exception : thrown froro f ()
at Rethrowing.f (Rethrowing.java:7 )
at Rechrowing.h (Rethrowing.java:2 0)
at Rethrowing.main(Rethrowing.java:35 )
maln: printStackTrace {)
java.lang.Exception: thrown fram f ()
at Rethrowing.h(Rethrowing.java:24)
at Rethrowing.main(Rethrowing.java:35 )
La lnea donde se invoca filllnStackTrace() ser el nuevo punto de origen de la excepcin.
Tambin resulta posible volver a generar una excepcin di stinta de aquella que hayamos capturado. Si hacemos esto, obte
nemos un efecto similar a cuando usamos filllnStackTrace() : la infonnacin acerca del lugar de origen de la excepcin se
pierde, y lo que nos queda es la informacin correspondiente a la nueva instruccin throw:
/1 : exceptions / RethrowNew.java
/1 Regeneracin de un objeto distinto de aqul que fue capturado.
class OneException extends Exception {
public OneException (String s ) { super (s ) i
c lass TwoException extends Exception {
public TwoException(String s ) { super (s} i
public class RethrowNew {
public static void f( ) throws OneException {
System. out. println ( ltoriginating the exception in f () " ) ;
throw new OneException ( " thrown from f () " ) i
public static void main(String [] args ) {
try {
try (
f () ;
catch (OneException e ) {
System.out.println(
"Caught in inner try, e. printStackTrace ( ) " ) ;
e . printStackTrace(System.out) i
throw new TwoException ( .. from inner try");
catch(TwoException e)
System.out .println (
12 Tratamiento de errores mediante excepciones 291
"Caught in outer try, e.printStackTrace() ");
e.printStackTrace(System.out l ;
1* Output:
originating the exception in f()
Caught in inner try, e.printStackTrace()
OneException : thrown from f()
at RethrowNew. f {RethrowNew.java: 15)
at RethrowNew. main {RethrowNew. java: 20)
Caught in outer try, e.printStackTrace()
TwoException: fram inner try
at RethrowNew. main {RethrowNew. java: 25)
* /// ,-
La excepcin final slo sabe que proviene del bloque try interno y no de f().
Nunca hay que preocuparse acerca de borrar la excepcin anterior, ni ningw1a otra excepcin. Se trata de objetos basados
en el cmulo de memoria que se crean con new, por lo que el depurador de memoria se encargar automticamente de
borrarlos.
Encadenamiento de excepciones
A menudo, nos interesa capturar una excepcin y generar otra, pero manteniendo la informacin acerca de la excepcin de
origen; este procedimi ento se denomina encadenamiento de excepciones. Antes de la aparicin del JDK 1.4, los programa-
dores tenan que escribir su propio cdigo para preservar la informacin de excepcin original, pero ahora todas las subcla-
ses de Throwable tienen la opcin de admitir un objeto causa dentro de su constnlctor. Lo que se pretende es que la causa
represente la excepcin original, y al pasarla lo que hacemos es mantener la traza de la pil a correspondiente al origen de la
excepcin, incluso aunque estemos generando una nueva excepcin.
Resulta interesante observar que las ni cas subclases de Throwable que proporcionan el argumento causa en el constnlC-
tor son las tres clases de excepcin fundamental es Error (utilizada por la mquina virtual JVM para informar acerca de los
errores del sistema), Exception y RuntimeException. Si queremos encadenar cualquier otro tipo de excepcin, tenemos
que hacerlo utilizando el mtodo initCause( ), en lugar del constructor.
He aqu un ejemplo que nos pennite aadir dinmicamente campos a un objeto DynamicFields en tiempo de ejecucin:
11 : exceptions/DynamicFields.java
II Una clase que aade dinmicamente campos a s misma.
II Ilustra el mecanismo de encadenamiento de excepciones.
import static net.mindview.util.Print.*
class DynamicFieldsException extends Exception {}
public class DynamicFields (
private Object [] [] fields
public DynamicFields(int initialSize)
fields ::: new Object [initialSizel [2]
for{int i ::: O; i < initialSize i++)
fields[i] = new Object[] { null, null };
public String toString () {
StringBuilder result = new StringBui lder {);
for(Object[] obj , fields) {
resul t . append (obj [O] ) ;
resul t . append ( ": 11 );
resul t. append (obj [1] ) ;
result.append ( U\n
U
) ;
return result . toString()
292 Piensa en Java
private int hasField (String id) {
far ( int i = O; i < fields.length; i++ )
if (id.equa1s ( fie1ds[iJ [OJ ))
return i
return -1;
private int
getFieldNumber(String id) throws NoSuchFieldException {
int fieldNum = hasField {id) i
if ( fieldNum == - 1 )
throw new NoSuchFieldException () ;
return fieldNuID;
private int makeFie1d (String id) {
far(int i = O; i < fields.length; i++l
if (fie1ds [iJ [OJ == null) {
fie1ds [iJ [OJ = id;
return i;
// No hay campos vacios. Aadir uno :
Object[J[J tmp = new Object[fie1ds.1ength + 1J[2J;
far {int i = O; i < fields.length; i++l
tmp [iJ = fie1ds [iJ ;
far(int i = fields.length; i < tmp.length; i ++ }
tmp[iJ = new Object[J { null, null j;
fields = tmpi
// Llamada recursiva con campos expandidos:
return makeField (id) ;
public Object
getField(String id) throws NoSuchFieldException
return fields [getFieldNumber(id)] [1];
public Object setField (String id, Object value)
throws DynamicFieldsException (
if(va1ue == nu11 ) {
J J La mayora de las excepciones no tienen un constructor con "causa".
JI En estos casos es necesario emplear initCause () ,
JI disponible en todas las subclase de Throwable.
DynamicFieldsException dfe =
new DynamicFieldsException( ) i
dfe.initCause(new NullPointerException( )}
throw dfe
int fieldNumber = hasField (id)
if (fieldNumber == -1 )
fieldNumber makeField ( id} i
Object result = null
try {
result = getField(id) i IJ Obtener valor anterior
catch (NoSuchFieldException e) {
J J Utilizar constructor que admite la "causa":
throw new RuntimeException(e) i
fields [fieldNumber] [1] = value
return result
public static void main(String[] argsJ
DynamicFields df
print (df ) ;
try {
new DynamicFields (3) ;
df.setField {"d", "A value ter d" ) i
df.setField ( nnumber", 47 ) ;
df. setField ( "number2 " , 48 ) i
print (df ) ;
df . setField ( "d", "A new value fer d" ) ;
df. setField ( "number3" , 11 ) ;
12 Tratamiento de errores mediante excepciones 293
print ( lIdf: " + df ) ;
print ( "df.getField {\ "d\ U) u +
Object field = df.setField ( "d",
catch (NeSuchFieldException e l {
e.printStackTrace (System. out ) i
catch (DynamicFieldsException e )
e.printStackTrace (System.out) i
df.getField ( "d" )) ;
null ) ; II Excepcin
1* Output:
null: null
null: null
null: null
d: A value for d
number: 47
number2: 48
df: d: A new value for d
number: 47
number2: 48
number3: 11
df.getField ( "d" l : A new value tor d
DynamicFieldsException
at DynamicFields.setField (DynamicFields.java:64 )
at DynamicFields.main (DynamicFields.java:94 l
Caused by: java.lang . NullPointerException
* /!/ , -
at DynamicFields.setField (DynamicFields . java:66 )
... 1 more
Cada objeto DynamicFiclds cont iene una matriz de parejas Object-Object. El primer objeto es el identificador del campo
(de tipo String), mientras que el segundo es el valor del campo, que puede ser de cualquier tipo, salvo una primiti va no
envuelta en otro tipo de objeto. Cuando se crea el objeto, tratamos de adivinar cuntos campos vamos a necesitar. Cuando
invocamos setField(), dicho mtodo locali za el campo existente que tenga dicho nombre o crea un nuevo campo, colocan-
do a continuacin el valor en l. Si se queda sin espacio, se aade nuevo espacio creando una matriz de longitud igual a la
anterior ms uno y copiando en ella los antiguos elementos. Si tratamos de almacenar un valor null , se genera una excep-
cin DynamicFieldsException creando una excepcin y utili zando initCause() para insertar una excepcin
NullPointerException como la causa.
Como valor de retomo, setField() tambin extrae el antiguo valor situado en dicbo campo utili zando getField( ), que podra
generar la excepcin NoSuchFieldException (no existe un campo con dicho nombre). Si el programador de cli entes invo-
ca getField(), entonces ser responsable de tratar la excepcin NoSuchFieldException, pero si esta excepcin se genera
dentro de setField(), se tratar de un error de programacin, por lo que NoSuchFieldException se convierte en una excep-
cin RuntirneException utili zando el constructor que admite un argwnento de causa.
Como podr obervar, toString() utiliza un objeto StringBullder para crear su resultado. Hablaremos ms en detalle acer-
ca de StringBuilder en el Captulo 13, Cadenas de caracreres, pero en general conviene utilizar este tipo de objetos cada
vez que estemos escribiendo un mtodo toString() que implique la utili zacin de bucles como es el caso aqu.
294 Piensa en Java
Ejercicio 10: (2) Cree una clase con dos mtodos. f( 1 y g( l. En g( l. genere una excepcin de un nuevo tipo det,nido
por el usuario. En f(). invoque a g(), capture su excepcin y, en la clusula catch. genere una excepcin
diferente (de un segundo tipo tambin definido por el usuario). Compruebe el cdigo en main( ).
Ejercicio 11: (1) Repita el ejercicio anterior, pero dentro de la clusula catch, envuelva la excepcin g() dentro de una
excepcin RuntimeException.
Excepciones estndar de Java
La clase Java Throwable describc todas las cosas que puedan generarse como una excepcin. Existen dos tipos generales
de objetos Throwable ("tipos de "= "que heredan de"). Error representa los errores de tiempo de compilacin y del siste-
ma de los que no tencmos que preocupamos de capturar (salvo en casos muy especiales). Exception es el tipo bsico que
puede generarse desde cualquiera de los mtodos de la biblioteca estndar de Java. as como desde nuestros propios mto-
dos y tambin cuando sc producen enores de ejecucin. Por tanto. el tipo base que ms interesa a los programadores de Ja\'a
es usualmente Exception.
La mejor fonna de obtener una panormica de las excepciones consiste en examinar la documentacin dcl JDK. Conviene
hacer esto al menos una vez para tencr una idea de las distintas excepciones, aunque si lo hace se dar cuenta pronto de que
no existen diferencias muy grandes entre una excepcin y otra, salvo en lo que se refiere al nombre. Asimismo, el nmero
de excepciones en Java continua creciendo; es por ello que resulta absurdo tratar de imprimirlas todas en un libro. Cualquier
nueva biblioteca que obtengamos de un fabricante de software dispondr probablemente, asimismo, de sus propias excep-
ciones. Lo importante es comprender el concepto y qu es lo que debe hacerse con las excepciones.
La idea bsica es que el nombre de la excepcin represente el problema que ha tenido lugar y que ese nombre de excepcin
trate de ser autoexplicativo. No todas las excepciones estn definidas en java.lang; algunas se defmen para dar soporte a
otras bibliotecas como util . Det e io, lo cual pueue deducirse a partir del nombre completo de la clase correspondiente o de
la clase de la que heredan. Por ejemplo. todas las excepciones de E/S beredan de java.io.lOException.
Caso especial: RuntimeException
El primer ejemplo de este captulo era:
if (t == nulll
throw new NullPointerException{) ;
Puede resultar un poco atenador pensar que debemos comprobar si todas las referencias que se pasan a un mtodo son igua-
les a ouU (dado que no podemos saber de antemano si el que ha realizado la invocacin nos ha pasado una referencia vli-
da). Afortunadamente, no es necesario realizar esa comprobacin manualmente; dicha comprobacin forma parte del
sistema de comprobacin estndar en tiempo de ejecucin que Java aplica automticamente. y si se realiza cualquier llama-
da a una referencia null , Java generar automticamente la excepcin NullPointerException. Por tanto, el anterior frag-
mento de cdigo resulta siempre superfluo, aunque si que puede resultar interesante realizar otras comprobaciones para
protegerse frente a la aparicin de una excepcin NuLlPointerException.
Existe un conjunto completo de tipos de excepcin que cae dentro de esta categora. Se trata de excepciones que siempre
son generadas de fomla automtica por Java y que no es necesario incluir en las especificaciones de excepciones.
Afortunadamente, todas estas excepciones estn agrupadas. dependiendo todas ellas de una nica clase base denominada
RuntimeException, que constituye un ejemplo perfecto de herencia: establece una familia de tipos que tienen detem1na-
das caractersticas y comportamientos en comn. Asimismo, nunca es necesario escribir una especificacin de excepcin
que diga que un mtodo puede generar RuntimeException (o cualquier tipo heredado de RuntimeException), porque se
trata de excepciones no comprobadas. Puesto que estas excepciones indican errores de programacin, nonnalmente no se
suele capnlrar una excepcin RuntimeException. sino que el sistema las trata automticamente. Si nos viramos obligados
a comprobar la aparicin de este tipo de excepciones, el cdigo sera enormemente lioso. Pero. aunque nonnalmenre no
vamos a capturar excepciones RuntimeException. s que podemos generar este tipo de excepciones en nuestros propios
paquetes.
Qu sucede cuando no capturamos estas excepciones? Puesto que el compilador no obliga a incluir especificaciones de
excepcin para estas excepciones, resulta bastante posible que lila excepcin RuntimeException ascienda por toda la jerar-
12 Tratamiento de errores mediante excepciones 295
qua de mtodos sin ser captllrada. hasta llegar al mtodo maine ). Para ver lo que sucede en este caso, trale de ejecutar el
siguient e ejemplo:
11 : exceptions / NeverCaught . java
II Lo que sucede al ignorar una excepcin RuntimeException .
11 {ThrowsException}
public class NeverCaught {
static void f () {
throw new RuntimeException( "From f () ti) i
static void 9 () {
f I I ;
public static void main (String[] args ) {
g il ;
Como puede ver, RuntimeException (o cualqui er cosa que herede de ella) es un caso especial. ya que el compilador no
requiere que incluyamos una especificacin de excepcin para estos tipos. La salida se enva a System.crr:
Exception in thread "main" java .lang. RuntimeException: From f ()
at NeverCaught.f{NeverCaught.java:7)
at NeverCaught.g(NeverCaught.java:lOl
at NeverCaught.main(NeverCaught.java:13)
Por tanto, la respuesta es: si una excepcin RuntimeException llega hasta main() sin ser capturada. se invoca
printStackTracc() para dicha excepcin en el momento de salir del programa.
Recuerde que slo las excepciones de tipo RuntimeException (y sus subclases) pueden ser ignoradas en nuestros progra-
mas, ya que el compilador obliga exhaustivamente a tratar todas las excepciones comprobadas. El razonamiento que expli-
ca esta fonna de actuar es que RuntimeException representa un error de programacin, que es:
1. Un error que no podemos anticipar. Por ejemplo, una referencia oull que escapa a nuestro contTo!.
2. Un error que nosotros, como programadores, deberamos haber comprobado en nuestro cdigo (como por ejem-
plo una excepcin ArraylndexOutOfBoundsExccption, que indica que deberamos haber prestado atencin al
tamao de una matriz). Una excepcin que tiene lugar como consecuencia del punto l suele convertirse en un
problema del tipo especificado en el punto 2.
Como puede ver, resulta enonnemente beneficioso disponer de excepciones en este caso, ya que nos ayudan en el proceso
de depuracin.
Es interesante observar que el mecanismo de tratamiento de excepciones de Java no tiene un nico objetivo. Por supuesto,
est di seado para tratar esos molestos errores de ejecucin que tienen lugar debido a la accin de fuerzas que escapan al
control de nuestro cdigo, pero tambin resulta esencial para ciertos tipos de errores de programacin que el compilador no
puede detectar.
Ejercicio 12: (3) Modifique innerclasses/Sequence.java para que genere una excepcin apropiada si tratamos de intro-
ducir demasiados elementos.
Realizacin de tareas de limpieza con finally
A menudo. existe algn fragmento de cdigo que nos gustara ejecutar independientemente de si la excepcin ha sido gene-
rada dentro de un bloque try. Usualmente, ese fragmento de cdigo se relaciona con alguna operacin di stinta de la de recu-
peracin de memoria (ya que esta operacin es realizada automticamente por el depurador de memoria). Para conseguir
este efecto, utili zamos una clusula finally4 despus de todas las rutinas de tratamiento de excepciones. La estructura com-
pl eta de una seccin de tratamiento de excepciones sera, por tanto:
" El mecanismo de tratamiento de excepciones de e++ no dispone de la clusula finally, porque depende de la utilizacin de destructores para llevar a
cabo estas tarcas de limpieza.
296 Piensa en Java
try {
// La regin protegida: actividades peligrosas
// que pueden generar A, B o e
catch lA al) {
11
Rutina de tratamiento para la situacin A
catch (B b1) {
11
Rutina de tratamiento para la situacin B
catch (C el) {
11
Rutina de tratamiento para la situacin e
final l y {
11
Actividades que tienen lugar en todas las ocasiones
Para demostrar que la clusula finaLl y siempre se ejecuta, pruebe a ejecutar este programa:
//: exceptions/FinallyWorks.java
II La clusula finally siempre se ejecuta.
class ThreeException extends Exception {}
public class FinallyWorks {
static int count = Di
public static void main(String[] args) {
while(true)
try {
/1 El post-incremento es cero la primera vez:
if(count++ == Ql
throw new ThreeException () i
System.out.println(tlNo exception
n
) ;
catch (ThreeException e l {
System. out .println ( "ThreeException
ll
) ;
finally {
System.out.println(nIn finally clause
n
);
if (count == 2) break; II fuera del bucle "while"
1* Output:
ThreeException
In finally clause
No exception
In finally clause
*111,-
Analizando la salida, podemos ver que la clusula fi nall y se ejecuta se haya generado o no una excepcin.
Este programa tambin nos indica cmo podemos tratar con el hecho de que las excepciones en Java no nos penniten con-
tinuar con la ejecucin a partir del punto donde se gener la excepcin, como ya hemos indicado anteriormente. Si inclui-
mos nuestro bloque t ry en un bucle, podremos establecer una condicin que habr que sati sfacer antes de continuar con el
programa. Tambin podemos aadir un contador esttico o algn otro tipo de elemento para pennitir que el bucle pruebe
con varias tcnicas diferentes antes de darse por vencido. De esta forma, podemos proporcionar un mayor nivel de robustez
a nuestros programas.
Para qu sirve finally?
En un lenguaje que no tenga depuracin de memoria y que no tenga llamadas automticas a destructores,5 la clusula finally
es importante porque pem1ite al programador garantizar que se libera la memoria, independientemente de lo que suceda en
S Un destructor es una funcin que siempre se invoca cuando un objeto deja de ser utilizado. Siempre sabemos exactamente dnde y cundo se invoca al
destructor. C++ dispone dc llamadas automticas a destnLctores, mientras que e#, que se parece ms a Java, dispone de un mecanismo que hace p o ~ i l e
que tenga lugar la destruccin automtica.
12 Tratamiento de errores mediante excepciones 297
el bloque try. Pero Java dispone de un depurador de memoria, por lo que la liberacin de memoria casi nunca es un proble-
ma. Asimi smo, no di spone de ningn destructor al que invocar. Por tanto, cundo es necesario utili zar finally en Java?
La clusula finally es necesaria cuando tenernos que restaurar a su estado original alguna afro cosa di stinta de la propia
memoria. Se trata de algn tipo de tarea de limpieza que se encargue, por ejemplo, de cerrar un archivo abierto o una cone-
xin de red, de borrar algo que hayamos dibujado en la pantalla o incluso de accionar un conmutador en el mundo exterior,
tal como se ilustra en el siguiente ejemplo:
/1 : exceptions/Switch.java
import static net.mindview util . Print.*
public class Switch {
private boolean state = false;
public boolean read () { return state; }
public void on () ( state = true print (this ) ;
public void off () ( state = false; print(this ) ;
public String toString () { return state ? "on" : "off";
/// ,-
/1 : exceptions / OnOffExcept ionl.java
public class OnOffExcept i onl extends Exception {} 111:-
1/ : exceptions/OnOffExcept ion2.java
public class OnOffException2 extends Exception {} ///:-
JI: exceptions/OnOffSwitch . java
// Por qu usar finally?
public class OnOffSwitch {
on
private static Switch sw = new Switch () i
public static void f ()
throws OnOffExceptionl,OnOffException2 {}
public static void main (String [] args ) {
try {
sw.on () j
// Cdigo que puede generar excepciones ..
f {) ,
sw.off l) ,
catch (OnOffExceptionl e l {
System. out. println ( "OnOffExceptionl" l ;
sw.offl) ,
catch(OnOffException2 e l {
System . out. println ( "OnOffException2") ;
sw.off {) ,
1* Output:
o ff
, /// , -
Nuestro objetivo es asegurarnos de que el conmutador est cerrado cuando se complete la ejecucin de main(), por lo que
situamos sw.off() al final del bloque try y al final de cada rutina de tratamiento de excepciones. Pero es posible que se gene-
re alguna excepcin que no sea capturada aqu, en cuyo caso no se ejecutara sw.off(). Sin embargo, con finally podemos
incluir el cdigo de limpi eza del bloque try en un nico lugar:
JI : exceptionsfWithFinally . java
// Finally garantiza que se ejecuten las tareas de limpieza .
public class WithFinally
298 Piensa en Java
static Switch sw ~ new Switch();
public static void main (String [] args) {
try {
sw.on() i
// Cdigo que puede generar excepciones ...
OnOffSwitch.f() ;
catch(OnOffException1 el {
System.out.println(nOnOffException1") ;
catch(OnOffException2 e l {
System.out .println("OnOffException2") ;
finally {
sw . off();
/ * Output:
Aqu , la llamada a sw.off( ) se ha desplazado incluyndola en un nico lugar, donde se garantiza que ser reali zada indepen
dientemente de lo que suceda.
Incluso en aquellos casos en que la excepcin no es capturada en el conjunto actual de clusulas catch, finally se ejecuta
ra antes de que el mecanismo de tratamiento de excepciones contine buscando una rutina de tratamiento adecuada en el
siguiente ni vel de orden superior:
// : exceptions/AlwaysFinally.java
// Finally siempre se ejecuta .
import static net.mindview.util.Print. *
class FourException extends Exception {}
public class AlwaysFinally {
public static void main(String[] args)
print("Entering first try block" );
try {
print ( "Entering second try block" ) ;
try {
throw new FourException()
finally {
print{"finally in 2nd try block");
catch (FourException e) {
System.out.println(
"Caught FourException in 1st try block");
finally {
System.out.println("finally in 1st try block");
/ * Output :
Entering first try block
Entering second try block
finally in 2nd try block
Caught FourException in 1st try block
finally in 1st try block
* /// , -
La instruccin finally tambin se ejecutar en aquellas situaciones donde estn implicadas instrucciones break y continuc.
Observe que la clusula finally junto con las instrucciones break y continue etiquetadas elimina la necesidad de una n s ~
truccin goto en Java.
12 Tratamiento de errores mediante excepciones 299
Ejercici o 13: (2) Modifique el Ejercici o 9 a;adiendo una clusul a fin.Uy. Verifique que la cl usula finall y se ej ecuta,
incl uso cuando se genera una excepcin NulLPointcrException.
Ejercici o 14: (2) Demuestre que OnOffSwitch.java puede fallar, generando una excepci n RuntimeException dentro
del bl oque try.
Ejercicio 15: (2) Demuestre que WithFinally,java no falla, generando una excepci n RuntimeException dentro del
bl oque try.
Utilizacin de finally durante la ejecucin de la instruccin return
Puesto que ulla cl usula finall y siempre se ejecuta, resulta posible volver desde mltipl es puntos dentro de un mtodo sin
dejar por ello de garanti zar que se realicen las tareas de limpieza importantes :
JJ : e x c e ptionsJMultipleReturns . java
import s t atic net . mindview. util . Print .* ;
public class MultipleReturns {
public static void f ( int i )
print ( " Initialization that requires cleanup") ;
try {
print ( " Point 1" ) ;
i f {i == l ' return;
print (" Poi nt 2 " ) i
i f {i == 2) return ;
print (" Point 3" ) i
i f ( i == 3 ) return;
print ( "End'" i
return;
finally {
print { "Performing cleanup" } ;
public static void main (String [) args) {
for ( int i = 1; i <= 4; i++ )
f ( i l ;
1* Output:
Initialization that requires cleanup
Point 1
Performing cleanup
Initialization that requires cleanup
Po int 1
Po int 2
Performing cleanup
Initialization that requires cleanup
Po int 1
Point 2
Po int 3
Perfor mi ng cleanup
Initiali zation that requires c l eanup
Po int 1
Po int 2
Point 3
End
Performi ng cleanup
* /// , -
Podemos ver, en la salida del ejemplo, que no importa desde dnde vol vamos, ya que siempre se ejecuta la clusula finally.
300 Piensa en Java
Ejercic io 16: (2) Modifique reusing/CADSystem.java para demostrar que si volvemos desde un punto siruado en la
mitad de una estmctura try- fin all y se seguirn ejecutando adecuadamente las tareas de limpieza.
Ej e rcicio 17: (3) Modifique polymorphi sm/Frog.j ava para que utilice la estrucrura try-fi nally con el fin de garantizar
que se lleven a cabo las tareas de limpieza y demuestre que esto funciona incluso si ejecutamos una ins-
tmccin retu r n en mitad de la estructura try-finall y.
Un error: la excepcin perdida
Lamentablemente, existe un fallo en la implementacin del mecanismo de excepciones de Java. Aunque las excepciones Son
una indicacin de que se ha producido una crisis en el programa y nunca deberan ignorarse. resulta posible que una excep-
cin se pierda si n ms. Esto sucede cuando se utili za una configuracin concreta con una clusula fi nally:
11 : exceptions / LostMessage.java
II Forma de perder una excepcin.
class VerylmportantException extends Exception
public String toString () {
return nA very important exception!";
class HoHumException extends Exception
public String toString() {
return "A trivial exception" i
public class LostMessage {
void f () throws VerylmportantException
throw new VerylmportantException () ;
void dispose () throws HoHumException
throw new HoHumException () i
public static void main (String [] args ) {
try (
LostMessage 1m = new LostMessage () ;
try (
1m. f () ;
finally
lm.dispose () ;
catch (Exception e ) {
System.out.println (e ) i
1* Output:
A trivial exception
- /// ,-
Podemos ver, analizando la salida, que no existe ninguna prueba de que se haya producido la excepcin
Vcrylmport ant Except ion, que es simplemente sustituida por la excepcin HoHumException en la clusula finall y. Se
trata de un fallo imponante, puesto que implica que puede perderse completamente una excepcin, y adems puede perder-
se de una fomla bastante ms sutil y dificil de detectar que en el ejemplo anterior. Por contraste, e++ considera como un
error de programacin que se genere una segunda excepcin antes de que la primera haya sido tratada. Quiz, una futura
versin de Java solventar este problema (por otro lado, normalmente lo que haremos ser encerrar cualquier mtodo que
genere una excepcin, C01110 es el caso de dispose( ) en el ejemplo anterior, dentro de una clusula try-catch).
12 Tratamiento de errores mediante excepciones 301
Una forma todava ms simple de perder una excepcin consiste en volver con return desde dentro de una clusula finally:
11: exceptions/ExceptionSilencer.java
public class ExceptionSilencer {
public static void main(St ring[] args)
try {
throw new RuntimeException(}
f inally {
1I La utili zacin de t r eturn
t
dentro de un bloque f inally
I1 har que se pier da la excepcin g e ne rada .
return
Si ejecutamos este programa, veremos que no produce ninguna salida, a pesar de que se ha generado una excepcin.
Ejercicio 18: (3) Aada un segundo nivel de prdida de excepciones a LostMessage.java para que la propia excepcin
HoHumException sea sustituida por una tercera excepcin.
Ejercicio 19: (2) Solucione el problema de LostMessage.java protegiendo la llamada contenida en la clusula finally.
Restricciones de las excepciones
Cuando sustituimos un mtodo, slo podemos generar aquellas excepciones que hayan sido especificadas en la versin del
mtodo correspondiente a la clase base. Se trata de una restriccin muy til . ya que implica que el cdigo que funci one con
la clase base funcionar tambin automticamente con cualquier objelO derivado de la clase base (lo cual , por supuesto, es
un concepto fundamental dentro de la programacin orientada a objetos), incluyendo las excepciones.
Este ejemplo ilustra los tipos de restricciones impuestas a las excepciones (en tiempo de compilacin):
11 : e x cepti ons/Stormyl nning . java
II Los mtodos sustituidos slo puede n generar l as exc e pciones
II especificadas en sus v e rsione s de la clase base ,
II o e x cep ciones der ivadas de las exc epcione s de la clase base .
class Basebal l Except ion extends Ex ception {}
class Foul extends BaseballExce ption {}
class Strike extend s BaseballException {}
abstract c l ass Inning {
public Inning() throws BaseballException {}
public void event() throws Basebal l Exception
II No tiene por qu generar nada
public abstract void atBat{) throws Strike, Faul
publ ic void walk () {} liNo genera ninguna excepcin comprobada
class StormEx ception e x t e nds Ex c eption {}
class RainedOut e xtends StormExc ept i an {}
class PopFoul extends Foul {}
int erface Storm {
publi c vo i d e vent () throws RainedOut
public void rainHard() throws RainedOut
public class Stormyl nning extends Inning implements Storm {
302 Piensa en Java
II Se pueden aadir nuevas excepciones para los constructores,
II pero es necesario tratar con las excepciones del constructor base:
public Stormylnning()
throws RainedOut, BaseballException {}
public Stormylnning (String s)
throws Foul, BaseballException {}
II Los mtodos normales deben adaptarse a la clase base:
II! void walk() throws PopFoul {} // error de compilacin
// Una interfaz NO PUEDE aadir excepciones a los mtodos
// existentes en la clase base:
ji! public void event() throws RainedOut {}
1/ Si el mtodo no existe ya en la clase
// base, la excepcin es vlida:
public void rainHard() throws RainedOut {}
// Podemos definir no generar ninguna excepcin,
// aun cuando la versin base lo haga:
public void event I I {}
// Los mtodos sustituidos pueden generar excepciones heredadas:
public void atBat() throws PopFoul {}
public static void main{String[] args) {
try (
Stormylnning si = new Stormylnning{)
si.atBat() i
catch IPopFoul e l {
System. out . println ( ti Pop foul") i
catch (RainedOut e) {
System. out. println ( tlRained out ti) i
catch{BaseballException e) {
System. out. println ( "Generi c baseball exception")
// Strike no se genera en la versin derivada.
try (
// Qu sucede si generalizamos?
Inning i = new Stormylnning()
i.atBat() i
// Hay que capturar las excepciones de la
1/ versin del mtodo correspondiente a la clase base:
catch(Strike el {
System. out. println ( " Strike
tl
) i
catchlFoul el {
System. out. println ( " Foul")
catch (RainedOut el {
System. out _ printIn ("Rained out ") i
catch(Baseball Exception e) {
System.out .printIn ( "Generic baseball exception") i
}
///,-
En Inning, podemos ver que tanto el constructor como el mtodo event() especifican que generan una excepcin, pero que
nunca lo hacen. Esto es legal, porque nos pem1ite obligar al usuario a capturar cualquier excepcin que podamos aadir en
las versiones sustituidas de event(). La mi sma idea puede aplicarse a los mtodos abstractos como podemos ver en atBat().
La interfaz Storm es interesante porque contiene un mtodo (event()) que est definido en Inning, y otro mtodo que no
lo est. Ambos mtodos generan un nuevo tipo de excepcin, RainedOut. Cuando Stormylnning ampla (extends) Inning
e implementa (implements) Storm, podemos ver que el mtodo event() de Storm /la puede cambiar la interfaz de excep-
ciones de cvcnt () definida en Inning. De nuevo, esto tiene bastante sentido porque en caso contrario nunca sabramos si
estamos capturando el objeto correcto a la hora de trabajar con la clase base. Por supuesto, si un mtodo descrito en una
interfaz no se encuentra en la clase base, como es el caso de r ainHard(), no existe ningn problema en cuanto a las excep
ciones que genere.
12 Tratamiento de errores mediante excepciones 303
La restriccin relativa a las excepciones no se aplica a los constructores. Podemos \'cr en Stormylnning que un construc-
tor puede generar todo aquello que desee. independientemente de lo que genere el constructor de la clase base. Sin embar-
go. puesto que siempre hay que invocar el constructor de la clase base de una forma o de otra (aqu se invoca el constructor
predetenninado de manera automtica). el conslructor de la clase derivada deber declarar todas las excepciones del cons-
tructor de la clase base en su propia especificacin de excepciones.
Un constructor de la clase derivada no puede capturar las excepciones generadas por su constructor de la clase base.
La razn por la que Stormylnning.walk( ) no podr compilarse es que genera una excepcin. mientras que lnning.walk()
no lo hace. Si se pennitiera esto. entonces podramos escribir cdigo que invocara a lnning.walk() y que no tuviera que
tratar ninguna excepcin, pero entonces, cuando efecturamos una sustitucin y empleramos un objeto de una clase deri-
vada de Inning. podran generarse excepciones. con Jo que nuestro cdigo fallaria. Obligando a los mtodos de la clase deri-
vada a adaptarse a las especificaciones de excepciones de los mtodos de la clase base. se mantiene la posibilidad de sustit uir
los objetos.
El mtodo sustituido eVf'nt() muestra que la versin de un mtodo definido en la clase derivada puede elegir no generar
ninguna excepcin, incluso a pesar de que la versin de la clase s las genere. De nuevo, no pasa nada por hacer esto, ya que
no dejarn de funcionar aquellos programas que se hayan escrito bajo la suposicin de que la versin de la clase base gene-
ra excepciones. Podemos aplicar una lgica similar atBat( ), que genera PopFoul. una excepcin que deriva de la excep-
cin Foul generada por la versin de la clase base de atBat(). De esta fonna, si escribimos cdigo que funcione con Inning
y que invoque at8at( ). deberemos capturar la excepcin Fou!. Puesto que PopFoul deri va de Foul. la rutina de tratamien-
10 de excepciones tambin pennitir capturar PopFoul .
El ltimo punto de inters se encuentra en main(). Aqu, podemos ver que. si estamos tratando con un objeto que sea exac-
lamente del tipo Stormylnning, el compilador nos obl igar a capturar nicamente las excepciones que sean especficas de
esa clase, pero si efectuamos una generali zacin al tipo base. entonces el compi lador (cOlTectamente) nos obligar a captu-
rar las excepciones del tipo base. Todas estas restricciones penniten obtener un cdigo de tratamiento de excepciones mucho
ms robusto
6
.
Aunque es el compilador el que se encarga de imponer las especificaciones de excepciones en los casos de herencia, esas
especificaciones de excepciones no fomlan parte de la signatura de un mtodo. que est compuesta slo por el nombre del
mtodo y los tipos de argumentos. Por tanto, no es posible sobrecargar los mtodos basndose solamente en las especifica-
ciones de excepciones. Adems, el hecho de que exista una especificacin de excepcin en la versin de la clase base de un
mtodo no quiere decir que dicha especificacin deba existir en la versin de la clase derivada del mtodo. Esto difiere bas-
tante de las reglas nomlales de herencia, segn las cuales un mtodo de la clase base deber tambin existir en la clase deri-
vada. Dicho de otra fonna, la "interfaz de especificacin de excepciones" de un mtodo concreto puede estrecharse durante
la herencia y cuando se realizan sustituciones. pero lo que no puede es se trata, precisamente. de la regla opues-
ta a la que se aplica a la interfaz de una clase durante la herencia.
Ej erci cio 20: (3) Modifique Stormylnning.j ava aadiendo un tipo excepcin UmpireArgument y una serie de mto-
dos que generen esta excepcin. Compruebe la jerarqua modificada.
Constructores
Es importante que siempre nos hagamos la pregunta siguiente: "Si se produce una excepcin, se limpiar todo apropiada-
mente?"' La mayor parte de las veces, podemos eslar razonablemente seguros, pero con los constructores existe un proble-
ma. El constructor sit a los objetos en un estado inicial seguro, pero puede realizar alguna operacin (como por ejemplo
abrir un archivo) que no revierta hasta que el usuario termine con el objeto e invoque un mtodo de limpieza especial. Si
generamos una excepcin desde dentro de un constructor, puede que estas tareas de limpieza no se ll even a cabo apropia-
damente. Esto quiere decir que debemos tener un especial cuidado a la hora de escribir los constructores.
Podamos pensar que la clusula finally es una solucin. Pero las cosas no son tan simples. porque finaUy ll eva a cabo las
tareas de limpieza lodas las \'eces. Si un constructor fa ll a en mitad de la ejecucin, puede que no haya tenido tiempo de crear
alguna parte del objeto que ser limpiado en la clusula linally.
6 El estndar ISO e ++ ha andido unas restricciones similares. que obligan a que las excepciones de los mtodos derivados sean iguales a las excepcio-
nes por los mtodos de la clase base. o al menos a que deri\'en de ellas. ste es uno de los casos en los que C++ es capaz de comprobar las espe-
cificaciones de excepciones en tiempo de compIlacin.
304 Piensa en Java
En el sigui ente ejemplo. se crea una clase denominada InputFile que abre un archivo y pemlite leerlo lnea a lnea. Utiliza
las clases FileReader y BufferedR der de la biblioteca estndar E/ S de Java que se analizar en el Captulo 18, E/S Estas
clases son lo suficientemente simples como para que el lector no tenga ningn problema a la hora de comprender los fun_
damentos de su utilizacin:
11: exceptions/lnputFile.java
II Hay que prestar a las excepciones en los constructores.
import java . io.*;
public class InputFile
private BufferedReader in;
public InputFile(String fname) throws Exception {
try (
in = new BufferedReader (new FileReader(fname;
II Otro cdigo que pueda generar excepciones
catch(FileNotFoundException el {
System.out.println("Could not open " + fname)
II No estar abierto, por lo que no hay que cerrarlo.
throw e
catch(Exception e l
II Todas las dems excepciones deben cerrarlo
try {
in. close () ;
catch (IOException e2) {
System.out .println(lIin.close() unsuccessfuI
U
);
throw e II Regenerar
finally (
II ji No cerrarlo aqu!!!
public String getLine () {
String Si
try (
s = in.readLine()
catch(IOException el
throw new RuntimeException("readLine() failed
ll
);
return s;
public void dispose()
try (
in.close ();
System.out .println ("dispose() successful");
catch (IOException e2) {
throw new RuntimeException (" in. clase () failed 11) ;
El constructor de InputFile toma un argumento String, que representa el nombre del archivo que queremos abrir. Dentro
de un bloque Iry, crea un objeto FileReader utili zando el nombre del archivo. Un objeto "ileReader no resulta particular-
mente til hasta que lo empleemos para crear otro objeto BufferedReader (para lectura con buffer). Uno de los beneficios
de InputFile es que combina las dos acciones.
Si el constnlctor de FileReader falla, generar una excepcin FileNolFoundException, que indicar que no se ha encon-
trado el archivo. ste es el nico caso en el cual no querernos cerrar el archivo, ya que no hemos llegado a poder abrirlo.
Cualquier aIra clusula catch deber cerrar el archivo, porque estar abierto en el momento de entrar en dicha clusula
catch (por supuesto, el asunto se complica si hay ms de un mtodo que pueda generar una excepcin
FileNolFoundExceplion. En dicho caso, normalmente, habr que descomponer las cosas en varios bloques Iry). El rnto-
12 Tratamiento de errores mediante excepciones 305
do c1ose() puede generar una excepci n, as que lo encerramos dentro de una ctusula try y tratamos de capulrar la excep-
cin an cuando ese mtodo se encuentre dentro del bloque de otra clusula catch: para el compi lador de Java se trata sim-
pl emente de un par adicional de s mbolos de llave. Despus de realizar las operaciones locales, la excepcin se vuelve a
generar. lo cual resulta apropiado porque este constructor ha fallado y no queremos que el mtodo que ha hecho la invoca-
cin asuma que el objeto se ha creado apropiadamente y es vlido.
En este ejemplo, la clusula finally no es. en modo alguno, el lugar donde cerrar el archivo con c1osc(), ya que eso hara
que el archi vo se cerrara cuando el constructor completara su ejecucin. Lo que queremos es que el archi vo contine abier-
to mientras dure la vi da til del objeto InputFile.
El mtodo getLinc() devuelve un objeto String que contiene la siguiente linea del archivo. Dicho mtodo invoca a
readLinc(), que puede generar una excepcin, pero dicha excepcin es capturada, por lo que gctLinc( ) no genera excep-
cin alguna. Uno de los problemas de diseo relati vos a las excepciones es el de si debemos tratar una excepcin comple-
tamente en este nivel. si slo debemos tratarla parcialmente y pasar la misma excepcin (u otra distinta) al ni vel siguiente,
o si debemos pasar la excepcin directamente al siguiente nivel. Pasar la excepcin directamente, siempre que sea apropia-
do, puede simplifi car bastante el programa. En nuestro caso, el mtodo getUnc() cOl/vierfe la excepcin al tipo
RuntimeException para indi car que se ha producido un error de programacin.
El mtodo dispose() debe ser llamado por el usuario cuando ya no se necesite el mtodo InputFile. Esto har que se libe-
ren los recursos del sistema (como por ejemplo los descriptores de archivo) que estn siendo utilizados por los objetos
BuffercdReader y/o FileReader. Evidentemente, no queremos hacer esto hasta que hayamos tenninado de utili zar el obje-
to InputFile. Podramos pensar en incluir dicha funcionalidad en un mtodo finalize() , pero como hemos dicho en el
Captulo 5, Inicializacin y limpieza. no siempre podemos estar seguros de que se vaya a llamar a finalize() (e, incluso si
estuviramos seguros de que va a ser llamado, lo que no sabemos es cundo). sta es una de las desventajas de Java: las
tareas de limpieza (excepn13ndo las de memoria) no ti enen lugar automticamente, por lo que es preciso infonllar a los pro-
gramadores de cli entes de que ellos son los responsables.
La fonna ms segura de utilizar una clase que pueda generar una excepcin durante la construccin y que requiera que se
lleven a cabo tareas de limpieza consiste en emplear bloques try anidados:
11: exceptions jCleanup . java
ji Forma de garantizar la apropiada limpieza de un recurso.
public class Cleanup
public static void main (St ring [] args) {
try {
InputFile in = new InputFile ("Cleanup.java") i
try {
String Si
int i == 1 i
while((s = in.getLine()) != null)
i II Realizar aqu el procesamiento lnea a lnea.
ca tch (Except ion e) {
System.out.println("Caught Exception in main") i
e.printStackTrace(Syscem.outl;
finally {
in . dispose() i
catch(Exception el
System.out . println("InputFi l e construction failed
lt
} i
1* Output:
dispose() successful
, /// ,-
Examine cuidadosamente la lgica utilizada: la construccin del objeto InputFile se encuentra en su propio bloque try. Si
di cha construccin falla, se entra la clusula catch extema y no se invoca el mtodo dispose(). Sin embargo, si la construc-
cin tiene xito, entonces hay que asegurarse de que el objeto se limpie, por lo que inmediatamente despus de la construc-
cin creamos un nuevo bloque try. La clusula finally que lleva a cabo las tareas de limpieza est asociada con el bloque
306 Piensa en Java
try interno; de esta fomla, la clusul a finally no se ejecuta si la construccin fa lla, mi entras que siempre se ejecuta si la
construccin ti ene xit o.
Esta tcnica general de limpieza debe utili zarse an cuando el constmctor no genere ninguna excepcin. La regla bsica es:
justo despus de crear un objeto que requiera limpi eza, Ln cluya una estmctura try-finally:
11 : exceptions / CleanupIdiom.java
II Cada objeto eliminable debe estar seguido por try-finally
class NeedsCleanup { II Construction can't fail
private static long counter = 1;
private final long id = counter++
publ ie void dispose () {
System.out.println ( "NeedsCleanup " + id + 11 disposed" ) ;
class ConstructionException extends Exception {}
class NeedsCleanup2 extends NeedsCleanup {
II La construccin no puede fallar :
public NeedsCleanup2() throws ConstructionException {}
public class CleanupIdiom {
public static void main (String [] args) {
II Seccin 1:
NeedsCleanup ncl = new NeedsCleanup( ) ;
try (
11 ...
finally
ncl.dispose() ;
II Seccin 2:
II Si la construccin no puede fallar, podemos agrupar los objetos:
NeedsCleanup nc2 new NeedsCleanup{)
NeedsCleanup nc3 = new NeedsCleanup();
try (
11
finally
nC3.dispose {) ; II Orden inverso al de construccin
nc2.dispose ( ) ;
II Seccin 3:
II Si la construccin puede fallar, hay que proteger cada uno:
try (
NeedsCleanup2 nc4 = new NeedsCleanup2{);
try (
NeedsCleanup2 ncS = new NeedsCleanup2();
try (
11
finally
ncS.dispose() i
catch (ConstructionException e) { II const ructor de ncS
System. out .println(e) i
finally (
nc4 . dispose ( ) ;
12 Tratamiento de errores mediante excepciones 307
catch (ConstructionException e l { // constructor de nc4
System.out.println (e l ;
/ *
Output:
NeedsCleanup 1 disposed
NeedsCleanup 3 disposed
NeedsCleanup 2 disposed
NeedsCleanup 5 disposed
NeedsCleanup 4 disposed
*/// ,-
En main( ), la seccin l nos resulta bastante sencilla de entender: incluimos una estructura tr y-fi nall y despus de un obje-
to eliminable. Si la constmccin del objeto no puede fallar, no es necesario incluir ninguna clusula catch. En la seccin 2,
podemos ver que los objetos con constructores que no pueden fallar pueden agruparse tanto para las tareas de construccin
como para las de limpieza.
La seccin 3 muestra cmo tratar con aquellos objetos cuyos constmctores pueden fallar y que necesi tan limpieza. Para
poder manejar adecuadamente esta situacin, las cosas se complican, porque es necesario rodear cada construccin con su
propia estructura try-catch, y cada construccin de objeto debe ir seguida de un tr y-finall y para garantizar la limpieza.
Lo complicado del tratamiento de excepciones en este caso es un buen argumento en favor de la creacin de constructores
que no puedan fallar, aunque lamentablemente esto no siempre es posible.
Observe que si di spose() puede generar una excepcin, entonces sern necesarios bloques tr)' adicionales. Bsicamente, lo
que debemos hacer es pensar con cuidado en todas las posibi 1 idades y protegernos frente a cada lIna.
Ejercicio 21: (2) Demuestre que un constTuctor de una clase derivada no puede capturar excepciones generadas por su
constnlClOr de la clase base.
Ejercicio 22: (2) Cree una clase denominada FailingConst r uctor con un constructor que pueda fa\lar en mitad del pro-
ceso de constmccin y generar una excepcin. En main(). escriba el cdigo que pemlita protegerse apro-
piadamente frente a este faUo.
Ejercicio 23: (4) Afiada una clase con un mtodo dispose( ) al ejercicio anterior. Modifique FailingConstructor para
que el constnlctor cree uno de estos objetos eliminables como un objeto miembro, despus de lo cual el
constructor puede generar una excepcin y crear un segundo objeto miembro eliminable. Escriba el cdi-
go necesario para protegerse adecuadamente contra los fallos y verifique en main() que estn cubiertas
todas las posibles situaciones de fa\lo.
Ejercicio 24: (3) Allada un mtodo di spose( ) a la clase Fai li ngConstr uctor y escriba el cdigo necesario para utilizar
adecuadamente esta clase.
Localizacin de excepciones
Cuando se genera una excepcin, el sistema de tratamiento de excepciones busca entre las mtinas de tratamiento "ms cer-
canas", en el orden en que fueron escritas. Cuando encuentra una correspondencia, se considera que la excepcin ha sido
tratada y no se contina con el proceso de bsqueda.
Locali zar la excepcin correcta no requiere que haya una correspondencia perfecta entre la excepcin y su rutina de trata-
miento. Todo objeto de una clase derivada se corresponder con una rutina de tratamiento correspondiente a la clase base,
como se muestra en este ejemplo:
// : exceptions/Human.java
/ / Captura de jerarquas de excepciones.
class Annoyance extends Exception {}
class Sneeze extends Annoyance {}
public class Human {
308 Piensa en Java
public static void main (String [] args) {
11 Capturar el tipo exacto:
try (
throw new Sneeze()
catch(Sneeze sl {
System.out. println (lICaught Sneeze
ll
);
catch (Annoyance a) {
System.out.println(IICaught Annoyance
ll
) i
11 Capturar el tipo base:
try (
throw new Sneeze()
catch (Annoyance al {
System. out. println ( 11 Caught Annoyance 11 )
1* Output:
Caught Sneeze
Caught Annoyance
* /// , -
La excepcin Sneeze ser capturada por la primera clusula catch con la que se corresponda, que ser por supuesto la pri-
mera. Sin embargo, si eliminamos la primera clusula catch, dejando slo la clusula catch correspondiente a Annoyance,
el cdigo seguir funcionando porque se est capturando la clase base de Sneeze. Dicho de otra forma, catch(Annoyance
a) penniti r capturar Ulla excepcin Annoyance o cualquier clase derivada de ella. Esto resulta til porque si decidimos
aadir ms excepciones derivadas a un mtodo, no ser necesario cambiar el cdigo de los programas cliente, siempre y
cuando el cliente capnlre las excepciones de la clase base.
Si tratamos de "enmascarar" las excepciones de la clase derivada, incluyendo primero la clusula catc h correspondi ente a
la clase base, como en el sigui ente ejemplo:
try (
throw new Sneeze();
catch (Annoyance al {
// ...
catch (Sneeze s)
//
el compi lador nos dar un mensaje de error, ya que ver que la clusula catch correspondiente a Sncezc nunca puede eje-
cutarse.
Ejercicio 25: (2) Cree una jerarqua de excepciones en tres niveles. Ahora cree una clase base A con un mtodo que
genere una excepcin de la base de nuestra jerarqua. Herede una clase B de A y sustituya el mtodo para
que genere una excepcin en el nivel dos de la jerarqua. Repita el proceso, heredando una clase e de B.
En main(), cree un objeto e y generalcelo a A, invoque el mtodo a continuacin.
Enfoques alternativos
Un sistema de tratamiento de excepciones es un mecani smo especial que permite a nuestros programas abandonar la ejecu-
cin de la secuencia nonna! de instrucciones. Ese mecani smo especial se utiliza cuando tiene lugar una "condi cin excep-
cionar', tal que la ejecucin nonnal ya no es posibl e o deseable. Las excepciones representan condiciones que el mtodo
actual no es capaz de gestionar. La razn por la que se desarrollaron los sistemas de tratamiento de excepci ones es porque
la tcnica de gest ionar cada posible condicin de error producida por cada ll amada a funcin era demasiado onerosa, lo que
haca que los programadores no la implementaran. Como resultado, se terminaba ignorando los errores en los programas.
Merece la pena recalcar que incrementar la comodidad de los programadores a la hora de tratar los errores fue una de las
principales motivac iones para desarrotlar los sistemas de tratamiento de excepciones.
12 Tratamiento de errores mediante excepciones 309
Una de las directrices de mayor importancia en el tratamiento de excepciones es "no captures una excepcin a menos que
sepa qu hacer con ella". De hecho, uno de los objetivos ms importantes del tratamjento de excepciones es quitar el cdi-
go de tratamiento de errores del punto en el que los errores se producen. Esto nos pennite concentramos en lo que quere-
mos conseguir en cada seccin del cdigo, dejando la manera de tratar con los problemas para ulla seccin separada del
mismo cdigo. Como resultado, el cdigo principal no se ve oscurecido por la lgica de tratamiento de errores, con lo que
resulta mucho ms fcil de comprender y de mantener. Los mecanismos de tratamiento de excepciones tambin tienden a
reducir la cantidad de cdigo dedicado a estas tareas. pemlitiendo que una rutina de tratamiento d servicio a mltiples luga-
res posibles de generacin de errores.
Las excepciones comprobadas complican un poco este escenario, porque nos fuerzan a aadir clusulas catch en lugares en
los que puede que no estemos listos para gestionar un error. Esto puede dar como resultado que no se traten ciertas excep-
ciones:
try {
II ... hacer algo til
} catch IObligatoryException e l {} / / Glub!
Los programadores (incluido yo en la primera edicin de este libro) tienden a hacer lo ms simple y a capturar la excepcin
olvidndose luego de tratarla; esto se hace a menudo de forma inadvertida, pero una vez que lo hacemos, el compi lador se
queda satisfecho, de modo que si no nos acordamos de revisar y corregir el cdigo, esa excepcin se pierde. La excepcin
tiene lugar, pero desaparece todo rastro de la misma una vez que ha sido capturada y se deja sin tratar. Puesto que el com-
pilador nos fuerza a escribir cdigo desde el principio para tratar la excepcin, incluir una rutina de tratamiento vaca pare-
ce la solucin ms simple, aunque en realidad es lo peor que podemos hacer.
Horrorizado al damlc cuenta de que yo haba hecho preci samente esto, en la segunda edicin del libro "correg" el proble-
ma imprimiendo la traza de la pila dentro de la mtina de tratamiento (como puede verse apropiadamente en varios ejemplos
de este captulo). Aunque resulta til trazar el comportamiento de las excepciones, esta forma de proceder sigue indicando
que realmente no sabemos qu hacer con esa excepcin en dicho punto del cdigo. En esta seccin, vamos a estudiar algu-
nos de los problemas y algunas de las complicaciones derivadas de las excepciones comprobadas, y vamos a repasar las
opciones que tenemos a la hora de tratar con ellas.
El tema parece bastante simple, pero no slo resulta complicado sino que tambin es motivo de controversia. Hay personas
que sost ienen finnemente los argumentos de ambos bandos y que piensan que la respuesta correcta (es decir, la suya) resul-
ta tremendamente obvia. En mi opinin, la razn de que den estas posiciones tan vehementes es que resulta bastante obvia
la ventaja que se obtiene al pasar de un lenguaje con un pobre tratamiento de los tipos, como el previo ANSI e a un lengua-
je fuertemente tipado con tipos estticos (es decir, comprobados en tiempo de compilacin) como e++ o Java. Cuando se
hace esa transicin (como hice yo mi smo), las ventajas resultan tan evidentes que puede parecer que la comprobacin est-
tica de tipos es siempre la mejor respuesta a la mayora de los problemas. Mi esperanza con las lneas que siguen es que, al
relatar mi propia evolucin, el lector pueda ver que el valor absoluto de la comprobacin esttica de tipos es cuestionable;
obviamente, resulta muy til la mayor parte de las veces, pero hay un lnea tremendamente difusa a partir de la cual esa com-
probacin esttica de tipos comienza a ser un estorbo y un problema (una de mis citas favoritas es la que dice "todos los
modelos son errneos, aunque algunos de ellos resultan tiles").
Historia
Los sistemas de tratamiento de excepciones tienen su origen en sistemas como PLII y Mesa, y posteriormente se incorpo-
raron en CLU, Smalltalk, Modula-3, Ada, Eiffel, C++, Python, Java y los lenguajes post-Java como Ruby y C#. El di seo
de Java es similar a C++-, excepto en aquellos lugares en los que los diseadores de Java pensaron que la tcnica usada en
C++ podra causar problemas.
Para proporcionar a los programadores un marco de trabajo que estuvieran ms di spuestos a utilizar para el tratamiento y la
recuperacin de errores, el sistema de tratamiento de excepciones se aadi a e ++ bastante tarde en el proceso de estanda-
rizacin, promovido por Bjame Stroustrup, el autor original del lenguaje. El modelo de las excepciones de e++- proviene
principalmente de CLU. Sin embargo, en aquel entonces existan otros lenguajes que tambin soportaban el tratamiento de
excepciones: Ada, Smalltalk (ambos tienen excepciones, pero no tienen especificaciones de excepciones) y Modula-3 (que
inclua tanto las excepciones como las especificaciones).
310 Piensa en Java
En su artculo pionero
7
sobre el tema, Liskov y Snyder observaron que uno de los principales defectos de los lenguajes tipo
e, que infomlan acerca de los errores de manera transitoria es que:
..... toda im'ocacilI debe ir seguida de una prueba incondicional para determinar cul ha sido el
resultado. Este requisito hace que se desarrollen programas dificiles de leer y que probablemente tam-
bin 5011 poco ejicie11les, lo que tiende a desanimar a los programadores a la hora de .,eliali:ar y tra-
tar las excepciones. "
Por tanto. uno de los motivos originales para desarrollar sistemas de tratamiento de excepciones era eliminar este requisito,
pero con las excepciones comprobadas en Java nos encontramos precisamente con este tipo de cdigo. Los autores conti-
nan diciendo:
.. ... si se requiere que se asocie el texto de tilla rutina de tratamiento a la invocacin que genera la
excepcin, el resultado sern programas poco legibles en los que las expresiones estarn descompues-
tas debido a la presencia de las rutinas de tratamiento. "
Siguiendo el enfoque adoptado en CLU, Stroustrup afinn, al disear las excepciones de e ++, que el objetivo era reducir
la cantidad de cdigo requerida para recuperarse de los errores. En mi opinin, estaba partiendo de la observacin de que
los programadores no solan escribir cdigo de tratamiento de errores en e debido a que la cantidad y la colocacin de dicho
cdigo en los programas era muy dificil de manejar y tenda a distraer del objetivo principal del programa. Como resulta-
do, los programadores solian abordar el problema de la misma manera que en e. ignorando los errores en el cdigo y utili-
zando depuradores para localizar los problemas. Para usar las excepciones, haba que convencer a estos programadores de
e de que escribieran cdigo "adiciona!", que normalmente no escribiran. Por tanto, para hacer que pasen a adoptar una
fonna ms eficiente de tratar los errores, la cantidad de cdigo que esos programadores deben "ai'iadi r" no debe ser excesi-
va. Resulta importante tener presente este objetivo de diseo ini cial a la bora de eliminar los efectos que las excepciones
comprobadas tienen en Java.
e++ tom prestado de CLU una idea adicional: la especificacin de excepcin, mediante la cual se enuncian programtica-
mente en la signatura del mtodo las excepciones que pueden generarse como resultado de la llamada al mtodo. La espe-
cificacin de excepciones tiene, en realidad, dos objetivos. Puede querer decir: "Puedo generar esta excepcin en mi cdigo,
encrgate de tratarla", Pero tambin puede significar: "Estoy ignorando esta excepcin que puede producirse como resulta-
do de mi cdigo, encrgate de tratarla'". Hasta ahora, nos estamos centrando en la parte que dice "encrgate de tratarla" a la
hora de examinar la mecnica y las sintaxis de las excepciones, pero lo que en este momento concreto nos interesa es el
hecho de que a menudo ignoramos las excepciones que se producen en nuestro cdigo, y eso es precisamente lo que la espe-
cificacin de excepciones puede indicar.
En C++, la especificacin de excepciones no fornla parte de la infonnacin de tipo de una funcin (la signatura). La nica
comprobacin que se realiza en tiempo de compilacin consiste en garantizar que las especificaciones de excepciones se
utilizan de manera coherente, por ejemplo, si una funcin o un mtodo generan excepciones, entonces las versiones sobre-
cargadas o derivadas tambin debern generar esas excepciones. A diferencia de Java, sin embargo, no se realiza ninguna
comprobacin en tiempo de compilacin para detenninar si la funcin o mtodo va a generar en realidad dicha excepcin.
o si la especificacin de excepciones est completa (es decir, si describe con precisin todas las excepciones que puedan ser
generadas). Esa va lidacin s que se produce, pero slo en tiempo de ejecucin. Si se genera una excepcin que viola la
especificacin de excepciones, el programa e++ invocar la funcin de la biblioteca estndar unexpected().
Resulta interesante observar que. debido al uso de plantillas, las especificaciones de excepciones no se utilizan en absoluto
en la biblioteca estndar de C++. En Java, existen una serie de restricciones que afectan a la forma en que pueden emplear-
se los genricos Java con las especificaciones de excepciones.
Perspectivas
En primer lugar. merece la pena observar que es el lenguaje Java el que ha inventado las excepciones comprobadas (inspi-
radas claramente en las especificaciones de excepciones de e++ y en el hecho de que los programadores e++ no suelen ocu-
parse de las mismas). Sin embargo, se trata de un experimento que ningn lenguaje subsiguiente ha incorporado.
7 Barbara Liskov y Alan Snydcr. Exceplion Handling in CLU. IEEE Transactions on Sonware Enginecring. Vol. SE-5, No. 6. Noviembre 1979. Este ant-
culo no c ~ t disponible en Internet. sino slo en copia impresa. por lo que tendr que encargar una copia a tra\cs de su biblioteca.
12 Tratamiento de errores mediante excepciones 311
En segundo lugar, las excepciones comprobadas parecen ser algo "evidentemente bueno" cuando se las contempla dentro
de ejemplos de nivel introductorio y en pequeos programas. Segn algunos autores, las dificultades ms sutiles comienzan
a aparecer en el momento en que los programas crecen de tamao. Por supuesto. el tamaiio de los programas no suele incre-
mentarse de manera espectacular de la noche a la maana. sino que lo ms normal es que los programas vayan creciendo de
tamao poco a poco. Los lenguajes que puedan no ser adecuados para proyectos de gran envergadura, se utilizan sin pro-
blema para proyectos de pequeo tamao. Pero esos proyectos crecen y. en algn punto, nos damos cuenta de que las cosas
que antes eran manejables ahora son relativamente dificiles. A eso es a lo que me refera al comentar que los mecanismos
de comprobacin de tipos pueden llegar a hacerse engorrosos: en particular. cuando esos mecanismos se combinan con el
concepto de excepciones comprobadas.
El tamao del programa parece ser una de las cuestiones principales. Y esto es, en s mismo, un problema porque la mayo-
ra de los anlisis tienden a utilizar como ilustracin programas de pequeilo tama1o. Uno de los diseadores de C# escribi
que:
"El examen de programas de pequeiio lamaiio nos I/e\'a a la conclusin de que imponer el uso de espe-
cificaciones de excepciones podra mejorar tanto la productividad del desarrollador como la calidad
de cdigo, pero la experiencia con los grandes proyecfOs de desarrollo software sugiere un resultado
completamente distinto: 11110 menor productividad y un incremento en la calidad del cdigo que es,
como mucho. poco significativo, .. 8
En referencias a las excepciones no capturadas, los creadores de CLU escriban:
"Pensamos que era poco realista exigir al programador que proporcionara rutinas de tratamiento en
aquellas situaciones en las que no es posible llevar a cabo ninguna accin con verdadero significa-
do. "9
A la hora de explicar por qu una declaracin de funcin sin ninguna especificacin significa que la funcin pueda generar
cualquier excepcin en lugar de ninguna excepcin. Stroustrup escribe:
"Sin embargo, eso requeriria que se incluyeran especificaciones de excepcin para casi todas lasfim-
ciones. hara que fuera necesario efectuar muchas recompilaciones y d{ficultara la cooperacin con
el software escrito en otros lenguajes, Esto animara a los programadores a subvertir los mecanis-
mos de tratamiento de excepciones y a escribir cdigo espreo para suprimir las excepciones.
Proporcionarla un falso sentido de seguridad a las personas que no se hubieran dado cuenta de la
excepcin, .. 10
Precisamente, con las excepciones comprobadas en Java podemos ver que se produce precisamente esta reaccin: tratar de
subvertir las excepciones.
Martin Fowler (autor de UAfL Distilled, Refactoring y Analysis Patlerns) escriba en cierta ocasin lo siguiente:
... en conjunto. creo que las excepciones son buenas. pero las excepciones compmbadas en Java
callsan ms problemas de los que resuelven. .,
Actualmente, lo que opino es que el paso ms importante dado por Java fue unificar el modelo de informacin de errores,
de modo que de todos los errores se informa utilizando excepciones, Esto no suceda en C++. porque. debido a la compati-
bilidad descendente con C, segua estando disponible el modelo de limitarse a ignorar los elTores, Pero, cuando disponemos
de un mecanismo de infonne de errores coherente con excepciones, las excepciones pueden utilizarse si se desea y. en caso
contrario, se propagarn al siguiente nivel superior (la consola u otro programa contenedor). Cuando Java modific el mode-
lo e++ para que las excepciones fueran la nica fonna de infom1ar de los errores, la imposicin <tdicional relativa a las
excepciones comprobadas puede que haya dejado de ser tan necesaria.
En el pasado, crea firmemente que tanto las excepciones comprobadas como la comprobacin esttica de tipos resultaban
esenciales para el desarrollo de programas robustos. Sin embargo, tanto la infoll11acin proporcionada por otros programa-
8 IlItp://disclIss.del'elop.com archives!;\ a.exe? A2=im1OO 1I A&L= DUTNET &P=R328JO
9 Exceplion Handling il/ eLV. Liskov & Snyder.
10 Bjame Strous{mp. rIJe c++ Progmmming Language. ~ EdilicJl/ (Addison-\Vcsley. 1997), p. 376.
312 Piensa en Java
dores como mi experiencia directa 11 con lenguajes que 5011 ms dinmicos que estticos, me han hecho pensar que las m y o ~
res ventajas provienen en realidad de:
t. Un modelo unificado de infonne de errores basado en excepciones. independientemente de si el programador est
obligado por el compilador a tratarlas.
2. Mecanismos de comprobacin de tipos, independientemente de cundo terna lugar esa comprobacin. Es decir.
en tanto que se imponga un uso apropiado de los tipos, a menudo no importa si eso sucede en tiempo de compi.
lacin o de ejecucin.
Adems, se puede aumentar muy significativamente la productividad si se reducen las restricciones impuestas al programa.
dor en tiempo de compilacin. De hecho, se necesita algo de reflexin junto con los genricos. para compensar la naturale_
za demasiado restrictiva del mecanismo esttico de tipos, C0l110 podr ver en una serie de ejemplos a lo largo del libro.
Algunas personas me han dicho que lo que digo constituye una autntica blasfemia y que al expresar estas ideas mi reputa-
cin quedar irremediablemente destruida, la civilizacin se vendr abajo y un mayor porcentaje de los proyectos de pro-
gramacin fallarn. La creencia de que el compilador puede ser la salvacin de nuestro proyecto, al indicarnos los errores
en tiempo de compilacin, se basa en evidencias bastante fuertes , pero es todava ms importante que nos demos cuenta de
las limitaciones que afectan a lo que el compilador es capaz de hacer. En el suplemento disponible en http://MindVielt'.
l1et/Books/ BetterJava, se hace un gran hincapi en el valor que tienen los procesos automatizados de construccin de pro-
gramas y las pruebas de las unidades componentes de un programa, mecanismos ambos que nos ayudan mucho ms que el
tratar de convertirlo todo en un error sintctico. Merece la pena recordar que:
"Un buen lenguaje de programacin es aqul que ayuda a los programadores a escribir buenos pro-
gramas. Ningn lenguaje de programacin podr impedir siempre que sus usuarios escriban malos
programas. ,. 12
En cua lquier caso, la probabilidad de que las excepciones comprobadas sean eliminadas de Java parece muy baja. Sera un
cambio de lenguaje demasiado radical y los que se oponen a esa eliminacin dentro de Sun parecen tener bastante fuerza.
Sun tiene una larga historia (y una oonna) de ser siempre compatible en sentido descendente; de hecho, casi todo el softwa-
re de SUD se ejecuta en lodo el hardware Sun, independientemente de lo antiguo que ste sea. Sin embargo, si ll ega a tener
la sensacin de que algunas excepciones comprobadas estn empezando a ser engorrosas, y especialmente si se encuentra
continuamente en la obligacin de capturar excepciones para luego no saber qu hacer con ellas, existen ciertas alternati vas.
Paso de las excepciones a la consola
En los programas simples, como muchos de los incluidos en este libro, la forma ms fcil de preservar las excepciones sin
escribir un montn de cdigo consiste en pasarlas a la consola desde main(). Por ejemplo, si queremos abrir un archivo
para lectura (que es algo que veremos cmo hacer en el Captulo 18, E/S), tenemos que abrir y cerrar un objeto
FilelnputStream, que genera excepciones. En un programa simple, podemos hacer lo siguiente (podr ver esta tcnica UTi-
lizada en numerosos lugares de este libro) :
// : exceptons/MainException.java
import java.io.*
public class MainException {
1/ Pasar todas las excepciones a la consola:
public static void main(String[] args) throws Exception {
/1 Abrir el archivo:
FilelnputStream file =
new FilelnputStream(UMainException.java")
1/ Usar el archivo ...
/1 Cerrar el archivo:
file. close ()
11 Indirectamente con Smalltalk a traves de conversaciones con muchos programadores con experiencia en dicho lenguaje; di reCiamente con pylhl1n
( lI'WII'.PylllOll.Ol'g).
12 Kees Kosler. diseador del lenguaje COL. citado por Benrand Meyer, diseador del lenguaje Eiffcl. wlI1\celj.com/eljlvllnllbm/riglu/.
12 Tratamiento de errores mediante excepciones 313
Observe que main( ) es un mtodo que tambin puede tener una especificacin de excepciones, y en este ejemplo el tipo de
la excepcin es Exception, que es la clase raz de todas las excepciones comprobadas. Pasando la excepcin a la consola,
nos vemos liberados de la obligacin de incluir clusulas try-catch dentro del cuerpo de main( ) (lamentablemente, la E/S
de archivo es signifi cati vamente ms compleja de lo que parece en este ejemplo, as que no se anime en exceso hasta que
baya leido el Capimlo 18, E/S).
Ejercicio 26: (1) Cambie la cadena de caracteres que especifica el nombre del archivo en MainException,java para pro-
porcionar un nombre de archivo que no exista. Ejecute el programa y anote el resul tado.
Conversin de las excepciones comprobadas en no comprobadas
Pasar una excepcin hacia el exterior desde main( ) resulta cmodo cuando estamos escribiendo programas simpl es para
nuestro propio consumo, pero no resulta til por regla general. El problema real surge cuando estamos escribiendo el cuer-
po de un mtodo nonnal, y entonces invocamos otro mtodo y nos damos cuenta de lo siguiente: "no tengo ni idea de lo
que hacer con esta excepcin aqu, pero no qui ero perderla ni imprimir simplemente un mensaje vana!". Gracias al meca-
nismo de las excepciones encadenadas existe una solucin nueva y simple, que consiste en limitarse a "envolver" una excep-
cin comprobada dentro de otra de tipo RuntimeException pasndosela al constmctor de RuntimeException, como en el
siguiente ejemplo:
try {
II ... hacer algo til
catch( IDontKnowWhatToDoWithThisCheckedException e)
throw new RuntimeException(e)
Esto parece una sol ucin ideal si queremos "desactivar" la excepcin comprobada: no la perdemos y no tenemos porqu
incluirla en la especificacin de excepciones de nuestro mtodo; adems, debido al encadenamiento de excepciones no per-
demos ninguna informacin relativa a la excepcin original.
Esta tcnica proporciona la opcin de ignorar la excepcin y dejarla progresar por la pila de llamadas, si n vemos obligados
a escribir clusulas try-catch y/o especificaciones de excepciones. Sin embargo, seguimos pudiendo capturar y tratar la
excepcin especfica, usando getCause(), como se muestra a continuacin:
11: exceptions/TurnOffChecking.java
II IIDesactivacin" de excepciones comprobadas.
import java.io.*
import static net.mindview.util.Print.*
class WrapCheckedException {
void throwRuntimeException (int type) {
try (
switch(typel
case o: throw new FileNotFoundException()
case 1: throw new IOException()
case 2: throw new RuntimeException("Where am I?") i
default: return
catch (Exception e) { II Adaptar a no comprobada:
throw new RuntimeException(e);
class SomeOtherException extends Exception {}
public class TurnOffChecking (
public static void main(String(] args) {
WrapCheckedException wce = new WrapCheckedException()
II Debemos invocar tbrowRuntimeException(} sin un bloque try
314 Piensa en Java
/1 y dejar que las excepciones de tipo RuntimeException
/1 salgan del mtodo:
wce . throwRuntimeException{3) ;
JI O podemos elegir capturar las excepciones:
for(int i = O; i <: 4; i++)
try {
if(i <: 3)
wce.throwRuntimeException(i) ;
else
throw new SomeOtherException() i
catch (SomeOtherException el {
print ("SomeOtherException: 11 + el;
catch (RuntimeException re) {
try {
throw re . getCause();
catch (Fil eNotFoundException el {
print (II FileNotFoundException : " + el i
catch(IOException el {
print {" IOException : 11 + el;
catch (Throwable el {
print (" Throwable : 11 + el ;
/ * Output :
FileNot FoundException: java . io . FileNot FoundException
IOException: java.io.IOException
Throwable : java . lang . RuntimeException : Where am I ?
SomeOtherException : SomeOtherExcept ion
* ///>
WrapCheckedExccption.throwRuntimeException( ) conti ene cdigo que genera diferentes tipos de excepciones. stas
se capturan y se envuel ven dentro de objetos RuntimeException, asi que se convierten en la "causa" de dichas excepcio-
nes.
En TurnOffChecking, podemos ver que es posible invocar throwRuntimeException( ) sin ningn bloque try porque el
mtodo no genera ninguna excepcin comprobada. Sin embargo, cuando estemos li stos para capturar las excepciones, segui -
remos teniendo la posibilidad de capturar cualquier excepcin que queramos poniendo nuestro cdigo dentro de un bloque
try. Comenzamos capturando todas las excepciones que sabemos explcitamente que pueden emerger del cdigo incluido
dentro del bloque try; en este caso. se captura primero SomeOtherException. Finalmente, se captura RuntimeException
y se genera con throw el resultado de getCa use() (la excepcin envuelta). Esto extrae las excepciones de origen, que pue-
den ser entonces tratadas en sus propi as clusulas catch.
La tcnica de envolver una excepcin comprobada en otra de tipo RuntimeException se utili zar siempre que sea apropi a-
do a lo largo del resto del libro. Otra solucin consiste en crear nuestra propia subclase de RuntirneException. De esta
forma, no es necesari o capturarla, pero alguien puede hacerlo si as lo desea.
Ejercicio 27: ( 1) Modifique el Ejercicio 3 para convertir la excepcin en otra de tipo RuntimeException.
Ejercicio 28: ( 1) Modifique el Ejercicio 4 de modo que la clase de excepcin personali zada herede de
RuntimeException, y muestre que el compi lador nos pem1ite no incluir el bloque try.
Ejercicio 29: ( 1) Modifique todos los tipos de excepcin de Stormylnning.java de modo que extiendan
RuntimeException, y muestre que no son necesarias especifi caciones de excepcin ni bloques try.
Elimine los comentarios ' II! ' y muestre cmo pueden compi larse los mtodos sin especificaciones.
Ejercicio 30: (2) Modifique Human.java de modo que las excepciones hereden de RuntimeException. Modifique
maine ) de modo que se utilice la tcnica de TurnOffChecking.java para tratar los diferentes tipos de
excepciones.
12 Tratamiento de errores mediante excepciones 315
Di rectrices relativas a las excepciones
Utilice las excepciones para:
1. Tratar los problemas en el nivel apropiado (evite capturar las excepciones a menos que sepa qu hacer con ellas).
2. Corregir el problema e invocar de nuevo el mtodo que caus la excepcin.
3. Corregir las cosas y continuar, sin volver a ejecutar el mtodo.
4. Calcular algunos resultados altemativos en lugar de aquellos que se supone que el mtodo deba producir.
5. Hacer lo que se pueda en el contexto actual y regenerar la misma excepcin, entregndosela a un contexto de nivel
superior.
6. Hacer lo que se pueda en el contexto actual y generar una excepcin diferente, entregndosela a un contexto de
nivel superior.
7. Terminar el programa.
8. Simplificar (si el esquema de excepciones utilizado hace que las cosas se vuelvan ms complicadas, entonces ser
muy molesto de utilizar).
9. Hacer que la biblioteca y el programa sean ms seguros (esto es una inversin a corto plazo de cara a la depura-
cin y una inversin a largo plazo en lo que respecta a la robustez de la aplicacin)
Resumen
Las excepciones son una parte fundamental de la programacin Java; no es mucho lo que puede hacerse si no se sabe cmo
trabajar con ellas. Por esa razn, hemos decidido introducir las excepciones en este punto del libro; hay muchas bibliotecas
(corno las de E/S, mencionadas anterionnente) que no pueden emplearse sin tratar las excepciones.
Una de las ventajas del tratamiento de excepciones es que nos permite concentramos en un cierto lugar en el problema que
estemos tratando de resolver, y tratar con los errores relati vos a dicho cdigo en otro lugar. Y, aunque las excepciones se
suelen explicar como herramientas que nos penniten informar acerca de los errores y recuperarnos de ellos en tiempo de
ejecucin, no es tan claro con cunta frecuencia se implementa ese aspecto de "recuperacin", como tampoco est muy claro
si resulta siempre posible. Mi percepcin es que la recuperacin es posible en no ms del I O por cierto de los casos, e inclu-
so en esas sihlaciones slo consiste en devolver la pila a un estado estable conocido, ms que reanudar el procesamiento del
programa. Pero, sea esto verdad o no, lo importante es que el valor fundamental de las excepciones radica en la funcin de
"informe de errores". El hecho de que Java insista en que se nfoone de todos los errores mediante las excepciones es lo que
le proporciona a este lenguaje una gran ventaja respecto a otros lenguajes como C++. que permite infonnar de los errores
de distintas manera o incluso no infonnar en absoluto. Disponer de un sistema coherente de infom1e de errores implica que
no tenemos ya por qu hacemos la pregunta de "se nos est colando algn error por alguna parte?" cada vez que escriba-
mos un fragmento de cdigo (siempre y cuando, no capturemos las excepciones para luego dejarlas sin tratar).
Como podr ver en futuros captulos, al pennitirnos olvidarnos de esta cuestin (aunque sea generando una excepcin de
tipo RuntimeException), los esfuerzos de diseo e implementacin pueden centrarse en otras cuestiones ms interesantes
y complejas.
Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico Tlle Thinking in Java Annorated So/mion Guide, disponible para
la venta en
Cadenas de
caracteres
La manipulacin de las cadenas de caracteres es probablemente una de las actividades ms
comunes en la programacin.
Esto resulta especialmente cierto en los sistemas web, en los que Java se ut ili za ampliamente. En este captul o, vamos a exa-
minar ms en detalle la que constituye, ciertamente, la clase ms comnmente utilizada de todo el lenguaje, String, junto
con sus utilidades y clases asociadas.
Cadenas de caracteres inmutables
Los objetos de clase String son inmutables. Si examina la documentacin del JDK referente a la clase String, ver que todos
los mtodos de la clase que parecen modificar una cadena de caracteres, 10 que hacen, en realidad, es devolver un objeto
String completamente nuevo que contiene dicha modificacin. El objeto String original se deja sin modificar.
Considere el siguiente cdigo:
//: strings/lrnmutable .java
import static net . mindview util .Print .*
public class Immutable {
public static String upcase (String s) {
return s.toUpperCase{);
public static void main (String [] args) {
String q = "howdy";
printlq); II howdy
String qq = upcase(q);
printlqq); II HOWDY
printlq); II howdy
/* Out put:
howdy
HOWDY
howdy
- 111>
Cuando se pasa q a upcase( ) se trata en realidad de una copia de la referencia a q. El objeto al que esta referencia est
conectado pennanece en una nica ubicacin fsica. Las referencias se copian a medida que se las pasa de un si lio a a Iro.
Examinando la definicin de upcasc( ), podemos ver que la referencia que se le pasa ti ene un nombre s, y que di cha refe-
rencia existe slo mientras que se ejecuta el cuerpo de upcase(). Cuando se completa upcase( ), la referencia local s desa-
parece. upcasc( ) devuelve el resultado, que es la cadena original con todos los caracteres en mayscula. Por supuesto, lo
que se devuelve en rea lidad es una referencia al resultado. Pero lo cierto es que la referencia que se devuelve apunta a un
nuevo objeto, dejndose sin modi ficar el objeto al que apuntaba la referencia q original.
Este comportamiento es n0011ahnente el que deseamos. Suponga que escribimos:
318 Piensa en Java
String s = "asdf";
String x = Immutable.upcase(s};
Realmente queremos que el mtodo upcase( ) mod(fique el argumento? Para el lector del cdigo, los argumentos suelen
aparecer como fragmentos de infon11acin proporcionados al mtodo, no como algo que haya que modificar. Esta garanta
es impol1ante. ya que hace que el cdigo sea ms fcil de escribir y comprender.
Comparacin entre la sobrecarga de '+' y StringBuilder
Puesto que los objetos String son inmutables, podemos establecer tantos alias como queramos para un objeto String con-
creto. Puesto que lm objeto String es de slo lectura, no hay ninguna posibilidad de que una referencia modifique algo que
pueda afectar a otras referencias.
La inmutabilidad puede presentar problemas de rendimiento. Un ejemplo claro es el operador'+' que est sobrecargado para
Jos objetos String. La palabra "sobrecargado" signifi ca que hay una operacin a la que se le ha proporcionado UIl significa-
do adicional cuando se la usa con una clase concreta (los operadores '+' y '+=' para objetos String son los nicos operado-
res sobrecargados en Java, y el lenguaje no pemlite que el programador sobrecargue ningn otro operadorV
El operador '+' nos pemlite concatenar cadenas de caracteres:
JJ: stringsJConcatenation.java
public class Concatenation {
public static void main (Str ing [] argsl {
String mango = "mango" i
String s = "abe" + mango + "def" + 47;
System.out.println(s) ;
J* Output:
abcmangodef47
* ///,-
Queremos tratar de imaginar cul sera la forma en que este mecanismo funciona. El objeto String "abe" podra tener un
mtodo append( ) que creara un nuveo objeto String que contuviera "abe" concatenado con el contenido de mango. El
nuevo objeto String creara entonces otro objeto String que aadiera "der', etc.
Esto podra funcionar. pero requiere la creacin de un montn de objetos String simplemente para componer esta nueva
cadena de caracteres. lo que conducira a que hubiera varios objetos String intermedios a los que habra que aplicar poste-
riormente los mecanismos de depuracin de memoria. Sospecho que los diseadores de Java intentaron en primer lugar esta
solucin (lo cual constituye una de las lecciones aplicndose al diseo software: en realidad no sabemos nada acerca de un
sistema hasta que lo probamos con cdigo y obtenemos algo que funcione). Tambin sospecho que descubrieron que esta
solucin presentaba un rendimiento inaceptable.
Para ver lo que sucede en realidad podemos descompilar el cdigo anterior utilizando la herramienta javap, que est inclui-
da en el JDK. He aqu la lnea de comandos necesaria:
javap -e Concatenation
El indicador -c genera el cdigo intennedio NM. Despus de quitar las partes que no nos interesan y editar un poco los
resultados. he aqu el cdigo intemledio relevante:
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=3, Args
o: ldc #2; J/String mango
I C++ pernlite al programador sobrecargar los operadores a voluntad. Como esto puede ser a menudo bastante complicado (I'ase el Capnilo 10 de
Thinkillg in C++. rEdicin. Prenticc Hall , 2000), los diseadores de Java consideraron que era una caracterstica "indeseable" que no habia que incluir
en Java. Resulta gracioso que al final terminaran ellos mismos recurriendo a la sobrecarga de operadores y, lo que todava resulta mas irnico, se da la cir-
cunstancia de que la sobrecarga de operadores resultara mucho mas fcil en Java que en C++. Esto es lo que sucede en Python WlVW.Prr}oll.org)
y CII, que disponen de mecanismos tanto de depuracin de memoria como de sobrecarga sencilla de los operadores.
13 Cadenas de caracteres 319
2: astare 1
3: new #3; I/class StringBuilder
6, dup
7: invokespecial #4; //StringBuilder."<init>lt: ()
10: ldc #5; I/String abe
12: invokevirtual #6; / /StringBuilder append: (String)
15: aload 1
16: invokevirtual #6 / /StringBuilder append: (String)
19, lde #7; //String def
21: invokevirtual #6; / /StringBuilder append: (String)
24: bipush 47
26: invokevirtual #8; //StringBuilder append: (I)
29: invokevirtual #9; / /StringBuilder. toString: ()
32: astore_2
33: getstatic #10; / /Field System. out: PrintStream;
36: aload 2
37: invokevirtual #11; JI PrintStream.println: (St ringl
40: return
Si tiene experiencia con el lenguaje ensamblador, puede que este cdigo le resulte fami liar: las instrucciones como dup e
invokevirtual son los equivalentes en la mquina virtual Ja\'3 (NM) al lenguaje ensamblador. Si nunca ha visto lenguaje
ensamblador. no se preocupe: lo que hay que observar es que el compi lador introduce la clase java.lang.StringBuilder. No
haba ninguna mencin de StringBuilder en el cdigo fuente. pero el compi lador ha decidido utilizarlo por su cuenta, por-
que es mucho ms eficiente.
En este caso, el compilador crea un objeto StringBuilder para crear la cadena de caracteres s. y ll ama a append( ) cuatro
veces, una para cada uno de los fragmentos. Finalmente, invoca a toString() para generar el resultado, que almacena (con
a,lore_2) como ,.
Antes de dar por supuesto que lo que hay que hacer es uti lizar cadenas de caracteres por todas partes y que el compilador
se encargar de que todo sea eficiente. examinemos un poco ms en detalle lo que el compilador est haciendo. He aqu un
ejemplo que genera un resultado de tipo String de dos maneras: utilizando cadenas de caracteres y realizando la codifica-
cin de fonna manual con StringBuilder:
jj: stringsjWhitherStringBuilder.java
public class WhitherStringBuilder
public String implicit (String [1 fields)
String result = "";
for(int i = O i < fields.length i++)
result += fields [i]
return result
public String explicit (String (] fields) {
StringBuilder result new StringBuilder{)
for(int i = O; i < fields.length; i++)
result .append(fields [iJ);
return result.toString{);
Ahora, si ejecutamos javap -c WitherSlringBuilder. podemos ver el cdigo (simplificado) para los dos mtodos diferen-
tes. En primer lugar, impLicit( ):
public java.lang.String implicit(java.lang.String[]);
Code:
O, lde #2; / /String
2,
astore 2
-
3, iconst O
-
4,
istore 3
-
5, iload_ 3
320 Piensa en Java
6: aload 1
7: arraylength
8: if_icmpge 38
11: new #3; I/class StringBuilder
14: dup
15: invokespecial #4; // StringBuilder."<init:>":()
18: alead 2
19: invokevirtual #5; // StringBuilder.append: ()
22: alead 1
23: iload 3
24: aaload
25: invokevirtual #5; // StringBuilder.append: ()
28: invokevirtual #6 i / / StringBuilder. toString: ()
31: astare 2
32: iinc 3, 1
35: goto 5
38: aload 2
39: areturn
Observe las lneas 8: y 35: , que forman un bucle. La lnea 8: reali za una "comparacin entera de tipo mayor o igual que"
con los operandos de la pila y salta a la lnea 38: cuando el bucle se ha terminado. La lnea 35: es una instruccin de salto
que vuelve al principio del bucle, en la lnea 5:. Lo ms importante que hay que observar es que la construccin del objeto
StringBuilder tiene lugar dentro de este bucle, lo que quiere decir que obtenemos un nuevo objeto StringBuilder cada vez
que pasemos a travs del bucle.
He aqui el cdigo intennedio correspondiente a explicit():
public java.lang.String explicit(java.lang.String[]);
Cede:
O: new #3; / /class StringBuilder
3, dup
4: invokespecial #4; / / StringBuilder." <ini t:>" : ()
7: astore 2
8: iconst O
9: istore 3
10: iload 3
11: aload 1
12: arraylength
13: lf lcmpge 30
16: alead 2
17: aload 1
18 : iload 3
19: aaload
20: invokevirtual #5; /1 StringBuilder.append: ()
23, pop
24: iinc 3, 1
27: goto 10
30: aload 2
31: invokevirtual #6; jj StringBuilder.toString: ()
34: areturn
No slo es el cdigo de bucle ms corto y ms simple, sino que adems el mtodo slo crea un nico objeto StringBuilder.
La creacin de un objeto StringBuilder explcito tambin nos pemlite preasignar su tamao si disponemos de infonnacin
adiciona l acerca de lo grande que debe ser, con lo cual no es necesario volver a reasignar constantemente el buffel:
Por tanto, cuando creamos un mtodo toString( ), si las operaciones son lo suficientemente simples como para que el com
pilador pueda figurarse el slo cmo hacerlas, podemos generalment e confiar en que el compilador construir el resultado
de una fonna razonable. Pero si hay bucles, conviene utili zar explcitamente un objeto StringBuUder en el mtodo
toString( ), como se hace a continuacin:
11 : stringsjUsingStringBuilder.java
import java . util.*;
public class UsingStringBuilder
public static Random rand = new Random(47)
public String toString () {
StringBuilder result = new StringBuilder(" (11);
for{int i = Oi i < 25; i++) {
result.append(rand,nextInt{lOO)) ;
result .append (", " ) j
result .delete (result .length () -2, result .length () ) ;
result.append("l ") i
return result.toString();
public static void main(String(] args) {
UsingStringBuilder usb = new UsingStringBuilder();
System.out.println(usb) i
/ * Output:
13 Cadenas de caracteres 321
[58, 55, 93, 61, 61, 29, 68, O, 22, 7, 88, 28, 51, 89, 9, 78, 98, 61, 20, 58, 16, 40,
11, 22, 4]
*///,-
Observe que cada parte del resultado se aade con una instmccin append( ). Si tratamos de seguir un atajo y hacer algo
como append(a + ": " + e), el compilador saldr a la palestra y comenzar a construir de nuevo ms objetos StringBuilder.
Si ti ene duda acerca de qu tcnica utilizar, siempre puede ejecutar javap para comprobar los resultados.
Aunque StringBuilder dispone de un conjunto completo de mtodos, incluyendo insert(), replace(), substring( ) e inclu-
so reverse( l , los que generalmente se usan son append( ) y toString( ). Observe el uso de delcte( ) para eliminar la lti-
ma coma y el ltimo espacio antes de aadir el corchete de cierre.
StringBuilder fue introduci do en Java SE5. Antes de esta versin, Java utili zaba StringBuffer, que garantizaba la seguri-
dad en lo que respecta a las hebras de programacin (vase el Captul o 21 , Concurrencia) y era, por tanto, significati vamen-
te ms caro en trminos de recursos de procesami ento. Por tanto, las operaciones de manej o de caracteres en Java SE5/6
deberan ser ms rpidas.
Ejercicio 1: (2) Anali ce SprinklerSystem.toString( ) en reusing/SprinklerSystem.java para descubrir si escri bir el
mtodo toString( ) con un mtodo StringBuilder explcito pennitira abarrar operaciones de creacin de
objetos StringBuilder.
Recursin no intencionada
Puesto que los contenedores estndar Java (al igual que todas las dems clases) heredan en ltimo trmino de Object, todos
ell os contienen un mtodo toString( ). Este mtodo ha sido sustituido para que los contenedores puedan generar una repre-
sentacin de tipo String de s mi smos, incluyendo los objetos que almacenan. ArrayList.toString( ), por ejemplo, recorre
los elementos del objeto Arr.yList y llama a toString() para cada uno de ell os:
ji : stringsjArrayListDisplay.java
import generics.coffee.*
import java.util.*
public class ArrayListDisplay
public static void main(String[] args) {
ArrayList<Coffee> coffees = new ArrayList<Coffee>() i
for (Coffee e new CoffeeGenerator(lO))
coffees.add(c}
System. out.println{coffees) i
/* Output:
322 Piensa en Java
[Americano 0, Latte 1, Americano 2, Mocha 3, Mocha 4, Breve S,
Americano 6, Latte 7, Cappuccino 8, Cappuccino 9]
* /// ,-
Suponga que quisiramos que el mtodo toString( ) imprimiera la direccin de la clase. Parece que tendra bastante s n t i ~
do hacer referencia simplemente a this :
jj : stringsjlnfiniteRecursion . java
jj Recursin accidental.
// {RunByHand}
import java.util.*;
public class InfiniteRecursion
public String toString () {
return 11 Inf initeRecursion address: " -+ this -+ " \n";
}
public static void main (String [] args ) {
List<InfiniteRecursion> v =
}
new ArrayList<InfiniteRecursi on> () ;
f or(int i = O; i < la; i-+-+)
v.add(new InfiniteRecursion() ) ;
Sys tem. out .println (v) ;
11/ , -
Si creamos un objeto InfiniteRecursion y luego lo imprimimos, obtendremos una secuencia muy larga de excepciones. Esto
tambin se produce si colocamos los objetos InfiniteRecursion en un contenedor ArrayList e imprimimos dicho contene
dar como aqu se muestra. Lo que est sucediendo es una conversin automtica de tipos para las cadenas de caracteres.
Cuando decimos:
"InfiniteRecursion address: 11 -+ this
El compilador ve un objeto String seguido de un smbolo '+' y algo que no es un obj eto Slring, por lo que trata de con-
vertir Ihis a String. El compil ador reali za esta conversin llamando a toString(), que es lo que produce una ll amada recur-
siva.
Si queremos impri mi r la direccin del objeto, la solucin es llamar al mtodo toString() de Object. y hace preci samente
eso. Por tanto, en lugar de especi ficar Ihis, lo que tendramos que escribir es super.toString().
Ejercicio 2: (1) Corrija el error de lnfiniteRecursion.java.
Operaciones con cadenas de caracteres
He aqu algunos de los mtodos bsicos disponibles para objetos String. Los mtodos sobrecargados se resumen en una
nica fila:
Mtodo Argumentos, sobrecarga l USO
Constructor Sobrecargado: predetenninado, String, Creacin de objetos String.
StringBuilder, StringBuffer, matrices char,
matrices byte.
length( ) Nmero de caracteres en el objeto String.
charAt( ) ndice nt El carcter char en una posicin dentro del
objeto String.
getChars( ), gelBytes( ) El principio y el final del que hay que copiar, Copia caracteres o bytes en una matriz ex
la matriz a la que hay que copiar, un ndice a tema.
la matriz de dest ino.
13 Cadenas de caracteres 323
Mtodo
Argumentos, sobrecarga Uso
IOCbarArray( ) Genera un char[] que contiene los caracteres
contenidos en el objeto String.
equals( ), Un objeto String con el que comparar. Una comprobacin de igualdad de los conteni-
equals-lgnoreCase( ) dos de dos objetos String.
compareTo( ) Un objeto St ring con el que comparar. El resultado es negativo, cero o positivo
dependiendo de la ordenacin lexicogrtica
del objeto String y del argumento. Las
maysculas y minusculas no son iguales!
contains( ) Un objeto CharSequence que buscar. E! resultado es true si el argumento est con-
tenido en el objeto String.
contentEquals( ) Un objeto CbarSequence o St ringBuffer con El resultado es true si hay una corresponden-
el que comparar.. cia exacta con el argumento.
equalslgnoreCase( ) Un objeto String con el que comparar .. El resultado es true si los contenidos son
iguales, sin tener en cuenta la diferencia entre
maysculas y minsculas.
rcgionMatcbes( ) Desplazamiento dentro de este objeto String, Un resultado booleano que indica si la regin
el otro objeto Stri ng junto con su desplaza- se corresponde.
miento y la longitud para comparar. La
sobrecarga aiiade la posibilidad de "ignorar
maysculas y minsculas".
starlSWith( ) Objeto String con el que puede comenzar. La Un resultado booleano que indica si el objeto
sobrecarga aiiade el desplazamiemo dentro de String comienza con el argumento.
un argumento.
endsWith( ) Objeto Slring que puede ser sufijo de este Un resultado booleano que indica si el argu-
objeto String. mento es un sufijo.
indexOf( ), Sobrecargado: char, char e ndice de inicio, Devuel ve - 1 si el argumento no se encuentra
lastlndexOf( ) String. String e ndice de inicio. dentro de este objeto String; en caso contra-
rio, devuelve el ndice donde empieza el argu-
mento. lastlndexOf( ) busca hacia atrs el
final.
substring( ) Sobrecargado: ndice de micio: ndice de ini- Devuelve un nuevo objeto String que conlie-
(tambin subScquencc( cio + ndice de fin. ne el conjunto de caracteres especificado.
concat( ) El objeto String que haya quc concatenar. Devuel ve un nuevo objeto StriDg que cOlllie-
ne los caracteres del objeto String original
seguidos de los caracteres del argumento.
replace( ) El antiguo canlcter que hay que buscar, el Devuelve un nuevo objeto String en el que se
nuevo carcter con el que hay que reempla- han efectuado las sustituciones. Utiliza el anti-
zarlo. Tambin puede reemplazar un objeto gua objeto String si no se encuentra ninguna
CharSequeoce con otro objeto correspondencia.
CharSequence.
toLowerCase( ) Devuelve un lluevo objeto String en el que se
toUpperCase( ) habrn cambiado todas las letras a minsculas
o maysculas. Utiliza el antiguo objeto String
si no es necesario realizar ningn cambio.
trim( )
Devuelve un nuevo objeto String eliminando
los espacios en blanco de ambos extremos.
Utiliza el antiguo objeto String si no es nece-
sario realizar ningn cambio.
324 Piensa en Java
Mtodo Argumentos, sobrecarga Uso
valueDr( l Sobrecargado: Object, charl! . charll y des- Devuclvc un objeto String que contiene una
plazamiento y recuento, boolean. charo int . representacin en fonna de caracteres del
long, noat. double. argumento.
intern( ) Produce una y slo una referencia a un objeto
String por cada secuencia de caracteres distinta.
Puede ver que todo mtodo de String devuelve, cuidadosamente. un nuevo objeto String cuando es necesario cambiar los
contenidos. Observe tambin que, si los contenidos no necesitan modificarse. el mtodo se limita a devol ver una referencia
al objeto String original. Esto ahorra espacio de almacenamiento y recursos de procesamiento.
Los mtodos String en los que estn impli cadas expres;ones regulares se explican ms adelante en este captulo.
Formateo de la salida
Una de las caractersticas ms esperadas que ha sido finalmente incorporada a Java SES es el formateo de la sa lida al esti-
lo de la instruccin printf() de C. No slo pemte esto simplificar el cdigo de salida. sino que tambin proporciona a los
desarrolladores Java una gran capacidad de control sobre el fomlato a la alineacin de esa sa lida.:!
printfO
La funcin printf() de e no ensambla las cadenas de caracteres en la forma en que lo hace Java, sino que toma una nica
cadena deformato e inserta valores en ell a. efectuando el f0n11ateo a medida que lo hace. En lugar de utili zar el operador
sobrecargado .+. (que el lenguaje C no sobrecarga) para concatenar ellexto entrecomillado y las variables. printf( ) utili-
za marcadores especiales para indicar dnde deben insertarse los datos. Los argumentos que se insenan en la cadena de for-
mato se indican a continuacin mediante una li sta separada por comas. Por ejemplo:
printf("Row 1: [%d %fl\n", x, y )
En tiempo de ejecucin. el valor de x se inserta en /od y el va lor de y se inserta en %f. Estos contenedores se denominan
especificadores de formato Y. adems de decirnos dnde se debe insertar el valor, tambin nos infonnan del tipo de varia-
ble que hay que insertar y de cmo hay que fonnatearla. Por ejemplo. el marcador '%d' anterior dice que x es un entero.
mientras que ' Olor dice que y es un valor de punto flotante (float o double).
System,out.format( )
Java SES introdujo el mlodo format( l, disponible para los objelos PrintStream o PrintWriter (de los que hablaremos
ms en deta ll e en el Captulo 18. EIS). enlre los que se incluye System.o"t. El mtodo format() est modelado basndose
en la funcin printf() de C. Existe incluso un mtodo printf( ) que pueden utilizar aquellos que se sientan nostlgicos, que
simplemente invoca format( ). He aqu un ejemplo simple:
JJ : strings / SimpleFormat. j ava
public class SimpleFormat {
public static void main (String (J args ) {
int x ::: 5
double y : 5.332542,
JJ A la antigua usanza:
System. out .println ( "Row 1, [" + x +
"
/1
A la nueva usanza:
System. out. format ( "Row 1, [\d UI \ n",
// o
+ y + "] " ) ;
x, y ) ,
2 Mark Welsh ha ayudado en la creacin de esta seccin. asi como en la seccin de la entrada".
Systern.out.printf(tlRow 1: [%d %fl\n", X, y);
1* Output :
Row 1, [5 5. 332542J
Row 1, [5 5.3325421
Row 1, [5 5. 332542J
* /// ,-
13 Cadenas de caracteres 325
Puede ver que format() y printf() son equivalentes. En ambos casos, hay una nica cadena de ronnata, seguida de un argu-
mento para cada especificador de fonnato.
La clase Formatter
Toda la nueva funcionalidad de fonnateo de Java es gestionada por la clase Formatter del paquete java.utU. Podemos COll-
siderarFormatter como una especie de traductor que convierte la cadena de fonnato y los datos al resultado deseado.
Cuando se crea un objeto Formatter, se le indica a dnde queremos que se manden los resultados. pasando esa infon11a-
cin al constmctor:
//: strings/Turtle.java
import java.io.*;
import java.util.*;
public class Turtle
private String name;
private Formatter f;
public Turtle (String name, Formatter f) {
this.name : name;
this.f = fi
public void move (int x, int y) {
f. format ("%s The Turtle is at (%d, %d) \n", name, x, y);
public static void main(String[] args)
PrintStream outAlias = System.out
Turtle tommy = new Turtle ("Tommy",
new Formatter(System.out))
Turtle terry = new Turtle ("Terry",
new Formatter(outAlias))
tommy.move(O,O) ;
terry.move{4,8)
tommy.move(3,4)
terry.move{2, 5)
tommy.move(3,3)
terry.move(3,3)
/*
Output:
Tommy The Turtle is at (O, O)
Terry The Turtle is at (4,8)
Tommy The Turtle is at (3,4)
Terry The Turtle is at (2,5)
Tommy The Turtle is at (3,3)
Terry The Turtle is at (3,3)
* /// >
Toda la salida representada por tommy va a System.out mientras que la representa por terry va a un alias de Systcm.out.
El constructor est sobrecargado para admitir di versas ubicaciones de sa lida, pero las ms til es son PrintStream (como en
el ejemplo), OulputStre.m y File. Veremos ms detalles sobre esto en el Capitulo 18, En/rada/salida.
Ejercicio 3: (1) Modifique Turtle.java de modo que envie toda la salida a System.err.
326 Piensa en Java
El ejemplo anterior uti liza un nuevo especificador de fommto, -%s'. Este marcador indica un argumento de tipo Stri ng y
es un ejemplo del tipo ms si mple de especificador de fonnato: uno que slo tiene un tipo de conversin.
Especificadores de formato
Para controlar el espaciado y la alineacin cuando se insertan los datos, hacen falta especificadores de fomlato ms elabo-
rados. He aqu la sintaxi s ms general:
% [i ndice_argumentoJ [indicadores] [anchura] [.precisin] conversin
A menudo, ser necesario controlar el tamao mnimo de un campo. Esto puede realizarse especificando una anchura. El
objeto Formatter garantiza que un campo tenga al menos una anchura de un cierto nmero de caracteres, rellenndolo con
espacios en caso necesario. De manera predeterminada, los datos se justifican a la derecha, pero esto puede cambiarse inclu-
yendo ;-' en la seccin de indicadores.
Lo contrario de la anchura es la precisin, que se utili za para especificar un mximo. A diferencia de la anchura, que es
aplicable a todos los tipos de conversin de datos y se comporta de la misma manera con cada uno de elJos, precisin tiene
un significado di stinto para los diferentes tipos. Para las cadenas de caracteres, la precisin especifica el nmero mximo
de caracteres del objeto Str ing que hay que imprimir. Para los nmeros en coma flotante, precisin especifica el nmero de
posiciones decimales que hay que mostrar (el valor predeterminado es 6), efecntando un redondeo si hay ms dgitos o aa-
diendo ms ceros al final si hay pocos. Puesto que los enteros no tienen parte fraccionaria, precisin no es aplicable a ellos
y se generar una excepcin si se utili za el argumento de preci sin con un tipo de conversin entero.
El siguiente ejemplo utili za especifi cadores de formato para imprimir una factura de la compra:
// : strings/Receipt.java
import java . util .*
public c lass Receipt
private double cotal = O;
private Formatter f = new Formatter (Syscem.out );
public void printTitle () {
f . format( "% - 15s %56 %lOs\n", "Item", "Qty", "Price
ll
)
f.format ( "%-15s %56 %lOs\n ", tI _____ " )
public void print (St ring name, int qty, double price) {
f.format ( "%-15.1Ss %5d %lO .2f \n", name, qty, price);
total += price;
public void printTotal()
f.format ("%-15s %5s %10.2f\n", "Tax", " ", tocal*0.06);
f.format("%-15s %5s %10s\n", "", "", "-----");
f. format (" %-155 %55 %10. 2f\n", "Total",
total * 1.06);
public static void main{String[] arg6)
Receipt receipt = new Receipt();
receipt.printTitle{) ;
1*
Item
receipt. print ("Jack' s Magic Beans", 4, 4.25);
receipt. print ( " Princess Peas", 3, 5.1);
receipt .pri nt("Three Bears Porridge", 1, 14.29);
receipt.printTotal{) ;
Output:
Qty Price
Jack's Magic Be 4 4.25
Princess Peas 3 5.10
Three Bears Por 1 14.29
Tax 1. 42
13 Cadenas de caracteres 327
25.06
Como puede ver, el objeto Formatter proporciona un considerable grado de control entre el espaciado y la alineacin, con
una notacin bastante concisa. Aqu, las cadenas de fannato se copian simplemente con el fin de producir el espaciado apro-
piado.
Ejercicio 4:
(3) Modifique Receipt.java para que todas las anchuras estn controladas por un nico conjunto de valo-
res constantes. El objetivo es poder cambiar fcilmente una anchura modificando un nico valor en un
determinado lugar.
Conversiones posibles con Formatter
stas son las conversiones con las que ms frecuentemente nos podremos encontrar:
Caracteres de conversin
d Entero (como decimal)
e Carcter Uni code
b Valor booleano
s Cadena de caracteres
f Coma flotante (como decimal)
e Coma fl otantes (en notacin cientifica)
x Entero (como hexadecimal)
h Cdigo IwsJ (as hexadeci mal )
% Literal " %"
He aqu un ejemplo que muestra estas conversiones en accin:
jj: strings;Conversion .java
import java.math.*
import java.util.*
public class Conversion
public static void main{String [] args) {
Formatter f = new Formatter(System.out)
char u = 'a I
System.out.println(tlu = 'a' ti)
f. format ( tls: %s\n", u);
JJ f.format("d: %d\n", u) i
f.format( "c: %c\n", u)
f.format("b: %b\n", u)
11 f.formatl"f, %f\n", u);
JI f.format("e: %e\n", u)
JI f.format ( "x: %x\n", u);
f.format{"h: %h\n", u);
int v = 121;
System. out. println ("v 121") ;
328 Piensa en Java
f.format {"d: %-d \ nll, v ) ;
f.format ( "c: %c \ n", v ) ;
f.format. ( l1b: %b \ nl1, v ) ;
f.format {"s: %s \ n", v )
11 f.format l "f, H\n", v ) ;
II f.format(lIe: %-e \ n", v};
f.format("x: %x\n", v )
f.format("h: %h\n", v);
Biglnteger w = new Biglnteger(1150000000000000
1'
}
System.out.println (
"w = new Biglnteger (\ "50000000000000 \ " ) " ) ;
f. format ( lid: %d\ nll, w) ;
II f.format ( "c: %-c \ n", w) ;
f.format ( lIb: %b\ n", w) ;
f.format {"s: %s \ n", w) ;
II f.format("f: %f \ n", w) ;
II f.format {"e: %e \ n", w) ;
f . format ( " x: %-x \ n ", w) ;
f.format ( "h: %h\n", w) ;
double x = 179.543;
System.out.println(lI x = 179.543 " );
1/ f.format("d: %d\n " , x);
1/ f.format("c : %c\n", x);
f.format("b: %b\n", xl;
f.format{"s: %s\n", xl;
f.format {"f: %-f \ n", x l ;
f.format(lIe: %e\nll, x l ;
II f.format ( lIx: %x\ n
ll
, x l ;
f.format ( "h: %-h \ n", x l
Conversion y = new Conversion () ;
System. out.println ( "y = new Conversion () " ) ;
II f. format ( lid : %d\ n ti, y ) ;
II f.format ( "c: %c\ n", y l ;
f.format ( "b: %b\ n", y ) ;
f.format ( lIs: %s \ n", y ) ;
11 f. format 1" f , H \ n", y ) ;
I I f.format("e: %-e\n", y};
1/ f.format{"x: %x\n", y);
f.f o rmat("h: %h\ nll, y ) ;
boolean z = false;
System.out.println ( "z = false" ) ;
1/ f.format ( "d: %d\ n", z ) ;
II f.format ( "c: %c \ n", z ) ;
f.format ( "b: %b\ n", z ) ;
f . format ( "s: %s \ n", z ) ;
/1 f.format l "L H \ n", z ) ;
II f.format ( "e: %e \ n", z ) ;
II f.format ( "x: %x\ n", z ) ;
f.format {"h: %h\ n", z ) ;
1* Output: (Sample )
u = 'a'
s: a
e: a
b: true
h, 61
v = 121
d, 121
e: y
b: true
s: 121
x: 79
h, 79
w = new Biglnteger("SOOOOOOOOOOOOO" )
d, 50000000000000
b: true
s, 50000000000000
x, 2d79883d2000
h: 8842ala7
x = 179.543
b: true
s: 179.543
f, 179.543000
e: 1 . 795430e+02
h: lef462c
y = new Conversion ()
b: true
s: Conversion@9cab16
h: 9cab16
z = false
b: false
s: false
h, 4d5
, /// ,-
13 Cadenas de caracteres 329
Las lneas comentadas muestran conversiones que no son vlidas para ese tipo de variables concreto, ejecutarlas haran que
se generar una excepcin.
Observe que la conversin ' b' funciona para cada una de las variables anteriores. Aunque es v lida para cualquier tipo de
argumento. puede que no se comporte C0l110 cabra esperar. Para las primitivas boolean o los objetos de tipo boolean, el
resultado ser true o false, segn corresponda. Sin embargo, para cua lqui er otro argumento, siempre que el tipo de argu-
mento no sea nulJ. el resultado ser siempre truco Incluso el valor numrico de cero, que es sinnimo de false en muchos
lenguajes (incluyendo C), generar true. de modo que tenga cuidado cuando utilice esta conversin con tipos no booleanos.
Existen otros tipos de conversin y otras opciones de especificador de fonnato ms extraos. Puede consultarlos en la docu-
mentacin del JDK para la clase Formatter.
Ejercicio 5: (5) Para cada uno de los tipos bsicos de conversin de la tabla anterior, escriba la expresin de formateo
ms compleja posible. Es decir, utilice todos los posibles especificadores de fornlato disponibles para
dicho tipo de conversin.
String.format( )
Java SE5 tambin ha tomado prestado de e la idea de sprintf(), que es un mtodo que se utiliza para crear cadenas de
caracteres. String.format( ) es un mtodo esttico que toma los mismos argumentos que el mtodo format() de Formatter
pero devuelve un objeto String. Puede resultar til cuando slo se necesita invocar una vez a format( ):
11 : strings/DatabaseException.java
public class DatabaseException extends Exception {
public DatabaseException ( int transactionID, int queryID,
String messagel {
super(String.format{ti (t%d, q%d) %SU, transactionID,
queryID, message});
330 Piensa en Java
public static void main(String(] args)
try (
throw new DatabaseException(3, 7, "Write failed");
catch(Exception el {
System.out . println(e) ;
1* Output:
DatabaseException: (t3, q7) Wri te failed
, ///,-
Entre bastidores, todo lo que Striog.format() hace es iostanciar el objeto Forma!!er y pasarle los argumentos que haya-
mos proporcionado, pero utili zar este mtodo puede resultar a menudo ms claro y ms fcil que hacerlo de fonna manual.
Una herramienta de volcado hexadecimal
Como segundo ejemplo, a menudo nos interesa examinar los bytes que componen un archivo binario utilizando formato
hexadecimal. He aqu una pequea utilidad que muestra una matriz binaria de bytes en un formato hexadecimal legibl e, uti-
li zando Striog.format():
11 : net/mi ndview/util/Hex .java
package net . mindview.ut il;
impor t java . io. *
public class Hex {
public static String f ormat (byte (] data) {
StringBui l der result = new StringBuilde r() ;
int n = O;
for (byte b , data) {
if (n % 16 == O)
r e sult.append(String.format( U%05X: n);
r e sult . append(String .format( " %02X ", b);
n ++ ;
if(n % 16 = = O) r esult . append(U\n
U
);
result.append{ u\n
U
) ;
return resul t .toSt ring() i
public static void main(String[] args) throws Exception
if(args . length == O)
II Comprobar mostrando este archivo de clase:
System.out.println(
format(BinaryFile.read{UHex class
U
) i
el se
System. out.println(
format{BinaryFile.read{new File{args[O] ;
/ ,
Output: (Sample)
00000 , CA FE BA BE 00 00 00 31 00 52 DA 00 05 00 22
00010 , 00 23 DA 00 02 00 22 08 00 24 07 00 25 DA 00
00020, 00 27 DA 00 28 00 29 DA 00 02 00 2A 08 00 2B
00030, 00 2C 00 2D 08 00 2E DA 00 02 00 2F 09 00 30
0004 O, 31 08 00 32 DA 00 33 00 34 DA 00 15 00 35 DA
00050, 36 00 37 07 00 38 DA 00 12 00 39 OA 00 33 00
* ///,-
07
26
DA
00
00
3A
Para abrir y leer el archi vo binario, este programa presenta otra ut ilidad que se presentar en el Captulo 18, En/radal
salida: net.mindview.utiI.BinaryFile. El mtodo read( ) devuelve el archi vo compl eto como una matri z de tipo byte.
13 Cadenas de caracteres 331
Ejercicio 6: (2) Cree una clase que contenga campos inl. long, noal y double, Cree un mtodo loSlring( ) para esta
clase que utilice String.format(), y demuestre que la clase funciona correctamente.
Expresiones regulares
Las expresiones regulares han sido durante mucho tiempo parte integrante de las utilidades estndar Unix como sed yawk,
y de lenguajes como Pylhon y Perl (algunas personas piensan incluso que las expresiones regulares son la principal razn
del xito de Perl) . Las herramientas de manipulacin de cadenas de caracteres estaban anterionnente delegadas a las clases
Slring. Sl ringBuffer y StringTokenizer de Java. que disponian de funcionalidades relativamente simples si las compara-
mos con las expresiones regulares.
Las expresiones regulares son herramientas de procesamiento de texto potentes y flexibles. Nos pCn11iten especificar.
mediante programas, patrones complejos de texto que pueden buscarse en una cadena de entrada. Una vez descubiertos estos
patrones, podemos reaccIOnar a su aparicin de la fonna que deseemos. Aunque la sintaxis de las expresiones regulares
puede resultar intimidante al principio. proporcionan un lenguaje compacto y dinmico que puede emplearse para resolver
todo tipo de tareas de procesamiento, comparacin, seleccin, edicin y verificacin de cadenas de una fonna general.
Fundamentos bsicos
Una expresin regular es una fonna de describir cadenas de caracteres en tnnmos generales, de modo que podemos decir:
"Si una cadena de caracteres contiene estos elementos, entonces se corresponde con lo que estoy buscando", Por ejemplo,
para decir que un nmero puede estar o no precedido por un signo menos, escribimos el signo menos seguido de un signo
de interrogacin de la fonna siguiente:
-?
Para describir un entero. diremos que est compuesto de uno o ms digitos. En las expresiones regulares, un dgito se des-
cribe mediante '\d', Si tiene experiencia con las expresiones regulares en otros lenguajes, obsenrar inmediatamente la dife-
rencia en la fomla de gestionar las barras inclinadas, En otros lenguajes, '\\' significa: '"Quiero insertar una barra incli nada
a la izquierda normal y corriente (literal) en la expresin regular, No le asignes ningn significado especial", En Java, '11'
significa: "Estoy insertando una barra inclinada de expresin regular, por lo que el siguiente carcter tiene un significado
especia!". Por ejemplo, si queremos indicar un dgito, la cadena de la expresin regular ser '\\d' , Si deseamos insertar una
barra inclinada literal, tendremos que escribir '\\\\', Sin embargo, elementos tales como de nueva lnea y de tabulacin uti-
lizan una barra inclinada simple: ' \n\t',
Para indicar "una o ms apariciones de la expresin precedente'\ se utiliza un smbolo '+', Por tanto, para decir "posible-
mente un signo menos seguido de uno o ms dgitos", escribiramos:
-?\\d+
La fonna ms simple de utilizar las expresiones regulares consiste en utilizar la funcionalidad incluida dentro de la clase
String. Por ejemplo, podemos comprobar si un objeto String se corresponde con la expresin regular anterior:
ji: strings/lntegerMatch.java
public class IntegerMatch {
public static void main(String[] args) {
System.out.println("-1234".matches("-?\\d-t")) ;
System.out.println(15678
I
,matches("-?\\d-t")) i
System.out.println("+911",matches(II-?\\d+
II
)) ;
System.out.println("+911".matches("(-I\\-t)?\\d+")) ;
/ * Output:
true
true
false
true
* / / / >
332 Piensa en Java
Las primeras dos expresiones se corresponden, pero la tercera comi enza con un ' +', que es un nmero leg timo pero que no
se aj usta a la expresin regul ar. Por tanto, necesitamos una fonna de decir "puede comenzar con un + o un -". En las expre-
siones regulares. los parntesis tienen el efecto de agrupar una expresin. y la barra verti cal '1' signifi ca OR (di syuncin).
Por lanto.
( - 1\\ + ) 7
qui ere decir que esta parte de la cadena de caracteres puede ser un ~ o un ' +' o nada (debido al '?' ). Puesto que el carc-
ter '+' ti ene un signifi cado especial en las expresiones regul ares. es necesario introducir la secuencia de escape '\\' para que
aparezca como un carcter normal dentro de la expresin.
Una herramienta til de expresiones regul ares incorporada en String es split(), que signjfi ca "partir esta cadena de carac-
teres juslO donde se produzcan las correspondenci as con la expresin regul ar indicada".
/f : strings f Splitting.java
import java.util. *
publ ic class Splitting (
public static String knights
"Then, when you have found the shrubbery, you must " +
"cut down the mightiest tree in the forest... It +
"with ... a herring! "
public static void split(String regex)
System.out.println(
Arrays . toString{knights.split{regex) ) ) ;
public static void main (String [] args) {
split ( " II ) II No tiene porqu contener caracteres regex
split ( " \\w+" ) II Caracteres no pertenecientes a una palabra
split(lIn\\W+") 1/ 'n ' seguida de caracteres no
II pertenecientes a una palabra
/ * Output :
[Then" when, you, have, found, the, shrubbery" you, must, cut, down , the, migh tiest,
tree, in, the, foresto .. , with .. . , a, herring!]
[Then, when, you, have, found, the, shrubbery, you, must, cut, down, the, mightiest,
tree, in, the, forest, with, a, herring]
(The, whe, you have found the shrubbery, you must cut dow, the mightiest tree i, the
forest. .. wi th. .. a herring!]
* /// , -
En primer lugar, observe que puede utili zar caracteres nomlales como expresiones regulares, una expresin regul ar no tiene
porqu cont ener caracteres especial es. como podemos ver en la primera ll amada a split (), que simpl emente efecta la par-
ti cin de acuerdo con los espacios en blanco.
La segunda y tercera llamadas a split() utili zan '\W', que represent a caracteres que no pertenezcan a palabras (la versin
en minsculas, " w', representa un carcter perteneciente a una palabra); podr ver que los signos de puntuacin han sido
eliminados en el segundo caso. La tercera ll amada a split( ) dice. "la letra n seguida de uno o ms caracteres que no perte-
nezcan a palabras". Podr ver que los patrones de di visin no aparecen en el resultado.
Una versi n sobrecargada de String.split( ) nos pennite limitar el nmero de di visiones que hayan de producirse.
La ltima de las herramieOlas de expresiones regulares incorporada en String es la de sustitucin. Podemos sustiulir la pri-
mera apari cin o todas elJas:
// : strings/ Replacing . java
import static net.mindview.util.Print. *
public class Replacing {
static String s = Splitting . knights
public static void main(String[] args)
print (s. replaceFirst ( " f \ \ w+ It, tt located" ) )
13 Cadenas de caracteres 333
print(s.replaceAll("shrubbery!treelherring","banana
ll
) i
/ * Output:
Then, when yau have located the shrubbery, you must cut down the mightiest tree in the
forest ... with . .. a herring!
Then, when you have found the banana, you must cut down the mightiest banana in the
forest ... with ... a banana!
* /// , -
La primera expresin se corresponde con la letra f seguida de uno o ms caracteres de palabras (observe que el carcter w
est en minscula esta vez). Slo sustituye la primera correspondencia que encuentra, por lo que la palabra "found" ha sido
sust ituida por la palabra "Iocated".
La segunda expresin se corresponde con cualquiera de las tres pa labras separadas por las barras vert icales que representan
la operacin OR, y sustituye todas las correspondencias que encuentra.
Ms adelante veremos que las expresiones regulares que no son de tipo String di sponen de herrami entas de sustitucin
ms potentes; por ejemplo, se pueden invocar mtodos para llevar a cabo las sustituciones. Las expresiones regulares que
no son de tipo String tambin son significativamente ms eficientes cuando hace falta utilizar la expresin regular ms de
una vez.
Ejercicio 7:
Ej ercicio 8:
Ejercicio 9:
(S) Utilizando la documentacin de java. util.regex.Pattern como referencia, escriba y pruebe una expre-
sin regular de prueba que compruebe una frase para ver si comienza con una let ra mayscula y tennina
con un punto.
(2) Divida la cadena Splitting.knights por las palabras "the" o "you".
(4) Utilizando la documentacin de j ava. util.regex.Pattern como referencia, sustituya todas las vocales
de Splitting. knights por guiones bajos.
Creacin de expresiones regulares
Podemos comenzar a aprender expresiones regulares con un subconjunto de las estrucUlras posibles. En la documentacin
del JDK correspondiente a la clase Pattern de java.ut il .regex podr encontrar la li sta completa de las estrucUlras que pue-
den emplearse para construir las expresiones regulares.
Caracteres
B El carcter especfico B
\xhh Carcter con el valor hexadecimal Oxhh
luhhhh El carcter Unicode con la representacin hexadecimal Oxhhhh
II Tabulador
In Nueva lnea
Ir Retomo de carro
Ir Avance de pgina
le Escape
La potencia de las expresiones regulares comienza a hacerse patente cuando se definen clases de caracteres. He aqu algu-
nas fonnas tpicas de crear clases de caracteres, junto con algunas clases predefinidas:
334 Piensa en Java
Clases de caracteres
Cualqui er carcter
label Cualquiera de los caracteres a, b o (' (lo mi smo que alblc)
I' abel Cualquier carcter excepto a, b y e (negacin)
la-zA-ZI Cualquier carcter de la a a la z o A a la Z (rango)
labcl hij ll Cualquiera de a.b.c,b,i.j (lo mismo que . lblelbli) (unin)
la-z&&lhijll Puede ser h. i o j (interseccin)
Is Un carcter de espaciado (espacio. tabulador, nueva linea, avance de pgina. retomo de carro)
IS Un carcter que no sea de espaciado (I"\s))
Id Un dgito numrico (0-91
ID Un carcter que no sea un dgito 1"' 0-91
\w Un carcter de palabra (a-zA-Z_0-91
\W Un carcter que no sea de palabra ("'\wl
Lo que se muestra aqu es slo un ejemplo; consultando la pgma de documentacin del JDK correspondiente a
java.util .regex. Pattern podr conocer todos los posibles patrones de expresiones regulares.
Operadores lgicos
XV X seguido de Y
XIY XoY
(X) Un grupo de captllra. Puede referirse posteriormente al
i-simo grupo capturado en la expresin mediante \i.
Localizadores de contorno
,
Comi enzo de lnea
$ Fin de linea
lb Frontera de palabra
\B Frontera de no palabra
IG Fin de la correspondencia anterior
Por ejemplo, cada una de las siguientes expresiones penni te localizar la secuencia de caracteres "Rudolph":
jj , strings j Rudolph.java
public class Rudolph {
public static veid main (String [] args l {
fer (String pattern : new String [] { "Rudolph",
13 Cadenas de caracteres 335
"[rRludolph", "[rR] [aeioul [a-z]ol.*", "R.*" })
System. out .println (II Rudo lph" . mat c hes (pattern) ) ;
/ * Output:
t rue
true
true
t r ue
* /// , -
Por supuesto, el objetivo no debe ser crear la expresin regular ms complicada sino la que sea ms simple y baste para rea-
lizar la tarea que tengamos entre manos. Una vez que comience a escribir expresiones regulares, podr ver cmo a menudo
conviene referirse a los ejemplos de cdigo escritos anterionnente, para facilitar la escritura de nuevas expresiones regu-
lares.
Cuantificadores
Un cuantificador describe la forma en que un patrn absorbe el texto de entrada:
Avaricioso: los cuantificadores son avariciosos a menos que se los modifique de alguna manera. Un expresin
avariciosa trata de encontrar el mximo nmero posible de correspondencias para el patrn indicado. Una causa
bastante comn de problemas consiste en suponer que el patrn slo se corresponder con el primer grupo de
caracteres, cuando lo cierto es que se trata de un patrn avaricioso y continuar procesando texto hasta que baya
logrado establecer una correspondencia con la cadena de caracteres ms larga posible.
Reluctante: especificado con un signo de interrogacin, este cuantificador hace que la correspondencia se esta-
blezca con el nmero mnimo de caracteres necesario para satisfacer el patrn. Tambin se denomina perezoso,
de correspondencia mnima o no avaricioso.
Posesivo: en la actualidad, este lipo de cuantificador slo est di sponible en Java (no en otros lenguajes) y es ms
avanzado, por lo que es posible que no lo utilice al principio. A medida que se aplica una expresin regular a una
cadena de caracteres, la expresin genera mltiples estados para poder retroceder si la correspondencia falla. Los
cuantificadores posesivos no conservan dicbos estados intennedios, evitando as el retroceso. Pueden utilizarse
para impedir que una expresin regular quede fuera de control y tambin para hacer que se ejecute de manera ms
eficiente.
Avaricioso I Reluctante Posesivo Correspondencia con
X? X?? X?+ X, una o ninguna
X* X*? X*+ X, cero o ms
X+ X+? X++ X, una o ms
X{n} X{n}? X{n}+ X, exactamente n veces
X{n,} X{n,}? X{n, }+ X, al menos n veces
X{n,m} X{n,m} ? X{n,m} + X, al menos n pero no ms de m veces
Recuerde que la expresin 'X' necesitar a menudo encerrarse entre parntesis para que funcione de la fomla deseada. Por
ejemplo:
abe ...
podra parecer que se debera corresponder con la secuencia 'abc' una o ms veces, y si la aplicamos a la cadena de entra-
da ' abcabcabc' , obtendremos de hecho tres correspondencias. Sin embargo, lo que la expresin dice en realidad es: "loca-
li za ' ab' seguido de una o ms apariciones de ' c'''. Para buscar correspondencias con la cadena completa 'abc' una o ms
veces, debemos decir:
336 Piensa en Java
(abc)+
Resulta bastante fcil equivocarse al utilizar expresiones regulares; se trata de un lenguaje completamente ortogonal a Java,
que funciona sobre ste y que presenta diferencias con el lenguaje de programacin.
CharSequence
La interfaz denominada CharScquence establece una definicin generalizada de una secuencia de caracteres abstrada de
las clase CharBuffer, String, StringBuffer o StringBuilder:
interface CharSequence
charAt{int i) i
length () ;
subSequence(int start, int end) i
toString()
Dichas clases implementan esta interfaz. Muchas operaciones con expresiones regulares toman argumentos de tipo
CharSequence.
Pattern y Matcher
En general, lo que se hace es compilar objetos de expres in regular en lugar de emplear las utilidades String, que son bas-
tante limit adas. Para ello, importamos java.util.regex, y luego compilamos una expresin regular utilizando el mtodo
static Patter n.compile( j . Esto genera un objeto Pattern basado en su argumento String. Para util izar el objeto Pattern, lo
que se hace es invocar el mtodo matcher( ), pasndole la cadena de caracteres que queremos buscar. El mtodo matcher( )
genera un objeto Matcher, que tiene un conjunto de operaciones de entre las cuales podemos elegir (puede consultar todas
las operaciones en la documentacin del JDK correspondiente a .util.regex.Matcher). Por ejemplo, el mtodo replaceAII()
sustituye todas las correspondencias por el argumento que se proporcione.
Vamos a ver un primer ejemplo: la clase sigui ente puede utili zarse para probar expresiones regulares con una cadena de
entrada. El primer argumento de la lnea de comandos es la cadena de entrada en la que hay que buscar las corresponden-
cias, seguida de una o ms expresiones regulares que haya que aplicar a la entrada. En Unix/Linux, las expresiones regula-
res deben estar entrecomilladas en la lnea de comandos. Este programa puede resultar ti l para probar expresiones regulares
mientras las construimos con el fin de comprobar que esas expresiones establecen las correspondencias deseadas.
11 : strings/TestRegularExpression.java
II Permite probar con facilidad expresiones regulares.
II {Args : abcabcabcdefabc 11 abc+ 11 "(abc)+" " (abc ){2,}" }
import java.util.regex. *
import static net.mindview.util.Print.*
public class TestRegularExpression
public static void main{String[] args)
if(args.length < 2) {
print ( "Usage: \njava TestRegularExpression +
"characterSequence regularExpression+")
System.exit{Q) ;
print(IIInput: \"" + args[Ol + "\"") i
for (String arg : args) {
print (" Regular expression: \"" + arg + "\"" ) i
Pattern p = Pattern.compile(arg) i
Matcher m = p.matcher {args[O]);
while(m.find()) (
print("Match \ "" + m.group() + "\" at positions " +
m.start() + 11_" + (m .end () 1));
} l ' Output,
Input: "abcahcahcdefabc"
Regular expression: "ahcabcabcdefahc"
Match "ahcabcabcdefabc" at positions 0-14
Regular expression: "abc+"
Match "abe" at positions 0-2
Match "abe" at positions 3-5
Match lIabc" at positions 6-8
Match "abe" at positions 12-14
Regular expression: " (abe) +"
Match lIabcabcabc" at positions 0-8
Match "abe" at positions 12-14
Regular expression: " (abc){2 ,}u
Match "abcahcabc " at positions 0-8
' 111 ,-
13 Cadenas de caracteres 337
Un objeto Pattcrn representa la versin compi lada de una expresin regular. Como hemos visto en el ejemplo anterior,
podemos utilizar el mtodo matcher() y la cadena de entrada para generar un objeto Matcher a partir del objeto Pattern
compilado. Pattern tambin tiene un mtodo esttico:
static boolean matches{String regex, CharSequence input)
para comprobar si regex se corresponde con el objeto input de tipo CharSequence utilizado como entrada, y un mtodo
split() que genera una matriz de tipo String despus de descomponer la entTada segn las correspondencias establ ecidas
con la expresin regular regex.
Podemos generar un objeto Matcher invocando Pattern.matcher() con la cadena de entrada como argumento. Despus el
objeto Matchcr se utiliza para acceder a los resultados, utili zando mtodos para evaluar si se establecen o no diferentes
tipos de correspondencias:
boolean matches()
boolean lookingAt()
boolean f ind ()
boolean find(int start)
El mtodo matches( ) tendr xito si el patrn se corresponde con la cadena de entrada completa. mientras que lookingAt( )
tendr xito si la cadena de entrada, comenzando por el pri ncipio, permite establecer una correpondencia con el patrn.
Ejercicio 10: (2) Para la frase 'Java now has expresiones regulares" evale si las siguientes expresiones pennitirn loca-
lizar la correspondencia:
.... Java
\8reg. *
n.w\s+h(ali)s
s?
s'
s+
S(4}
S(l} .
s(a,3}
Ejercicio 11 : (2) Apl ique la expresin regular
(?i) (( A laeiouJ) I (\s+ laeiouJ)) \ w+? laeioul \b
a
"Arline ate eight apples and one orange while Anita hadn' t any"
find( )
Matchcr.find() puede utilizarse para descubrir mltiples correspondencias de patrn en el objeto CharSequence al cual se
aplique, Por ejemplo:
338 Piensa en Java
JI: strings/Finding.java
import java.util.regex.*
import static nec.mindview.util.Print.*;
public class Finding {
public static void main (String [] args) {
Matcher ID = Pattern.compile{"\\w+"l
.matcher(UEvening is full of the linnet's wings
ll
);
while (m. Eind () )
princnb(m.group() +" ")i
print() ;
int i :: O i
whilelm.find(i))
printnb (m .group () + 11 11);
i++;
/ * Output:
Evening is full of the linnet s wings
Evening vening ening ning ing ng 9 i5 is s full full ull 11 1 of of
f the the he e linnet linnet innet nnet net et t s s wings w ~ n s ings ngs 9S s
* /// ,-
El patrn '\\W+' divide la entrada en palabras. find() es como un iterador, que se desplaza hacia adelante a travs de la cade-
na de caracteres de entrada. Sin embargo, la segunda versin de find() puede aceptar un argumento entero que le dice cul
es la posicin del carcter en el que debe comenzar la bsqueda; esta versin reinicial iza la posicin de bsqueda con el
valor de su argumento, como puede ver analizando la salida.
Grupos
Los grupos son expresiones regulares delimitadas por parntesis y a las que luego se puede hacer referencia utilizando su
nmero de grupo. El grupo O indi ca la expresin compl eta, el grupo 1 es el primer grupo entre parntesis, etc. Por tanto, en
A(B(C))D
existen tres grupos: el grupo O es ABCD, el grupo 1 es BC y el ''fUpO 2 es C.
El objeto Malcher dispone de mlodos para proporcionamos informacin acerca de los grupos:
public int groupCounl( ) devuelve el nmero de grupos que hay en el patrn. El grupo O no se incluye dentro de este
recuento.
public Slri ng group( ) devuelve el grupo O (la correspondencia completa) de la operacin anterior de establecimiento de
correspondencias (por ejemplo, find( )).
public String group(inl i) devuelve el nmero de grupo indicado dentro de la operacin de establecimiento de correspon-
dencias anterior. Si esa operacin ha tenido xito, pero el grupo especificado no se corresponde con ninguna parte de la cade-
na de entrada, devuelve el valor null .
public inl slarl(int group) devuelve el ndice de inicio del grupo encontrado en la operacin anterior de establecimiento de
correspondencias.
public int end(int group) devuelve el ndice del ltimo carcter, ms uno, del grupo encontrado en la anterior operacin de
establecimi ento de correspondencias.
He aqu un ejemplo:
// : strings/Groups.java
import java.util.regex.*
import static net.mindview.util.Print.*
public class Groups {
static public final String POEM
"Twas brillig, and the slithy toves\n" +
"Did gyre and gimble in the wabe . \n" +
"All mimsy were the borogoves , \n" +
"And the mame raths outgrabe. \n\nll +
"Beware the Jabberwock, rny son, \n" +
"The jaws that bite, the claws that catch. \ n" +
IIBeware the Jubjub bird, and shun\n" +
"The frumious Bandersnatch ." i
public static void main(String[] argsl
Matcher ID
Pattern. compile (" (?ml (\ \8+1 \ \8+ ( (\ \8+1 \ \ 8+ (\ \8+11 $" 1
.matcher (POEM) i
while(m.find(11 (
for (int j = O; j <= m.groupCount{); j++)
printnb(" [" + m.group(j) + lO] " ) ;
print ()
1* Output :
[the slithy taves] [the] [sl ithy taves] [slithy] [taves]
[in the wabe.l [in] [the wabe.] [the] [wabe.l
[were the borogoves,] [werel [the borogoves,] [the] [borogoves,]
[mame raths out grabe .] [mame] [raths out grabe .] [raths] [out grabe .
[Jabberwock, my son,] [Jabberwock,] [my son,] [rny] [son,]
[cl aws that catch. ] [claws] [that catch.] [that] [catch . ]
[bird, and shun] [bird,] [and shun] [and] [shunl
13 Cadenas de caracteres 339
[The frumious Bandersnatch.] [Thel [frumious Bandersnatch.] [ frumi ousl [Bandersnatch.]
*///,-
Este poema es la primera parte de "Jabberwocky", de Lewis Carroll extrado del libro A travs del espejo. Puede ver que el
patrn de expresin regular tiene una serie de grupos entre parntesis, compuestos de cualquier nmero de caracteres que
no sea de espaciado ('18+' ) seguido de cualquier nmero de caracteres de espaciado ('Is+'). El objetivo es capturar las tres
ltimas palabras de cada lnea, el final de una lnea est delimitado por '$'. Sin embargo, el comportamiento normal con-
siste en hacer corresponder "$' con el final de la secuencia de entrada completa, por lo que es necesario decir expLcitamen-
te a la expresin regular que preste atencin a los caracteres de nueva lnea desde dentro de la entrada. Esto se consigue con
el indicador de patrones '(?m)' al principio de la secuencia (los indicadores de patrones los veremos enseguida).
Ejercicio 12: (5) Moditique Groups.java para contar todas las palabras que no empiecen con una letra mayscul a.
start( ) y end( )
Despus de una operacin de establecimiento de correspondencias que haya pennitido encontrar al menos una correspon-
dencia, start() devuelve el ndice de inicio de la correspondencia anterior, mientras que cnd() devuelve el ndice del lti-
mo carcter de la correspondencia mas uno. Al invocar start() o end() despus de una operacin de localizacin de
correspondencias que no haya tenido xito (o antes de intentar una operacin de localizacin de correspondencias), se gene-
ra la excepcin lllegalStateException. El siguiente programa tambin ilustra los mtodos matcbes() y lookingAt( )3
jj : stringsjStartEnd.java
import java. uti l.regex.*
import static net.mindview.util.Print.*
public class StartEnd {
public static String input
"As long as there is injustice, whenever a \ n" +
"Targathian baby cries out, wherever a distress\ n" +
"signal sounds among the stars ... We I 11 be there. \ n" +
"This fine ship, and this fine crew ... \ n" +
"Never give up! Never surrender!"
1 El texto indicado es una cita de uno de los discursos del Comandante Taggart en Gala-cy Quest.
340 Piensa en Java
private static class Display {
private boolean regexPrinted = false;
priva te String regex;
Display (String regex) { this . regex = regex;
void display(String message) {
if (! regexPrinted) {
print (regex) ;
regexPrinted = true;
print (message);
static void examine(String s, String regex)
Display d new Display(regex);
Pattern p = Pattern.eompile(regex);
Matcher m = p.matcher(sl;
whilelm.find() )
d.display("findO '" + m. group() +
'" start = "+ m.start{) + " end = " + m.end(;
if(m.lookingAt{ II No reset(} necessary
d. display (" lookingAt () start = "
+ m.start() + " end = " + m. end(;
if(m.matches( II No reset() necessary
d. display ("matehes () start
+ rn.start() + " end = " + m.end(;
public statie void mai n {String[] args)
for(String in : input.split("\n" {
print (" input : ti + in);
for{String regex : new String[) {H\\w*ere\\w*" ,
"\\w*ever", "T\\W+", "Never. *?!"})
examine {in, regex) ;
1* Output:
input : As long as there is injustice, whenever a
\w*ere\w*
findO 'there' start ::: 11 end = 16
\w*ever
find() 'whenever' start
input : Targathian baby
\w*ere\w*
find () 'wherever' start
\w*ever
find () 'wherever' start
T\w+
31 end = 39
cries out, wherever a distress
27 end 35
27 end 35
find () 1 Targathian' start O end = 10
lookingAt() start = O end 10
input: signal sounds among the stars . . . We'll be there.
\w*ere\w*
find() ' there' start = 43 end = 48
input: This fine ship, and this fine crew . . .
T\w+
find () 'This ' start = O end = 4
lookingAt{) start = O end = 4
input : Never give up! Never surrender!
\w*ever
f ind () 'Never' start O end = 5
f ind () 1 Never' start 15 end = 20
13 Cadenas de caracteres 341
lookingAt() start = O end = 5
Never. *?!
find() 'Never give up!' start O end = 14
find() 'Never surrender!' start 15 end = 31
looki ngAt () start O end = 14
matches () start = O end = 31
-/1/,-
Observe que find() permite localizar la expresin regular en cualquier lugar de la entrada, mientras que lookingAt() y mat-
chcs() slo tienen xito en la bsqueda si la expresin regular se corresponde desde el pri ncipio de la entrada. Mientras que
matches( ) slo tiene xito en la bsqueda si loda la entrada se corresponde con expresin regular, lookingAt()4 tiene xito
en la bsqueda aunque slo se corresponda la expresin regular con la primera parte de la entrada.
Ej e rcici o 13: (2) Modifique Start End.java para que ut ilice Groups.POEM como entrada, pero siga produciendo resul-
tados posit ivos para find(), lookingAt( ) y mat ches( ).
Indi cadores de Pattern
Hay un mtodo compile( ) altemativo que acepta indi cadores que afectan al comportamiento de bsqueda de corresponden-
cias:
Pattern Pattern.compile{String regex, int flag)
donde flag puede ser una de las siguientes constantes de la clase Pattero:
Indicador de compilacin Efecto
l' attcrn.CANON_EQ Se considera que dos caracteres se corresponden si y slo si sus descomposiciones cannicas
completas 10 hacen. Por ejemplo, la expresin " u003F' se corresponder con la cadena o?,
cuando se especifique este indicador. De manera predeterminada. la bsqueda de correspon-
dencias no tiene en cuenta la equivalencia cannica.
Pattern.CASE_INSENSITl VE Por omisin. la bsqueda de correspondencias sin distincin de maysculas y minsculas presu-
(?i) pone que slo se estn util izando caracteres del conjunto de caracteres US-ASCII. Este indicador
permite establecer una correspondencia con el patrn sin tener cn cuenta maysculas o minscu-
las. Puede habilitarse la bsqueda de correspondencias Unicode sin distincin de maysculas y
minsculas especificando el indicador UNICODE_ CASE en conjuncin con este indicador.
Pattern.COMM ENTS En este modo. se ignoran los caracteres de espaciado, y tambin se ignoran los comentarios
(?x) incnlstados que comicncen con # hasta el final de la lnea. Tambin puede habilitarse el modo
de lneas Unix mediante la expresin de indicador incnlstado.
Pattern.DOTALL En este modo, la expresin'.' se corresponde con cualquier carcter, incluyendo el tenninador de
(?s) lnea. Por omisin, la expresin'.' no se corresponde con los tenninadores de lnea.
-
-
Pattern.M UL TI UNE En el modo multilnea. las expresiones 'A' y 'S' se corresponden con el principio y el final de una
(? m) lnea. respectivamente. 'A' tambin se corresponde con el principio de la cadena de entrada y 'S'
lo hace con el final de la cadena de entrada. De manera predetenninada. estas expresiones slo se
corresponden con el principio y el final de la cadena de entrada completa.
Pattern.UNICODE_CASE La correspondencia sin distincin de maysculas y mi nsculas, si est habi litada por el indicador
(?u) CASE_lNSENSITIVE. se realiza de manera coherente con el estndar Unicode. De manera pre-
detenninada, la correspondencia sin distincin de maysculas y minsculas presupone que slo se
estn buscando correspondencias con caracteres del conjunto de caracteres US-ASCI 1.
Pancrn.UNIX _ LI NES En este modo, slo se reconoce el tenni nador de lnea '\n' en el comportamiento de ;. ', ,'" y '$'.
(?d)
4 No s por qu dieron este nombre a dicho mtodo ni a qu refiere ese nombre. Pero resulta reconfortante saber que quienquiera que sea que inventa esos
nombres de mtodos tan poco intuitivos contina empleado en SUIl y que su aparente politica de no revisar los diseos de cdigo sigue estando vigente.
Perdn por el sarcanno, pero es que este tipo de cosas empiezan a cansar despus de unos cuamos aos.
342 Piensa en Java
De especial utilidad entre todos estos indi cadores son Pattern.CASE_INSENSITlVE, Pattern.MULTlLlNE y
Pattern.COMMENTS (que resulta til para mejorar la claridad y/o con propsitos de dcoumentacin). Observe que el
comportami ento de la mayor parte de los indi cadores puede obtenerse tambi n insertando en la expresin regular los carac-
teres entre parntesis que se muestran en los indi cadores de la tabla, j usto antes del lugar donde se quiera que ese modo
tenga efecto.
Tambi n podemos combinar el efecto de estos y otros indicadores medi ante una operacin "OR" el'):
11 : strings/ ReFlags.java
import java.util.regex. * ;
public class ReFlags {
public static void main (String [) args ) {
Pattern p = Pattern . compi le ( lI"'java
lt
,
Pattern . CASE_I NSENSITIVE I Pattern . MULTILINE) ;
Matcher m = p.matcher (
Itjava has regex\ nJava has regex\ n
ll
+
IIJAVA has pretty good regular expressions \ n
ll
+
"Regular e xpressions are in Java
lt
);
while (m.find () )
Sys t em. out .prin tln {m.group {)) i
1* Output:
java
Java
J AVA
* /// ,-
Esto crea un patrn que se corresponder con las lneas que comi encen por "java," "Java," "JAVA," etc., y que intentar bus-
car una correspondencia con cada lnea que fonne parte de un conjunto multilnea (correspondencias que comienzan al prin-
cipio de la secuencia de caracteres y a continuacin de cada terminador de lnea contenido dentro de la secuencia de
caracteres). Observe que el mtodo group() slo devuel ve la porcin con la que se ha establecido la correspondencia.
split( )
split() divide una cadena de caracteres de entrada en una matri z de obj etos St r ing, utili zando como delimitador la expre-
sin regul ar.
String[] split {CharSequence input )
String[] split {CharSequence input, int limit )
sta es una forma cmoda de descomponer el texto de entrada utili zando una frontera di visori a comn:
11 : strings/Sp litDemo.java
import j ava.util.regex.*;
import java . util.*.
import static net . mindview. util.Print. *
publi c c lass SplitDemo {
public static void main (String [] args ) {
String input =
"This! !unusual use! ! o f e xc l amation! !poi n ts
tl
;
print (Arrays . toStri ng (
Pattern . compile ( II ! ! 11 ) . split(input )))
II Hacer slo las t r es p r imeras :
print(Arrays . toString (
Pattern . compile ( " !!" ) .split (input, 3 )))
1* Output :
[This, unusual use , o f e xclamation, pointsl
[This, unusual use, of exclamation! !points]
* /// ,-
La segunda forma de split( ) limita el nmero de di visiones que pueden tener lugar.
Ejercicio 14: (1) Escriba de nuevo SplitDemo util izando String.split( ).
Operaciones de sustitucin
13 Cadenas de caracteres 343
Las expresiones regulares resultan especialmente tiles para sustituir texto. He aqu los mtodos di sponibles:
rcplaceFirst(String rcplacement) sustituye por replacement la primera parte que se corresponde de la cadena de caracte-
rcS.
replaceAII(StriDg replacement) sustituye por replacemeDt todas aquellas partes que se correspondan en la cadena de
caracteres de entrada.
appendReplacement(StringBuffer sbuf, String replacement) reali za sustituciones paso a paso en sbuf, en lugar de susti-
nJir slo la primera o todas ellas, como sucede con replaceFirst() y rcplaceAII(), respecti vamente. ste es un mtodo muy
importante, porque permite invocar mtodos y realizar otros tipos de procesamiento para generar la cadena de sustitucin
replacement (replaceFirst() y replaceAII() slo pueden utilizar cadenas de caracteres fijas para la sustitucin). Con este
mtodo, podernos separar los grupos mediante programa y crear potentes rutinas de sustitucin.
appendTail(StringBuffcr sbuf, String replacement) se invoca despus de UDa o ms invocaciones del mtodo
appendReplacement() para copiar el resto de la cadena de caracteres de entrada.
He aqu un ejemplo que muestra el uso de todas las operaciones de sustitucin. El bloque de texto comentado al principio
del programa es extrado y procesado con expresiones regulares para usarlo como entrada en el resto del ejemplo:
/1: strings/TheReplacements.java
import java.util.regex. *
import net.mindview.util . *
import static net.mindview.util.Print. *
/*! Here's a block of text to use as input to
the regular express ion matcher. Note that we'll
first extract the block of text by looking tor
the special delimiters, then process the
extracted block. ! * I
public class TheReplacements
public static void main(String(] args) throws Exception {
String s ::: TextFile. read ( UTheReplacements. java")
II Establecer correspondencia con el bloque de texto con
II comentarios especiales mostrado anteriormente:
Matcher mlnput :::
Pattern.compile("I\\*! (.*) !\\ * /". Pattern.DOTALL)
.matcher(s)
if(mlnput.find())
s ::: mlnput.group(l) II Capturado por parntesis
II Sustituir dos o ms espacios por un nico espacio:
s ::: s.replaceAII(to {2, }II, " ");
II Eliminar dos o ms espacios al principio de cada
II lnea. Hay que habilitar el modo MULTILNEA:
s = s.replaceAII("{?m) '" +", ""J
print(s)
s = s. replaceFirst ( " [aeiou] ". " (VOWEL1J 11) ;
StringBuffer sbuf ::: new StringBuffer{);
Pattern p ::: Pattern. compile (11 [aeiou] ")
Matcher m = p.matcher(s);
II Procesar la informacin de localizacin a medida
II que se realizan las sustituciones:
while (m . find ())
344 Piensa en Java
m.appendReplacement {sbuf, m.group () .toUpperCase ()) i
// Insertar el resto del texto:
m.appendTail (sbuf) i
print (sbuf ) ;
/ * Output:
Here's a block of text to use as input to
the regular expression matcher. Note that we'll
first extract the block of text by looking for
the special delimiters, then process the
extracted block.
H{VOWEL1 ) rE' s A blOck Of tExt tO UsE As InpUt tO
thE rEgUlAr ExprEssIOn mAtchEr. NOtE thAt wE'll
fIrst ExtrAct thE blOck Of tExt by lOOkIng fOr
thE spEcIAl dEllmItErs, thEn prOcEss thE
ExtrActEd blOck.
*// / ,-
El archivo se abre y se lee utilizando la clase TextFiJe de la biblioteca net.mindview.util (el cdigo correspondiente se mos-
trar en el Capitulo 18, E/S). El mtodo esttico read() lee el archivo completo y lo devuelve como objeto Slring. mlnput
se crea para corresponderse con todo el texto (observe los parntesis de agrupamiento) comprendido entre '{*! ' y ' !*/'.
Despus, los conjuntos de ms de dos espacios seguidos se reducen a un nico espacio y se eliminan todos los espacios situa-
dos al principio de cada lnea (para hacer esto en todas las lneas y no slo al principio de la cadena de entrada, es necesa-
rio habilitar el modo multilnea). Estas dos sustituciones se realizan con el mtodo equivalente (pero ms cmodo, en este
caso) replaceAII() que fom1a parte de Slring. Observe que, como cada sustitucin slo se emplea una vez en el programa,
no se produce ningn coste adicional por hacerlo as en lugar de precompilar la operacin en fonna de un objeto Pattern.
replaceFirst() slo sustituye la primera correspondencia que encuentre. Adems, las cadenas de sustitucin en
replacefirst() y replaceAII() son simplemente literales, por lo que si queremos realizar algn procesamiento en cada sus-
titucin, no nos sirven de ninguna ayuda. En dicho caso, tendremos que utilizar appendReplacement(), que nos pennite
escribir cualquier cdigo que queramos para reali zar la sustitucin. En el ejemplo anterior, se selecciona y procesa un grupo
(en este caso, poniendo en mayscula la vocal encontrada por la expresin regular) a medida que se constmye el objeto sbuf
resultante. Nonnalmente, lo que haremos ser recorrer toda la entrada y hacer todas las sustituciones y luego invocar a
appendTail(), pero si queremos simular replaceFirst() (o una operacin de "sustitucin de n apariciones"), basta con hacer
la sustitucin una vez y luego invocar appendTail() para insertar el resto de la infonnacin en sbuf.
appendReplacement() tambi n nos permite hacer referencia directamente en la cadena de sustitucin a los grupos capul-
radas mediante la notacin "$g", donde 'g' es el nmero de grupo. Sin embargo, este mtodo slo sirve para tareas de pro-
cesamiento simples y no nos vara los resultados deseados en el programa anterior.
reset( )
Podemos aplicar un objeto Matcher existente a una nueva secuencia de caracteres utili zando los mtodos reset():
// : strings/ Resetting . java
import java.util.regex.*;
publi c class Resetting {
public static void main(String[] args) throws Exception
Matcher m = Pattern.compile {tI {frbl {aiu] [gx] ti )
.matcher(lIfix the rug with bags
ll
) ;
while (m.find () )
System. out.print {m. group {) + 11 ti) i
System.out . println( ) ;
m.reset ( "fix the rig with rags
ll
) ;
while(m.find() )
System.out.print (m.group () + 11 11 ) ;
/ * Output:
fix rug bag
fix rig rag
' 111 ,-
13 Cadenas de caracteres 345
rcsct() sin ningn argumento bace que Matcher se situe al princi pi o de la secuencia actual.
Expresiones regulares y E/S en Java
La mayora de los ejemplos vistos hasta ahora mostraban la apli cacin de las expresiones regulares a cadenas de caracteres
estticas. El sigui ente ej emplo muestra una forma de aplicar expresiones regulares a la bsqueda de correspondencias en un
archivo. Inspirada en la utilidad grep de Unix, JGrep.java lOma dos argumentos: un nombre de archi vo y la expresin regu-
lar con la que se qui ere buscar correspondencias. La salida muestra cada lnca en la que se ha detectado una corresponden-
ci a y la posicin o posiciones de las correspondenci as dentro de la lnea:
// : strings/JGrep.java
/ / Una versin muy simple del programa "grep".
II {Args, JGrep.java " \\b[Ssctl\\w+"}
import java.util.regex. *
import net.mindview. util .*
public class JGrep {
public static void ma i n{Stri ng[] args) throws Exception
if (args . lengt h < 2) {
System.out . println(ItUsage : java JGrep file regex " )
Sy stem. exit(O)
Pattern p Pattern . compile (args (1 ]) ;
/1 Iterar a travs de las lineas del archivo de entrada:
int index Oi
Matcher m p . matcher ("") ;
for(String line : new TextFile(args[O)))
m. reset (line) ;
while (m. find())
System.out.println(index++ + 11: " +
m.group() +" : " +m.start());
1* Output: (Sample)
O,
strings: 4
L simple: 10
2, the: 28
3,
Ssct: 26
4,
class: 7
5, static: 9
6, String: 26
7, throws: 41
8, System: 6
9,
System: 6
10, compile: 24
II ,
through: 15
12, the: 23
13, the: 36
14, String: 8
15, System: 8
16, start: 31
' 111,-
El archivo se abre como un objeto net.mindview.util .TcxtFil c (del que hablaremos en el Capitulo 18. E/S). que lee las li-
neas del archivo en un contenedor tipo ArrayList. Esto si gnifica que podemos utilizar la sintaxi s foreach para iterar a tra-
vs de las lneas almacenadas en el objeto TextFile.
346 Piensa en Java
Aunque es posible crear un nuevo objelO Matcher dentro del bucle for, resulta ligeramente ms ptimo crear un objeto
vacio Matcher fuera del bucle y utili zar el mtodo reset() para asignar cada linea de la entrada al objeto Matcher. El resul_
tado se analiza con tind().
Los argumentos de prueba abren el archivo JGrep.java para leerlo como entrada y buscan las palabras que comiencen por
Ssct .
Puede aprender mucho ms acerca de las expresiones regulares en A1astering Regular E).pressions, 2
u
Edicin, por Jeffrey
E. F. Friedl (O'Reilly, 2002). Hay tambin numerosas introducciones a las expresiones regulares en Internet, y tambin se
puede encontrar a menudo infonnacin til en la documentacin de lenguajes tales como Perl y Python.
Ejercicio 15: (5) Modifique JGrep.java para aceptar indicadores como argumentos (por ejemplo, Pattern.CASE_
INSENSITIVE, Pattern.MULTILl NE).
Ejercicio 16: (5) Modifique JGr.p.java para aceptar un nombre de directorio o un nombre de archivo como argumen-
to (si se proporciona un directorio, la bsqueda debe extenderse a todos los arcluvos de directono).
Consejo: puede generar una lista de nombres de archivo con:
File (] files = new File (" . ") .listFiles () ;
Ejercicio 17: (8) Escriba un programa que lca un archivo de cdigo fuente Java (tendr que proporcionar el nombre del
archivo en la lnea de comandos) y muestre todos los comentarios.
Ejercicio 18: (8) Escriba UD programa que lea un archivo de cdigo fuente Java (tendr que proporcionar el nombre del
archi vo en la lnea de comandos) y muestre todos los literales de cadena presentes en el cdigo.
Ejercicio 19: (8) Utilizando los resultados de los dos ejercicios anteriores, escriba un programa que examine el cdigo
fuente Java y genere todos los nombres de clases utilizados en un programa concreto.
Anlisis de la entrada
Hasta ahora, resultaba relativamente complicado leer datos de un archivo de texto legible o desde la entrada estandar. La
solucin usual consiste en leer una lnea de texto, extraer los elementos y luego utilizar los diversos mtodos de anlisis sin-
tctico de Integer, Double, etc. , para analizar los datos:
11: strings/SimpleRead.java
impert java.io.;
public class SimpleRead
public static BufferedReader input = new BufferedReader(
new StringReader("Sir Robin of Camelot\n22 1.61803"));
public static void main(String (] args) {
try (
System.out.println("What is your name?");
String name = input. readLine () ;
System.out.println(name) ;
System.out.println(
"How old are you? What is your favorite double?");
System.out . println(l!(input: <age> <double");
String numbers = input.readLine();
System.out . println (numbers) ;
String (] numArray = numbers. split (" ");
int age = Integer.parselnt(numArray[O));
double favorite = Double.parseDouble(numArray(1]) i
System.out. format ("Hi %s. \nl!, name);
System. out.format(Uln 5 years you will be \d.\n",
age + 5 ) ;
System.out. format ( IIMy favorite double is %f.",
favorite I 2);
catch (IOException e) {
System.err.println ( III / O exception" ) ;
/ '* Output:
What i5 your name?
Sir Robin of Camelat
How old are you? What i5 your favorite double?
(input: <age> <double
22 1.61803
Hi Sir Robin o f Camelat .
In 5 years you will b e 27.
My favorite double i 5 0 . 809015 .
. /// ,-
13 Cadenas de caracteres 347
El campo input utili za clases de java.io, que no vamos a presentar oficialmente basta el Captulo 18. E/S. Un obj eto
StringReadcr transfonna un dato de tipo String en un flujo de datos legible y este objeto se emplea para crear un obj eto
BufferedRcader porque BufferedReader tiene un mtodo readLine(). El resultado es que el objeto input puede leerse de
linea en lnea, como si fuera la entrada estndar procedente de la consola.
readLine( ) se utili za para obtener la cadena de caracteres correspondiente a cada lnea de entrada. Resulta bastante cmo-
do de utili zar cuando queremos obtener un dato de entrada por cada linea de datos, pero si hay dos valores de entrada que
se encuentran en una misma lnea, las cosas empiezan a complicarse: la lnea debe dividirse para poder analizar cada dato
de entrada por separado. Aqu, la divisin tiene lugar al crear numArray. pero observe que el mtodo splil( ) fue introdu-
cido en J2SE lA, por lo que en las versiones anteriores era necesario hacer alguna otra cosa.
La clase Scanner, miad ida en Java SES, elimina buena parte de la complejidad relacionada con el anli sis de la entrada:
11 : strings/BetterRead . java
import java . util. * ;
public class BetterRead
public static void main (String [] args) {
Scanner stdin = new Scanner(SimpleRead.input);
System.out.println(UWhat is your name?");
String name = stdin . nextLine( ) ;
System.out.println(name) ;
System.out.println(
"How old are you? What is your favorite double?");
System.out.println(" (input: <age> <double n);
int age = stdin.nextlnt();
double favorite = stdi n.nextDouble();
System.out.println(age) ;
System.out.println(favorite) i
System.out.format("Hi %s.\n", name);
System.out.format ( "In 5 years you will be %d. \ n",
age + 5) i
System.out. format ( nMy favorite double is %f.",
favorite I 2 ) j
1* OutPUt:
What is your name?
Sir Robin of Camelot
How old are you? What i5 your favorite double?
(input: <age> <double
22
1.61803
Hi Sir Robin of Camelot .
In 5 years you will be 27 .
My favorite double is 0 . 809015 .
' /// ,-
348 Piensa en Java
El constructor de Scanner admite casi cualquier lipo de objeto de entrada, incluido el objeto File (que tambin veremos en
el Captulo 18, E/S), un objeto InputStream, un objeto String o. como en este caso un objeto Readable, que es una inl er-
faz introducida en Java SE5 para describir "algo que dispone de un mtodo read( )". El objeto BufferedReader del ejem-
plo anterior cae dentro de esta categora.
Con Scanner. los pasos de entrada. extraccin de elementos y anlisis si ntctico estn implementados mediante diferentes
lipos de mtodos de "continuacin", Un mtodo next() devuelve el siguiente elemento String y existen elementos si mila-
res para todos los tipos primitivos (excepto char). as como para BigDecimal y Biglnteger. Todos estos mtodos se blo-
quean, lo que significa que slo temlinan despus de que haya un elemento de datos completo di sponible como entrada.
Tambin existen los mtodos "hasNext"' correspondientes que devuelven true si el siguiente elemento de entrada es del tipo
correcto.
Una interesante diferencia entre los dos ejemplos anteriores es la falta de un bloque try para las excecpiones IOExcepti on
en BetterRead.java. Una de las suposiciones hechas por el objeto Scanner es que IOExccption marca el final de la entra-
da, por lo que estas excepciones son capturadas y hechas desaparecer por el objeto Scanner. Sin embargo, la excepcin ms
reciente est di sponible a travs del mtodo ioException( ), por lo que podemos examinarla en caso necesario.
Ejercicio 20: (2) Cree una clase que contenga campos inl, long, noat , double y String. Cree un constructor para esta
clase que tenga un nico argumento String, y anal ice dicha cadena de caracteres para rellenar los diferen-
tes campos. Anada un mtodo toString() y demuestre que la clase funciona correctamente.
Delimitadores de Scanner
De manera predetenl1inada, un objeto Scanner divide los elementos de entrada segn los caracteres de espaciado, pero tam-
bin podemos especificar nuestro patrn delimitador en forma de una expresin regular:
11 : strings/ScannerDelimiter.java
import java.util.*;
public class ScannerDelimiter
12
42
78
99
public sta tic void main (String [] args) {
Scanner scanner = new Scanner("12, 42, 78, 99, 42
1t
);
scanner.useDelimiter{"\\s*,\\s*") ;
while{scanner.hasNextlnt())
System out.println(scanner.nextlnt{))
1* Output:
42
*///,-
Este ejemplo utiliza comas (rodeadas por cantidades arbitrarias de espacios) como los delimitadores a la hora de leer la cade-
na de caracteres dada. Esta misma tcnica puede utili zarse para leer desde archivos delimitados por comas. Adems del
mtodo useDel imiterO. que sirve para fijar el patrn de delimiracin. existe tambin otro mlOdo denominado delimiterO.
que devuelve el objeto Pattern que est siendo acnl3lmcl1le uti li zado como delimitador.
Anlisis con expresiones regulares
Adems de ana li zar en busca de tipos predefinidos, tambin podemos realizar el anlisis empleando nuestros propios patro
nes definidos por el usuario, lo cual resulta muy til a la hora de analizar datos ms complejos. El siguiente ejemplo anali-
za un archivo de registro generado por un cortafuegos en busca de datos que revelen potenciales amenazas.
11 : strings/ThreatAnalyzer.java
import java.util.regex.*
import java.util.*;
public class ThreatAnalyzer {
scatic String threatData =
"58.27.82.161@02/10/2005\n" +
"204.45.234 . 40@02/11/2005\n" +
"58.27 . 82.161@02/11/2005\n" +
"58 . 27.82.161@02/12/2005\n" +
"58.27.82.161@02/12/2005\n" +
ti [Next 10g section with different data format] It i
public static void main (String [] args) {
Scanner scanner = new Scanner (threatData) ;
String pattern = " (\\d+ [.l \\d+ [.l \\d+ [.l \\d+)@" +
"{\\d{2}/\ \d{2}/\ \d{4})";
while (scanner. hasNext (pattern {
scanner.next(pattern) ;
MatchResult match = scanner.match();
String ip = match.group(l);
String date = match.group(2);
System.out.format("Threat on \:5 from %5\n", date,ip);
/* Output:
Threat en 02/10/2005 from 58 . 27.82.161
Threat en 02/11/2005 from 204.45.234 . 40
Threat en 02/11/2005 from 58 . 27 . 82.161
Threat en 02/12/2005 from 58 . 27 . 82.161
Threat en 02/12/2005 from 58 . 27.82.161
*/1/,-
13 Cadenas de caracteres 349
Cuando utili zamos next ( ) con un patrn especfico, se trata de hacer corresponder di cho patrn con el siguiente elemento
de entrada. El resultado es devuelto por el mtodo match( ) y, como podemos ver en el ej empl o, el mecani smo funciona
exactament e igual que las bsquedas con expresiones regulares que hemos visto anterionnente.
Existe un problema a la hora de anali zar con expresiones regul ares. El patrn se hace corresponder nicamente con el
siguiente elemento de entrada, por lo que si el patrn contiene un delimitador nunca se encontrar una correspondencia.
StringTokenizer
Anl es de que hubi era di sponibles expresiones regulares (en J2SE 1.4) o la clase Scanner (en Java SES), la fonna de di vidir
una cadena en sus partes componentes era extraer los elementos con StringTokenizer . Pero ahora resulta mucho ms fc il
y ms suci nto hacer lo mi smo con expresiones regulares o la clase Sca nner . He aqu ull a comparacin simpl e de
StringTokeni zcr con las otras dos tcni cas:
JJ: stringsJReplacingStringTokenizer.java
impert java.util.*;
public class ReplacingStringTokenizer
public static void main(String[1 args)
String input = "But 1 'm not dead yet! 1 feel happy!";
StringTokenizer stoke = new StringTokenizer(input);
while(stoke.hasMereElements())
System.out.print(stoke.nextToken() + " ");
System.out.println() ;
System. out . println {Arrays. toString (input. spli t (" ")));
Scanner scanner = new Scanner (input) ;
while(scanner.hasNext())
System.out.print(scanner.next() + " ");
1* Output:
But l ' m not dead yet! 1 feel happy!
350 Piensa en Java
[But, 1
'
m, not, dead, yet!, 1, feel, happy!]
eut 1'm not dead yet! 1 feel happy!
, /// ,-
Con las expresiones regulares o con los objetos Scanner, tambin puede di vidirse una cadena en sus partes componentes
uti li zando patrones ms compl ejos, cosa que resulta dificil de realizar con StringTokenizer. Podemos decir, sin mucho
temor a equivocarnos que StringTokenizer est obsoleto.
Resumen
En el pasado, el sopone que Java tena para la manipulacin de cadenas de caracteres era rudimentario, pero en las edicio_
nes recientes del lenguaje hemos podido ver cmo se han ido adoptando funcionalidades ms sofi sticadas, copiadas de otros
lenguajes. Actualmente. el soporte para cadenas de caracteres es razonablemente completo, aunque en ocasiones es necesa-
rio prestar algo de atencin a los detall es de eficiencia, como por ejemplo haciendo un uso apropiado de StringBuilder.
Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico The Thinking ill Jm'o Allllotared SOlulioll Cuide, disponible pard
la venia en 1\'W\\ ,. \findl'ie\\: /lel .
Informacin
de tipos
El mecanismo RTTI (Run/ime Iype informa/ion, informacin de tipos en tiempo de ejecucin)
nos pennite averiguar y uti lizar la informacin acerca de los tipos de los datos mientras un
programa se est ejecutando.
Este mecanismo nos libera de la rest riccin de efectuar operaciones orientadas a tipos nicamente en tiempo de compila-
cin, y pennite construir algunos programas de gran potencia. La necesidad de RTTI pennite descubrir una gran cantidad
de cuestiones del di seo orientado a objetos de gran inters (y a menudo bastante intrigantes) y hace que surjan cuestiones
fundamentales acerca de cul tiene que ser la manera de estructurar los programas.
En este captulo se examinan las formas en que Java permite averiguar la informacin acerca de los objetos y las clases en
tiempo de ejecucin. Estos mecani smos adoptan dos formas diferentes: RTTI "tradiciona)", que supone que tenemos lodos
los tipos disponibl es en tiempo de compilacin y el mecani smos de reflexin que pennite descubrir y utilizar la informacin
sobre clases exclusivamente en tiempo de ejecucin.
La necesidad de RTTI
Considere el ya familiar ejemplo de una jerarqua de clases que utiliza los mecanismos de polimorfismo. El tipo genrico
es la clase base Shape y los tipos derivados especficos son Circle. Square y Triangle:
Shape
draw( )
I I
I
Circle
I
Square
I
Triangle
I
Se trala de un diagrama de jerarqua de clases tpico, con la clase base en la parte superior y las clases derivadas distribu-
yndose en senl ido descendente. El objetivo nom1al de la programacin orientada a objetos es que el cdigo manipule refe-
renci as a tipo base (en este caso, Shape), de 1110do que si decidimos ampliar el programa aiiadielldo una nueva clase (como
por ejemplo Rhomboid. derivada de Shape). el grueso del cdigo no se vea afectado. En este ej emplo, el mtodo de aco-
plamiento dinmico de la interfaz Shapc es draw(), por lo que la intencin es que el programador de clientes invoque a
draw( ) a travs de una referencia genrica a Shape. En todas las clases derivadas. el mtodo draw( ) se susti ulye y, pues-
to que es un mtodo con acoplamiento dinmico, obtendremos el comportamiento adecuado an cuando se invoque al mto-
do sustiuno a travs de una referencia genrica a Shape. Esto es, ni ms ni menos, polimorfismo.
Por tanto, lo que hacemos en general es crear un objeto especfico (Circle. Squarc o Triangle), generalizarlo a Shape (olvi-
dando el tipo especfico de objeto) y utilizar esa referencia annima a Shape en el reslO del programa.
Podemos codificar la jerarqua Shape de la siguiente manera:
352 Piensa en Java
11: typeinfo/shapes . java
import java.util.*;
abstract class Shape
void draw() { System. out . println(this + ".draw() ") }
abstract public String toString();
class Circle extends Shape {
public String toString() { return "Circle"; }
class Square extends Shape {
public String toString() { return "Square" }
class Triangle extends Shape
public String toString() { return "Triangle" }
public class Shapes {
public static void main (String [] args) {
List<Shape> shapeList = Arrays.asList(
new Circle(), new Square(), new Triangle()
) ;
for(Shape shape : shapeList )
shape.draw()
/* Output :
Circle . draw ()
Square. draw ()
Triangle. draw ()
*///,-
La clase base contiene un mtodo draw() que utili za indirectamente toString() para imprimir un identifi cador de la clase,
pasando this a System.out.println() (observe que toString() se declara como abstracto para obligar a las clases herederas
a sustituirl o, y para impedir la instantacin de un obj eto Shape simpl e). Si un objeto aparece en una expresin de concate-
nacin de cadenas (donde estn involucrados '+' y objetos String), se invoca automti camente el mtodo toString() para
generar una representacin de tipo Slring de dicho objelo. Cada una de las clases derivadas sustituye el mtodo toString()
(de Object) de modo que draw() termine (polimrficamente) imprimiendo algo distinto en cada caso.
En este ejemplo, la generalizacin tiene lugar cuando se coloca la fonna geomtri ca en el contenedor List<Shape>. Durant e
la generali zacin a Shape, el hecho de que los objetos sean lipos especficos de Shape se pierde. Para la matriz se (rata si m-
plemente de objetos Shape.
En el momento en que se extrae un element o de la matriz, el contenedor (que en la prcti ca almacena todos los elementos
como si fueran de tipo Object) proyecta automticamente el resultado sobre un objeto Shape. ste es el tipo ms bsico del
mecanismo RTTI , porque todas las proyecciones de tipos se comprueban en tiempo de ejecucin para comprobar Su correc-
cin. Eso es lo que RTTI significa: el tipo de los objetos se identifica en tiempo de ejecucin.
En este caso, la proyeccin RTTl slo es parcial : el objeto Object se proyecta sobre Shape, y no sobre Circle, Square o
Triangle. Eso es debido a que lo nico que sabemos en este punto es que el contenedor List<Shape> est ll eno de objetos
Shape. En tiempo de compilacin, esto se impone mediante el contenedor y el sistema genri co de Java, pero en ti empo de
ejecucin es la proyeccin la que garanti za que esto sea as.
Ahora es cuando entra en accin el polimorfismo y se detennina el cdigo exacto que ejecutar el objeto Shape viendo
si la referencia corresponde a un objeto Ci rcle, Square o Triangle. Y, en general, as es como deben ser las cosas. Lo que
queremos es que la mayor parte de nuestro cdigo sepa lo menos posible acerca de los tipos especficos de los objetos.
debiendo limitarse a tratar con la representacin general de una fami li a de objetos (en este caso, Shape). Como resultado.
el cdigo ser ms fcil de escribir, de leer y de mant ener, y los diseos sern ms senci llos de impl ementar, comprender y
14 Informacin de tipos 353
modificar. Por ello. el polimosrfismo es UIlO de los objetivos generales que se persiguen con la programacin orientada a
objetos.
Pero qu sucede si tenemos un problema especial de programacin que resulta ms fcil de resolver si conocemos el tipo
exacto de una referencia genrica? Por ejemplo, suponga que queremos pem1itir a nuestros usuarios que resalten todas las
fom1as geomtricas de un cierto tipo concreto, asignndolas un color especial, de esta fonn8. pueden localizar todos los
tringulos de la pantalla resaltndolos. 0 , por ejemplo. imagine que nuestro mtodo necesita "rotar" una li sta de fomlas geo-
mtricas. pero que no tiene sentido rotar un crculo, por lo que preferimos saltarnos los crculos al implementar la rotacin
de todas las formas. Con RTTl, podemos preguntar a una referencia de tipo Shape cul es el tipo exacto al que est apun-
tando. lo que nos permite seleccionar y aislar los casos especiales.
El obj eto Class
Para comprender cmo funciona el mecani smo RTTI en Java. primero tenemos que saber cmo se representa la infonna-
cin de tipos en tiempo de ejecucin. Esto se lleva a cabo mediante un tipo de objeto especial denominado objeto Class. que
contiene infonnacin acerca de la clase. De hecho. el objeto Class se utiliza para crear todos los objetos ""nonnales" de una
clase. Java implementa el mecanismo RTIl utilizando el objeto Class. incluso si lo que estamos haciendo es algo como una
proyeccin de tipos. La clase Class tambin pennite otra serie de fonnas de utilizacin de RTTI.
Existe un objeto Class para cada clase que fonne parte del programa. En otras palabras, cada vez que escribimos y compi-
lamos una nueva clase. tambin se crea un determinado objeto Class (y ese objeto se almacena en un archivo .class de nOI11-
bre idntico). Para crear un objeto de esa clase. la mquina virtual Java (JVM) que est ejecutando el programa utiliza un
subsistema denominado cargador de clases.
El subsistema cargador de clases puede comprender, en la prctica, una cadena de cargadores de clases. pero slo existe un
cargador de clases primordial, que forma parte de la implementacin de la NM. El cargador de clases primordial carga las
que se denominan clases de confian:::a, que incluyen las clases de las interfaces API de Java, y esa carga se reali za normal-
mente desde el di sco local. Usualmente no es necesario tener cargadores de clases adicionales en la cadena, pero si tenemos
necesi dades especiales (como por ejemplo, cargar clases de alguna manera especial para dar soporte a aplicaciones de ser-
vidor web, o descargar clases a travs de una red). entonces disponemos de una manera de enlazar cargadores de clases adi-
cionales.
Todas las clases se cargan en la JVM dinmicamente, cuando se utiliza la clase por primera vez. Esto sucede cuando el pro-
grama hace referencia a un miembro esttico de dicha clase. Resulta que el constructor tambin es un mtodo esttico de
una clase, an cuando no se utilice la palabra clave sta tic para el constructor. Por tanto, el crear un nuevo objeto de di cha
clase utilizando el operador Dew tambin cuenta como una referencia a un miembro esttico de la clase.
Por tanto. los programas Java no se cargan por completo antes de comenzar la ejecucin, sino que se van cargando los dis-
tintos fragmentos del programa a medida que son necesarios. Esto difiere de muchos lenguajes tradicionales. El mecani s-
mos de carga permite conseguir un tipo de comportamiento que resulta muy dificil, o incluso imposible, de obtener con un
lenguaje esttico de carga como pueda ser C++.
El cargador de clases compnteba primero si el objeto Class de dicho tipo est cargado. Si no lo est, el cargador de clases
predetem1inado localiza el archivo .class con dicho nombre (un cargador de clases adicional podra. por ejemplo. extraer el
cdigo intenuedio de una base de datos en lugar de hacerlo de un archivo). A medida que se carga el cdigo intennedio
correspondiente a la clase, dicho cdigo se verifica para garantizar que no est corrompido y que no incluya cdigo Java
mal fonnado (sta es una de las lneas de defensa de los mecanismos de seguridad de Java).
Una vez que el objeto Class de dicho tipo se encuentra en memoria se le utiliza para crear todos los objetos de dicho tipo.
He aqu un programa que ilustra esta fom1a de actuar:
JI : typeinfo/ SweetShop.java
JI Examen de la forma en que funciona el cargador de clases.
import static net.mindview.uti l.Print.*;
class Candy {
static { print ("Loading Candy"); }
354 Piensa en Java
class Gum {
static { print ("Loading Gum"); }
class Cookie {
static { print (IILoading Cookie
ll
); }
public class SweetShop {
public static void main (String (] args) {
print (11 inside main " );
new Candy () ;
print(IIAfter creat ing Candy " );
try (
Class. forName ("Gum") i
catch (ClassNotFoundException e) {
print ("Couldn' t find Gum") ;
print ( " After Class. f orName (\ "Gum\ " ) 11 ) ;
new Cookie () ;
print("After creating Cookie
lt
);
/ * Output :
inside main
Loading Candy
After creating Candy
Loading Gum
After Class. forName ( "Gum")
Loading Cookie
After creating Cookie
* ///, -
Cada una de las clases de Candy, Gum y eookic tiene una clusula statie que se ejecuta cuando se carga la clase por pri-
mera vez. Se imprimir la infonnacin para decimos cundo tiene lugar la carga de esa clase. En maine ), las creaciones de
objetos estn mezcladas con instmcciones de impresin, como ayuda para detemlnar el instante de la carga.
Podemos ver, a partir de la salida, que cada objeto Class slo se carga cuando es necesario, y que la ini cinlizHcin de tipo
static se realiza durante la carga de la clase. Una lnea particulal111ente interesante es:
Class. forName ( "Gum" ) ;
Todos los objetos elass pertenecen a la clase elass. Un objeto elass es como cualquier otro objeto, por lo que se puede
obtener y manipular Wl3 referencia a l (esto es lo que hace el cargador). Una de las fonnas de obtener una referencia al
objeto Class es el mtodo esttico forName(), que toma un argumento de tipo String que contiene el nombre textual (tenga
cuidado con la ortografa y el uso de maysculas!) de la clase concreta de la cual se quiera obtener una referencia. Elmto-
do devuelve una referencia de tipo Class, que en este ejemplo se ignora; la llamada a forName() se rcali za debido a su efec-
10 secundario que consiste en cargar la clase Gum si no est ya cargada. Durante el proceso de carga se ejecuta la clusula
sta tic de Gum.
En el ejemplo anterior, si elass.forName( ) falla porque no puede encontrar la clase que estemos intentando cargar. gene-
rar una excepcin ClassNotFoundException. Aqui, simplemente nos limitamos a infonnar del problema y a continuar.
pero en otros programas ms sofisticados podramos intentar resolver el problema dentro de la mtina de tratamiento de
excepciones.
Siempre que queramos utilizar la infonnacin de tipos en tiempo de ejecucin, debemos primero obtener una referenci a al
objeto elass apropiado. elass.forName( ) es una fo ml a cmoda de hacer esto, porque no necesitamos un objeto de di cho
tipo para obtener la referencia Class. Sin embargo, si ya disponemos de un objeto del tipo que nos interesa. podemos ext raer
la referencia Class invocando un mtodo que fomJa parte de la clase raz Objeet: gctelass( ). Este mecanismo devuel\'e la
referencia Class que representa el tipo concreto de objeto. Class dispone de muchos mtodos interesantes; el siguiente es
un ejemplo que ilustra algunos de ellos:
11 : typeinfoltoys/ToyTest.java
11 Prueba de la clase Class.
package typeinfo.toys;
import static net.mindview.util.Print. * ;
interface HasBatteries {}
interface Waterproof {}
interface Shoots {}
class Toy
1/ Desactive con un comentario el constructor predeterminado
11 siguiente para ver NoSuchMethodError de (*1 *)
Toy () {}
Toy l int i l {}
class FancyToy extends Toy
implements HasBatteries, Waterproof, Shoots {
FancyToy 11 { super 111; }
public class ToyTest {
static void print l nfo (Class ce) {
print( "Class name: u + ce.getName() +
u is interface? [ " + cc.islnterface() + I']U);
print (ti Simple name: " + ce. getSimpleName () ) ;
print ( "Canonical name : " + cc. getCanonicalName (l ) ;
public static void main(String[] args) {
Class e = null i
try {
c o:: Class. forName ( "typeinfo. toys. FaneyToytl) ;
catch (ClassNotFoundException e) {
print ( "Can I t find FancyToy");
System.exit(1)
printlnfo(c) i
for (Class face : c.getlnterfaces()
printlnfo(face) ;
Class up = c.getSuperclass()
Object obj = null
try {
11 Requiere un constructor predeterminado:
obj = up.newlnstance()
cateh (InstantiationException el {
print ("Cannot instantiate");
System. exi t (l) ;
eatch(IllegalAccessException el {
print(IICannot access")
System.exit(1)
printlnfo(obj .getClass(;
1* Output:
Class name: typeinfo. tays. FancyToy is interface? [false]
Simple name: FancyToy
Canonical name : typeinfo.toys.FancyToy
Class name: typeinfo. tays. HasBatteries i5 interface? [true]
Simple name: HasBatterie5
14 Informacin de tipos 355
356 Piensa en Java
Canonical name : typeinfo.toys.HasBatteries
Class name: typeinfo. toys. Waterproof is interface? [true]
Simple name: Waterproof
Canonical name : typeinfo.toys .Waterproof
Class name: typeinfo . toys . Shoots is interface? [true]
Simple name: Shoots
Canonical name : typeinfo.toys.Shoots
Class name: typeinfo . toys . Toy is interface? (false]
Simple name: Toy
Canonical name : typeinfo.toys.Toy
*///,-
FancyToy hereda de Toy e impl ementa las interfaces HasBatteries, Waterproof y Shoots. En main( ), se crea una refe-
rencia Class y se la inicializa para que apunte al objeto Class FancyToy Class utili zando forName() dentro de un bloque
Iry apropiado. Observe que hay que utilizar el nombre completamente cualificado (incluyendo el nombre del paquete) en la
cadena de caracteres que se pasa a forName( ).
printlnfo() utiliza geIName() para generar el nombre de clase completamente cualificado, y geISimpleName() y
geICanonicaIName() (introducidos en Java SE5) para generar el nombre sin el paquete y el nombre completamente cuali-
ficado. respectivamente. Como su propio nombre indica, islnterface() nos dice si este objeto Class representa un interfaz.
Por tanto, con el objeto Class podemos averiguar casi todo lo que necesi temos saber acerca de un determinado tipo.
El mtodo Class.getlnlerfaces( ) invocado en m.inO devuelve una matriz de objetos Class que representa las interfaces
contenidas en el objeto Class de inters.
Si tenemos un objeto Class, tambin podemos preguntarle cul es su clase base directa utilizando geISuperclass(). Este
mtodo devuelve una referenci a Class que podemos, a su vez, consultar. Por tanto, podemos detenninar la jerarqua de cIa-
ses completa de un objeto en ti empo de ejecucin.
El mtodo newlnstance( ) de Class constituye una forma de implementar un "constructor virtual" que nos pennite decir:
"No s exactamente de qu tipo eres, pero crea una instancia de t mismo de todas formas". En el ejemplo anterior, up es
simplemente una referencia Class de la cual no se conoce ninguna infoffi1acin de tipos adicional en tiempo de compila-
cin. Y cuando creamos una nueva instancia, obtenemos como resultado una referencia Object. Pero dicha referencia apun-
ta a un objeto Toy. Por supuesto, antes de poder enviar a ese objeto ningn mensaje diferente de los que admite Object, es
necesario invest igar un poco acerca del objeto y efectuar algunas proyecciones de tipos. Adems, la clase que se crea con
newlnstance() debe disponer de un constructor predeterminado. Posterionnente en este captulo, veremos cmo crear obje-
tos dinmicamente de una cierta clase utilizando cualquier constructor, empleando para ello la API de reflexin de Java.
Ejercicio 1:
Ejercicio 2:
Ejercicio 3:
Ejercicio 4:
Ejercicio 5:
Ejercicio 6:
Ejercicio 7:
(1) En ToyTest.java, desactive mediante un comentario el constmctor predetenninado de Toy y explique
lo que sucede.
(2) Incorpore un nuevo tipo de interfaz en ToyTest.java y verifique que dicha interfaz se detecta y se
muestra adecuadamente.
(2) Anada Rhomboid a Shapes.java. Cree un objeto Rhomboid y generalcelo a Shape, y vuelva a espe-
cializarlo a Rhomboid. Trate de especial izarlo a un objelO Circle y vea lo que sucede.
(2) Modifique el ejercicio anterior para que utilice instanceof con el fin de comprobar el tipo, antes de
efectuar la especializacin.
(3) Implemente un mtodo rotate(Shape) en Shapes.java, que compmebe si est girando un c rculo (y,
en caso afirmativo, no realice la operacin).
(4) Modifique Shapes.java para que permita "resaltar" (acti vando un indicador) todas las formas de un
tipo concreto. El mtodo toString( ) para cada objeto derivado de Shape debe indicar si dicho objeto
Shape est resaltado".
(3) Modifique SweelShop.java para que la creacin de cada tipo de objeto est controlada por un argu-
mento de la lnea de comandos. En otras palabras, si la lnea de comandos es "java SweetShop Candy",
entonces slo se crear el objeto Candy. Observe cmo se pueden controlar los objetos Class que se car-
gan, utilizando argumentos de la lnea de comandos.
14 Informacin de tipos 357
Ejercici o 8 : (5) Escriba un mtodo que tome un objeto e imprima de manera recursiva todas las clases presentes en la
jerarquia de ese objeto.
Ejercici o 9 : (5) Modifique el ejercicio anterior de modo que utilice Class.getDeclaredFields( ) con el fin de mostrar
tambin infom13cin acerca de los campos contenidos en cada clase.
Ejercici o 10: (3) Escriba un programa para determinar si una matriz de char es un tipo primitivo o un verdadero o j e ~
too
Literales de clase
Java proporciona una segunda fom13 de generar la referencia al objeto Class: el Iileral de clase. En el programa anterior,
dicho literal de clase tendra el aspecto:
FancyToy.class;
lo que no slo es ms simple. sino tambin ms seguro ya que se comprueba en tiempo de compilacin (y no necesita, por
tanto, colocarse dentro de un bloque try). Asimismo, puesto que elimina la llamada al melado forNal11c( ). es tambin ms
eficiente.
Los literales de clase funcionan tanto con las clases nonnales COlT'IO con las interfaces. matrices y tipos primitivos. Adems,
existe un campo estndar denominado TVPE en cada una de las clases envoltorio de los tipos primitivos. El campo TYPE
produce una referencia al objeto Class correspondiente al tipo primitivo asociado, de modo que:
.... equIvIIInee ....
boolcan. class Boolean.T"PE
char.cJass Character.TYPE
byte.class Byte.TYPE
short. cl ass Short.TYPE
int.cJass Integer.TYPE
long.cJass Long.TYPE
float.class Float .TYPE
double.cJass Double.TYPE
\'oid.cJass Void.TYPE
-
En mi opinin, es mejor utilizar las versiones ". class" siempre que se pueda. ya que son ms coherentes con las clases nor-
males.
Es interesante observar que al crear una referencia a un objeto Class utilizando '.class' no se inicializa automticamente el
objeto Class . La preparacin de una clase para su uso consta, en realidad. de tres pasos diferentes :
1. Carga, que es realizada por el cargador de clases. Este proceso localiza el cdigo intennedio (que usualmente se
encuentra en el disco. dentro de la ruta de clases, aunque no tiene porque ser necesariamente as) y crea un obje-
to Class a partir de dicho cdigo intem1edio.
2. A1onlClje. La fase de montaje verifica el cdigo intenncdio de la clase. asigna el campo de almacenamiento para
los campos estticos y. en caso necesario, resuelve todas las referencias que esta clase haga a otras clases.
3. Inicializacin. Si hay una superclase, es preciso inicializarla, ejecutando los inicializadores de tipo static y los
bloques de inicializacin de tipo static.
La inicializacin se retarda hasta que produce la primera referencia a un melodo esttico (el constructor es implcitamente
de tipo sta tic) o a un campo estt ico no constante:
358 Piensa en Java
//: typeinfojClasslnitialization.java
import java.util. *;
class Initable
statie final int staticFinal = 47;
statie final int staticFina12 =
Classlnitialization.rand.nextlnt(lOOOl;
static {
System. out .println ("Initializing Initable");
class Initable2 {
statie int staticNonFinal = 147;
sta tic {
System.out.println(lIInitializing Initable2");
class Initable3 {
static int staticNonFinal = 74;
statie {
System.out.println(ltlnitializing Initable3");
public class Classlnitialization {
public static Random rand = new Random(47 ) ;
pUblic static void main(String[] args) throws Exception
Class initable = Initable.class;
System .out. println ("After creating Initable ref");
II No provoca la inicializacin:
System.out.println(Initable.staticFinal) ;
II Provoca la inicializacin:
System.out.println(Initable.staticFinal2) i
II Provoca la inicializacin:
Systern.out.println(Initable2.staticNonFinal) i
Class initable3 :: Class. forName ( 11 Initable3
11
) ;
System .out. println ( "After creating Initable3 ref") i
System.out.println(Initable3.staticNonFinal) i
1* Output:
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74
* ///,-
En la prctica, la inicializacin es lo ms tarda posible. Analizando la creacin de la referencia nHable, podemos ver que
usar simpl emelllc la sintaxis .class para obtener una referencia a la clase no provoca la inicializacin. Sin embargo.
Class.forName( ) inicializa la clase inmediatamente para generar la referencia Class, como puede ver analizando la crea-
cin de i"ilable3.
Si un valor final esttico es una "constante de tiempo de compilacin", tal como Jnitable.staticFinal, dicho valor puede
leerse sin que ello haga que la clase Initabl e se inicialice. Sin embargo, definir un campo como esttico y final no garanti-
14 Informacin de tipos 359
za este comportamiento: al acceder a Initable.static Final2 se fuerza a la inicial izacin de clase, porque dicho campo no
puede ser una constante de tiempo de compi lacin.
Si un campo esttico no es de tipo fin al, acceder al mismo requiere siempre que se ejecute la fase de montaje (para asignar
el espacio de almacenamiento para el campo) y tambin la de inicializacin (para inicializar dicho espacio de almacena-
mienw) antes de que el valor pueda ser ledo, como puede ver analizando el acceso a Ini table2.staticNonFinal.
Referencias de clase genricas
Una referencia Class apunta a un objeto Class, que genera instancias de las clases y contiene todo el cdigo de los mto-
dos correspondientes a dichas instancias. Tambin contiene los valores estticos de dicha clase. Por tanto, una referencia
Class realmente indica el tipo exacto de aquello a lo que est apuntando: un objeto de la clase Class.
Sin embargo, los di seadores de Java SES vieron la oportunidad de hacer esto un poco ms especfico, pennitiendo restrin-
gir el tipo de objeto Class al que la referencia Class apunta, utilizando para ello la sintaxis genrica. En el siguiente ejem-
plo, ambos tipos de sintaxis son correctos:
/1: typeinfo/GenericClassReferences.java
public class GenericClassReferences
public static void main{String[] args)
Class intClass = int.class;
Class<Integer> genericlntClass int.class
genericlntClass = Integer .class II Lo mismo
intClass = double . class
II genericlntClass = double . class II Ilegal
La referencia de clase nonnal no genera ninguna advertencia de compilacin. Sin embargo, puede ver que la referencia de
clase nomlal puede reasignarse a cualqui er otro objew Class, mientras que la referencia de clase genrica slo puede asig-
narse a su tipo declarado. Utilizando la sintaxis genrica, permilimos que el compilador imponga comprobaciones adicio-
nales de los tipos.
Qu sucede si queremos relajar un poco las restricciones? lnici almente, parece que deberamos ser capaces de hacer algo
como lo siguiente:
Class<Number> genericNumberClass = int.class
Esto parece tener sentido. porque Integer hereda de Number. Sin embargo, este mtodo no funciona, porque el objeto Class
Int eger no es una subclase del objeto Class Number (puede parecer que esta di stincin resulta demasiado sutil ; la analiza-
remos con ms detall e en el Captulo 15. Genricos).
Para relajar las restri cciones al utili zar referencias Class genricas, yo personalmente empleo el comodn, que fonna parte
de los genricos de Java. El smbolo del comodn es ' ?', e indica "cualquier cosa". Por tanto. podemos aadir comodines a
la referencia Class del ejemplo anterior y generar los mi smos resultados:
jI : typeinfo/ WildcardClassReferences.java
public class WildcardClassReferences {
public static void main (String[} args)
Class<?> intClass = int.class;
intClass = double . class
En Java SE5, se prefi ere utilizar Class<?> en lugar de Class, an cuando ambos son equivalemes y la referencia Class nor-
mal. como hemos visto, no genera ninguna advertencia del compil ador. La ventaja de Class<?> es que indica que no esta-
mos pasando una referencia de clase no especfica simplemente por accidente o por ignorancia, sino que hemos elegido la
versin no especifica.
360 Piensa en Java
Para crear una referencia Class que est restringida a un detemlinado tipo o a cualquiera de sus stlbripos, podemos combi-
nar un comodn con la palabra clave extends para crear un limite. Por lanlO, en lugar de decir simplemente
Class<Number>. lo que diramos seria:
11: typeinfo/BoundedClassReferences.java
public class BoundedClassReferences {
public static void main(String[] args)
Class<? extends Number> bounded = int.class;
bounded = double.class;
bounded = Number.class;
II o cualquier otra cosa derivada de Number.
)
/11 ,-
La razn de aiiadir la si ntaxis genrica a las referencias Class estriba nicamente, en real izar una comprobacin de los tipos
en tiempo de compilacin, de modo que si hacemos algo incorrecto lo detectaremos un poco antes. No es posible realizar
nada realmente deslnlctivo con las referencias Class normales, pero si cometemos un error no podremos detectarlo hasta el
tiempo de ejecucin, lo que puede resultar incmodo.
He aqu un ejemplo donde se utiliza la si ntaxis de clases genricas. El ejemplo almacena una referencia de clase y luego
genera un contenedor List relleno con objelOs generados mediante newlnstance( ):
11 : typeinfo/FilledList.java
import java.util.*;
class Countedlnteger
private static long counter;
private final long id = counter++;
public String toString() ( return Long. toString (i d )
public class FilledList<T> {
private Class<T> type
public FilledList (Class<T> type) { this. type
public List<T> create (int nElements) {
List<T> result = new ArrayList<T>();
try {
for(int i = O; i < nElements; i++)
result.addCtype.newlnstance()) ;
catch (Exception e) (
throw new RuntimeException(e);
return resul t;
public static void main(String[] args) (
FilledList<Countedlnteger> fi =
type; )
new FilledList<Countedlnteger> (Countedlnteger.class) ;
System. out.println(fl.create(15)) ;
1* Output:
[O, 1, 2, 3, 4, S, 6, 7, 8, 9, lO, 11, 12, 13, 14J
* /// ,-
Observe que esta clase debe asumir que cualquier tipo con el que trabaje dispondr de un constnlctor predetenninado (uno
que no tenga argumentos), obtenindose una excepcin si no es ste el caso. El compilador no genera ningn tipo de adver-
tencia para este programa.
Cuando utilizamos la sintaxi s genrica para los objetos Class sucede algo interesante: ncwInsta nce( ) devolver el tipo
exacto del objeto. en lugar de si mplemente un objeto bsico Obj ect como vimos en ToyTest.java. Esto resulta un tanto limi-
tado:
JI : typeinfo/ toys / GenericToyTest.java
JI Prueba de la clase Class.
package typeinfo.toys
public class GenericToyTest
public static void main(String[] args ) throws Exception {
Class<FancyToy> ftClass = FancyToy.classi
/1 Produce el tipo exacto:
FancyToy fancyToy = ftClass.newlnstance() i
Class<? super FancyToy> up = ftClass.getSuperclass{) i
JI Esto no se compilar:
/1 Class<Toy> up2 = ftClass.getSuperclass{);
JI Slo produce Object:
Object obj = up . newlnstance () ;
}
/ // , -
14 Informacin de tipos 361
Si obtenemos la supcrclase, el compilador slo nos pcnnit ir decir que la referencia a la superclase es "alguna clase que es
superclase de FancyToy" , como podemos ver en la expresin Class<? super FancyToy>. No aceptar una declaracin de
Class<Toy>. Esto parece un poco extrao, porque getSupercl ass( ) devuelve la clase base (no una interfaz) y el compila-
dor conoce en tiempo de compilacin lo que esa clase es: en este caso, Toy.class, no simplemente "al guna superclase de
FancyToy". En cualquier caso, debido a la vaguedad, el valor de retorno de up. newlnsta nce( ) 110 es de un tipo preci so,
sino slo de tipo Obj cct.
Nueva sintaxis de proyeccin
Java SE5 tambin ha aadido una sintaxi s de proyeccin para utilizarla con las referencias Class, nos referimos al mtodo
cast( ):
/1 : typeinfolclassCasts.java
class Building {}
class House extends Building {}
public class ClassCasts {
public static void main(String[] argsl (
Building b = new House();
Class<House> houseType = House.class;
House h = houseType. cast (b )
h = (House ) b 1/ o haga simplemente esto.
)
///,-
El mtodo cast() toma el objeto proporcionado como argumento y lo proyecta sobre el tipo de la referencia Class. Por
supuesto, si examinamos el cdigo anterior parece que es demasiado trabajo adicional, si lo comparamos con la ltima lnea
de maine ), que hace exactamente lo mismo.
La nueva sintaxis de proyeccin resulta til en aquellas situaciones en las que no podemos utili zar una proyeccin ordina-
ria. Esto sucede, usualmente, cuando estamos escribiendo cdigo genrico (de lo que hablaremos en el Captulo 15,
Genricos), y hemos almacenado una referencia Class que queremos utilizar en algn momento posterior para efectuar la
proyeccin. Este caso no resulta muy frecuente; de hecho, slo he podido encontrar una nica ocasin en la que cast( ) se
use dentro de la biblioteca de Java SE5 (concretamente en com.sun.mirror.ut iI.Decl arationFilter).
Hay otra nueva funcionalidad que 110 se Uliliza en absoluto cnla biblioteca Java SE5: Class.asSubclass(). Este mtodo per-
mite proyectar el objeto de clase sobre un tipo ms especfico.
Comprobacin antes de una proyeccin
Hasta ahora, hemos visto varias fonuas de RTTI , incluyendo:
362 Piensa en Java
J. La proyeccin clsica, por ejemplo, "(Shape);' que utili za RTTl para asegurarse de que la proyeccin es correc-
ta. Esto generar ClassCastException si se ha realizado una proyeccin incorrecta.
2. El objeto Class representativo del objeto. Podemos consultar el objeto Class para obtener informacin til en
tiempo de ejecucin.
En C++. la proyeccin clsica "(Shape)" no utiliza mecani smos RTTI. Simplemente le dice al compilador que trate el obj e-
to como si fuera del tipo indicado. En Java, s realiza la comprobacin de tipos. esta proyeccin se denomina a menudo
"especiali zacin segura en lo que respecta a tipos". La razn de utilizar el tnnino "especializacin" se basa en la di sposi-
cin histricamente utilizada en los diagramas de jerarquas de clases. Si la proyeccin de Circle sobre Shape es una gene-
rali zacin, entonces la proyeccin de Shape sobre Circle es una especializacin. Sin embargo, puesto que el compilador
sabe que un objeto Circle es tambin de tipo Shape, permite que se realicen libremente asignaciones de generalizacin, si n
que sea obligatorio incluir una sintaxis de proyeccin especfica. El compilador no puede saber, dado un objeto Shape, de
qu tipo concreto es ese objeto; podra ser exactamente de Shape, o podra ser un subtipo de Shape, como Circle, Square,
Triangle o algn olro tipo. En ti empo de compilacin, el compilador slo ve un objeto Shape. Por tanto, no nos permitir
que realicemos una asignacin de especializacin sin utilizar una proyeccin especfi ca. con la que le decimos al compila-
dor que disponemos de informacin adicional que nos permite saber que se trata de un tipo concreto (el compilador com-
probar si dicha especializacin es razonable, por lo que no nos permitir efectuar especializaciones sobre un tipo que no
sea realmente una subclase del anterior).
Exi ste un tercer mecanismo de RTTI en Java. Se trata de la palabra clave instanceof, que nos dice si un objeto es una ins-
tancia de un tipo concreto. Devuelve un valor de tipo boolean, as que esta palabra clave se utiliza en fomla de pregunta,
como en el fragmento sigui ente:
if(x instanceof Oog)
(( Dog) x ) .bark( ) ;
La instruccin ir comprueba si el objeto x pertenece a la clase Dog antes de proyectar x sobre Dog. Es importante utilizar
instanceof antes de una especializacin cuando no di spongamos de otra in[onnacin que nos indique el tipo del objeto; en
caso contrario, obtendremos una excepcin ClassCastException.
Nonnalmente, lo que estaremos tratando de locali zar es un determinado tipo (por ejemplo, para pintar de prpura todos los
tringul os), pero podemos fcilmente seleccionar todos los objetos utilizando instanceof. Por ejemplo, suponga que di spo-
nemos de una familia de clases para describir mascotas, Pet, (y sus propietarios, una caracterstica que nos ser til en un
ejemplo posterior). Cada individuo (I ndividual ) de la jerarquia tiene un identificador id y un nombre opcional. Aunque las
clases que siguen heredan de Individual , existen ciertas complejidades en la clase Individual, por lo que mostraremos y
explicaremos di cho cdigo en el Captulo 17, Anlisis detallado de los contenedores. En realidad, no es imprescindible ana-
li zar el cdigo de Individual en este momento; lo nico que necesitamos saber es que podemos crear un individuo con o
sin nombre, y que cada objeto Individual tiene un metodo d() que devuelve un identificador unvoco (creado mediante un
simple recuent o de los objetos). Tambin hay un mtodo toString( ); si no se proporciona un nombre para un objeto
Individual , toString() slo genera el nombre si mple del tipo.
He aqui la jerarqua de clases que hereda de Individual :
// : typeinfo/pets/Person.java
package typeinfo.pets;
public class Person extends Individual {
public Person{String name } ( super(name);
) /// ,-
// : typeinfo/pets/Pet.java
package typeinfo pets;
public class Pet extends Individual {
public Pec(String name) { super(name);
public Pet () { super () ; )
///,-
// , typeinfo/pets/Dog.java
package typeinfo pets;
public class 009 extends Pet {
public Dog (String name ) { super(name )
public Dog () { super () ; }
1/ 1,-
11 , typeinfo/pets/Mutt.java
package typeinfo.pets i
public class Mutt extends 009 {
public Mutt (String name ) { super (name) ;
public Mutt () { super () ; }
111 >
1/ : typeinfo/ pets/Pug . java
package typeinfo.pets
public class Pug extends 009 {
public Pug (String name ) { super (name ) i
public Pug () { super () ; }
1/ 1,-
JI : typeinfo/ pets/ Cat.java
package typeinfo.pets i
public cIass Cat extends Pet {
public Cat (String name ) { super (name ) ;
public Cat () { super () ; }
1/ 1, -
ji : typeinfo/ pets / EgyptianMau . java
package typeinfo.pets;
public class EgyptianMau extends Cat {
public EgyptianMau (String name ) { super (name ) ;
public EgyptianMau (1 { super () ; }
1/ 1,-
// : typeinfo/ pets / Manx.java
package typeinfo.pets;
public cIass Manx extends Cat {
public Manx (String name ) { super (name ) ;
public Manx (1 { super () ; }
111 , -
// : typeinfo/ pets / Cymric . java
package typeinfo.pets;
public class Cymric extends Manx {
public Cymric (String name ) ( super (name ) ;
public Cymric (1 { super () ; }
111 ,-
JI : typeinfo/ pets / Rodent . java
package typeinfo.pets;
public class Rodent extends Pet {
14 Informacin de tipos 363
364 Piensa en Java
public Rodent(String name)
public Rodent (1 { super 11 ;
111>
//: typeinfo/pets/Rat.java
package typeinfo . petsi
super (name) ; }
public class Rat extends Rodent {
public Rat (String name) { super (name) ;
public Rat (1 { super (1; )
111,-
11: typeinfo/pets/Mouse.java
package typeinfo.pets;
public class Mouse extends Rodent {
public Mouse (String name) { super (name) ;
public Mouse (1 { super (1; )
111,-
/1: typeinfo/pets/Hamster .java
package typeinfo.pets;
public class Hamster extends Rodent {
public Hamster(String name) { super (name)
public Hamster () { super (); }
111,-
A continuacin, necesitamos una fanna de crear aleatoriamente diferentes tipos de mascotas, y por comodidad, vamos a
crear matrices y li stas de mascotas. Para pemlitir que esta herramienta evolucione a travs de varias impl ementaciones dife-
rentes. vamos a definir dicha herramienta como una clase abstracta:
1/ : typeinfo/pets/PetCreator.java
// Crea secuencias aleatorias de objetos Peto
package typeinfo.pets
import java.util.*
public abstract class PetCreator {
private Random rand = new Random(47)
II La lista de los diferentes tipos de Pet que hay que crear:
public abstract ListeClasse? extends Pet types()
public Pet randomPet () { / / Crear un obj eto Pet aleatorio
int n = rand.nextlnt (types (l .size())
try {
return types () . get (n) . newlnstance () ;
catch (InstantiationException e) {
throw new RuntimeException{e);
catch{IllegalAccessException el
throw new RuntimeException(e);
public Pet(] createArray{int size)
Pet[] result = new Pet[size];
for(int i = O; i < size; i++)
result[iJ = randomPet() i
return resul t;
public ArrayListePet> arrayList (int size) {
ArrayListePet> result = new ArrayListePet>();
Collections.addAll(result, createArray(size));
return result
}
111>
14 Informacin de tipos 365
El mtodo abstracto getTypes() deja para las clases derivadas la tarea de obtener la lista de objetos Class (esto es una
variante del patrn de diseo basado en el mtodo de las plantillas). Observe que el tipo de clase se especifica como "cual-
quier cosa derivada de Pet", por lo que newlnstance() produce un objeto Pet sin requerir ninguna proyeccin.
randomPet() realiza una indexacin aleatoria en el contenedor de tipo List y utiliza el objeto Class seleccionado para ge-
nerar una nueva instancia de dicha clase con Class.newlnstance(). El mtodo createArray() utiliza randomPet( ) para
rellenar una matriz y arrayList() emplea a su vez createArray() .
Podemos obtener dos tipos de excepciones al llamar a newInstance(). Analizando el ejemplo, podr ver que estas excep-
ciones se tratan en las clusulas catch que siguen al bloque try. De nuevo, los nombres de las excepciones son casi autoex-
plicativos e indican cul es el problema (1llegalAccessException est relacionado con una violacin del mecanismo de
seguridad de Java, en este caso si el constructor predetenninado es de tipo private).
Cuando derivamos una subclase de PetCreator, lo nico que necesi tamos sumini strar es el contenedor List de todos los
tipos de mascotas que queremos crear mediante randomPet() y los otros mtodos. El mtodo getTypes() normalmente
devolver, simplemente, una referencia a un lista esttica. He aqu una implementacin utilizando forName():
11 : typeinfo/pets/ForNameCreator.java
package typeinfo.pets
import java.util.*
public class ForNameCreator extends PetCreator {
private static List<Class<? extends Pet types
new ArrayList<Class<? extends Pet() i
11 Tipos que queremos crear aleatoriamente:
private static String[) typeNames = {
"typeinfo. pets . Mutt 11 ,
IItypeinfo.pets. Pug
ll
,
"typeinfo. pets. EgyptianMau" ,
11 typeinfo. pets. Manx 11 ,
"typeinfo.pets.Cymric
ll
,
"typeinfo.pets.Rat",
11 typeinfo. pets. Mouse" ,
IItypeinfo.pets.Hamster"
} ;
@SuppressWarnings (lIunchecked
ll
)
priva te static void loader() {
try {
for(String name : typeNames)
types.add(
(Class<? extends PetClass.forName{name))
catch(ClassNotFoundException el
throw new RuntimeException {e);
static { loader() }
public List<Class<? extends Pet types () {return types}
111 ,-
El mtodo loader() crea la lista de objetos Class utilizando Class.forName(). Esto puede generar la excepcin
ClassNotFoundException, lo cual tiene bastante sentido, ya que estamos pasndole un objeto String que no puede validar-
se en tiempo de compilacin. Puesto que los objetos Pet se encuentran en el paquete typeinfo, es necesario utilizar el nom-
bre del paquete al hacer referencia a las clases.
Para generar una lista con tipos de objetos Class, se necesita una proyecc in, lo cual produce una advertencia en tiempo de
compilacin. El mtodo loader( ) se define por separado y luego se incluye dentro de una clusula de inicializacin estti-
ca, porque la anotacin @SuppressWarnings no puede insertarse directamente en la clusula de inicializacin esttica.
366 Piensa en Java
Para recontar el nmero de mascotas, necesitamos una herramienta que vaya controlando el nmero de los diferentes tipos
de mascotas. Un contenedor Map resulta perfecto para esta tarea; las claves con los nombres de los tipos de objetos Pet y
los valores son enteros que almacenan el nmero de objetos Pet de cada tipo. De esta fanna, podemos preguntar cosas como
"Cuntos objetos Hamster hay?". Podemos utilizar instanceof para recontar las mascotas:
//: typeinfo/PetCount.java
/1 Utilizacin de instanceof.
import typeinfo.pecs. * ;
import java.util. *
import static net . mindview util.Print.*
public class PetCount {
static class PetCounter extends HashMap<String,Integer>
public void CQunt (String type) (
Integer quantity = get(type);
if(quantity == null)
put (type, 1);
else
put(type, quaneity + 1);
public static void
countPets (PetCreator creator) {
PetCounter counter= new PetCounter();
for(Pet pee : creator . createArray(20))
II Enumerar las mascotas individuales :
)
printnb (pet. getClass () . getSimpleName () + u U);
if(pet instanceof Pet)
counter.count(UPet") ;
if(pet instanceof Dog)
counter.count(OIDog" ) ;
if(pet instanceof Mute )
counter.count ( "Mutt
U
) ;
if(pet instanceof Pug)
couneer . count ( "Pug" ) ;
if(pet instanceof Cat)
counter.count {IICat") ;
if(pet instanceof Manx)
counter. count ("EgyptianMau") ;
if(pet instanceof Manx)
counter. count ( "Manx" ) ;
if(pet instanceof Manx)
counter. count ("Cymric") ;
if(pet instanceof Rodent)
counter . count ( "Rodent 11) ;
if(pet instanceof Rat )
counter. count (" Rat" ) ;
if(pet instanceof Mouse )
counter. count ( "Mouse" ) ;
if(pet instanceof Hamster )
counter . count ( "Hamster" ) ;
II Mostrar las cantidades:
print ();
print (counter ) ;
public static void main(String[) args)
countPets(new ForNameCreator());
14 Informacin de tipos 367
} / * Output o
Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster
EgyptianMau Mutt Mutt cymric Mouse Pug Mouse Cymric
{Pug=3, Cat=9, Hamster=l, Cymric=7, Mouse=2, Mutt=3, Rodent=5,
Pet=20, Manx=7, EgyptianMau=7, 00g=6, Rat=2}
*/// 0-
En countPets(), se rellena aleatoriamente una matri z con obj etos Pet utilizando un obj eto PetCreator. Despus, cada obj e-
to Pet de la matriz se comprueba y se recuenta util izando instanceof.
Existe una pequea restri ccin en la utili zacin de instanceof: podemos comparar nicamente con un tipo nominado, y no
con un obj eto Class. En el ej emplo anterior, podra parecer que resulta tedioso escribir todas esas expresiones instanceof,
y efectivamente lo es. Pero no hay ninguna forma inteli gente de automatizar instanceof creando una matriz de objetos Class
y realizando la comparacin de di chos objetos (aunque, si sigue leyendo, ver que existe una alternati va). Sin embargo, esta
restriccin no es tan grave como pudiera parecer, porque ms adelante veremos que si un di seo nos exige escribir una gran
cantidad de expresiones instanceof probabl emente eso signifique que el di seo no est bi en hecho.
Utilizacin de literales de clase
Si reimplernentamos la clase PctCreator usando literales de clase, el resultado es mucho ms limpio en muchos aspectos:
11 : typeinfo/pets / LiteralPetCreator.java
II Utili zacin de literales de clase.
package typeinfo . pets;
import java . util. ;
public class LiteralPetCreator extends PetCreator {
11 No hace f alta b l oque t r y.
@SuppressWarn ings{"unchecked
n
)
public static final List<Class<? extends Pet allTypes
Collections.unmodifiabl eList {Arrays.asList {
Pet . class, Dog . class, Cat. c l ass, Rodent. class,
Mutt . class, Pug.class, EgyptianMau.class, Manx .class,
Cymric.class, Rat.class, Mouse.class,Hamster . class )) ;
II Tipos para la creacin aleatoria :
private static final List <Class<? extends Pet types =
aIITypes . subList (allTypes . indexOf {Mutt.class ) ,
all Types. size () ) ;
public List <Class<? extends Pet types () {
return typesi
publi c stat ic vo id main {String (] args ) {
System.out.println ( types ) ;
1* Output:
[class typeinfo.pets.Mutt, class typeinfo.pets.Pug, class typeinf o.pets.EgyptianMau, class
typeinfo . pets . Manx, class typeinfo.pets . Cymric, class typeinfo.pets.Rat, class
typeinfo.pets . Mouse, class typeinfo.pets.Hamster]
*/// 0-
En el ejemplo PetCount3.java, que veremos ms adelante, necesitamos precargar un contenedor Map con todos los tipos
de Pel (no slo con aquellos que hayan de ser generados aleatoriamente), por lo que es necesaria la li sta allTypes Lisl, que
incluye lodos los tipos. La li sta typcs es la parte de allTypes (creada mediante List.subList()) que incluye los tipos exac-
tos de mascota, por lo que es la que se utili za para la generacin aleatoria de objetos Pet.
Esta vez, la creacin de types no necesita estar rodeada por un bloque try, puesto que se evala en tiempo de compilacin
y no generar ninguna excepcin a diferencia de Class.forName( ).
Ahora tenemos dos implementaciones de PetCreator en la biblioteca typeinfo.pels. Para definir la seglmda como imple-
mentacin predetenninada, podemos crear un envoltorio que utilice LiteralPetCreator:
368 Piensa en Java
//: typeinfo/pets/Pets.java
// Envoltorio para generar un PetCreator predeterminado.
package typeinfo.pets;
import java.util. *
public class Pets {
public sta tic final PetCreator creator
new LiteralPetCreator()
public static Pet randomPet()
return creator .randomPet () ;
public static Pet [] createArray(int size) {
return creator . createArray (size)
public static ArrayList<Pet:> arrayList (int size) {
return creator .arrayList (size)
}
111,-
Esto proporciona tambi n una indireccin para acceder a randomPet( ), createArray( ) y .rrayList().
Puesto que PetCount.countPets() toma como argumento PetCreator, podemos probar fcilmente la clase de
LiteralPetCreator (mediante el envoltorio anteriorment e definido):
//: typeinfo/PetCount2 . java
import typeinfo.pets.*
public class PetCount2 {
public static void main(String[] args)
PetCount.countPets(Pets.creator) ;
/* (Execute to see output) *///:-
La salida es igual que la de PetCount.java.
Instanceof dinmico
El mtodo Class.islnstance() proporciona una ronlla para probar dinmicamente el tipo de un objeto. Por tanto, podemos
el iminar todas esas tediosas instrucciones instanceof de PetCount.java:
//: typeinfo/PetCount3.java
// Utilizaci6n de islnstance()
import typeinfo.pets.*
import java.util.*
import net.mindview.util.*;
import static net.mindview.util.Print.*
public class PetCount3 {
static class PetCounter
extends LinkedHashMap<Class<? extends Pet:>,Integer:> {
public PetCounter () {
super (MapData.map (LiteralPetCreator. allTypes, O));
public void count (Pet pet) (
// Class .islnstance {) elimina instrucciones instanceof:
for(Map.Entry<Class<? extends Pet>,Integer:> pair
: entrySet())
if (pair .getKey() . islnstance (pet))
put(pair.getKey(), pair.getValue() + 1);
public String toString ()
StringBuilder resul t = new StringBuilder (" {") ;
for(Map.Entry<Class<? extends Pet>,Integer> pair
entrySet ()) {
result.append(pair.getKey() .getSimpleName( i
result,append("=") i
result.append(pair.getValue( ;
resul t . append ( ", ") i
result.delete{result.length()-2, result.length(;
result. append ( "} ") ;
return result.toString();
public static void main (String [J args) {
PetCounter petCount = new PetCounter();
for (Pet pet : Pets.createArray(20 {
printnb (pet . getClass () . getSimpleName () + I! " );
petCount.count(pet) ;
print ();
print(petCount) ;
/ * Output:
Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat
14 Informacin de tipos 369
EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric
{Pet =20, Dog=6, Cat=9, Rodent=5, Mutt=3, Pug=3, EgyptianMau=2,
Manx=7, Cymric=5, Rat=2, Mouse=2, Hamster=l}
*1 I lo-
Para contar todos los tipos diferentes de objetos Pet, se precarga el mapa PetCounter Map con los tipos de
LiteraIPetCreator.aIlTypes. Esto utili za la clase net.mndvew.utl.MapData, que toma un objeto Iterable (la li sta
allTypes) y un valor constante (cero, en este caso) y rellena el mapa con claves tomadas de allTypes y valores iguales a
cero). Sin precargar el contenedor de tipo Map, lo que haramos sera contar los tipos que se generan aleatoriamente y no
los tipos base como Pet y Cato
Como puede ver, el mtodo islnstance() ha eliminado la necesidad de utili zar expresiones instanceof. Adems, esto signi-
fica que podemos aadir nuevos tipos de Pet si mplemente cambiando la matri z LiteraIPetCreator.types; el resto del pro-
grama no necesita modifi cacin (al revs de lo que suceda al utili zar expresiones instanceof).
El mtodo toString( ) ha sido sobrecargado para obtener una salida ms legibl e que siga correspondiendo con la salida tpi-
ca que podemos ver a la hora de imprimir un contenedor de tipo Map.
Recuento recursivo
El mapa en PetCounO.PetCounter estaba precargado con todas las diferentes clases de obj etos Peto En lugar de sobrecar-
gar el mapa, podemos utili zar Class.sAssignableFrom() y crear una herramienta de propsito general que no est limita-
da a recontar objetos Pet:
11 : net/mindview/util/TypeCounter.java
11 Recuenta instancias de una familia de tipos .
package net.mindview.util;
import java . util . *;
public class TypeCounter extends
private baseType;
public TypeCounter baseType) {
this.baseType = baseType;
public void count(Object obj)
type = obj . getClass();
370 Piensa en Java
if(!baseType.isAssignableFrom(type) )
throw new RuntimeException(obj + incorrect type:
+ type + ", should be type or subtype of "
+ baseType) i
countClass(type) ;
private void countClass (Class<?> type) {
Integer quantity = get(type);
put(type, quantity == null ? 1 : quantity + 1) i
Class<?> superClass = type.getSuperclass() i
if(superClass != null &&
baseType.isAssignableFrom(superClass
countClass(superClass) ;
public String toString () {
StringBuilder result = new StringBuilder (" {") ;
for(Map.Entry<Class<?>,Integer> pair : entrySet{
result.append(pair.getKey() . getSimpleName(;
result.append("= " ) ;
result.append(pair.getValue( ;
resul t. append (", ");
resul t . delete (resul t .length () - 2, result .length () ) ;
result.append("} " l;
return result.toString();
El mtodo count() obtiene el obj eto Class de su argumento y utili za isAssignableFrom() para reali zar una comprobaci n
en tiempo de ejecucin con el fin de veri ficar que el objeto que se le haya pasado pertenece verdaderamente a la jerarqua
de clases que nos interesa. countClass() incrementa primero el contador correspondiente al tipo exacto de la clase. Despus,
si baseType es asignable desde la superclase, se invoca a countClass( ) recursivamente en la superclase.
11 : typeinfo/PetCount4.java
import typeinfo.pets .* ;
import net . mindview.util .* ;
import static net.mindview.util . Print.*;
public class PetCount4 {
public static void main(String[] args) {
TypeCounter counter = new TypeCounter(Pet.classl;
for(Pet pet : Pets.createArray(20 {
printnb (pet. getClass () . getSimpleName () + " ");
counter.count(pet) ;
print ();
print (counter) ;
1* Output: (Sample)
Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat
EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymr ic
{Mouse=2 , 00g=6 , Manx=7 , EgyptianMau=2, Rodent=S, Pug=3,
Mutt=3, Cymric=S, Cat=9, Hamste r =l , Pet=20, Rat=2}
* /// , -
Como puede ver anal izando la salida, se cuentan ambos tipos base as como los tipos exactos.
Ejercicio 11: (2) Aada Gerbil a la biblioteca typeinfo.pets y modifique todos los ejemplos del captulo para adaptar-
los a esta nueva clase.
Ejercicio 12: (3) Ut ili ce TypeCounter con la clase CoffeeGenerator.java del Capitulo 15, Genricos.
14 Informacin de tipos 371
Ejercici o 13: (3) Ulilice TypeCounter con el ejemplo RegisteredFactories.java de esle captulo.
Factoras registradas
Unos de los problemas a la hora de crear objetos de la jerarqua Pet es el hecho de que cada vez que aadimos un nuevo
lipa de objelo Pel a la jerarqua lenemos que acordamos de aadirlo a las entradas de LiteraIPetCr eator.java. En aquellos
sistemas donde tengamos que aadir un gran nmero de clases de fanna habitual. esto puede llegar a ser problemtico.
podramos pensar en aadir un inicializador esttico a cada subclase, de modo que el inicializador aadiera su clase a una
lista que se conservara en algn lugar. Desafortunadamente, los inicializadores estticos slo se invocan cuando se carga por
primera vez la clase, as que tenemos el tpico problema de la gallina y el huevo: el generador no tiene la clase en su lista,
por lo que nunca puede crear un objeto de esa clase, as que la clase no se cargar y no podr ser incluida en la li sta.
Bsicamente, podemos obligarnos a crear la lista nosotros mismos de manera manual (a menos que queramos escribir una
herramienla que analice el cdigo fuenle y luego genere y compile la liSia). Por lanto. lo mejor que podemos hacer. proba-
blemente, es colocar la lista en algn lugar central lo suficientemente obvio. Seguramente, el mejor lugar ser la clase base
de la jerarqua de clases que nos interese.
El otro cambio que vamos a hacer aqu es diferir la creacin del objeto, dejndoselo a la propia clase, utilizando el parrn
de diseo denominado mtodo de/aclora. Un mtodo de factora puede invocarse polimrficamente y se encarga de crear
por nosotros un objeto del tipo apropiado. En esta versin muy simple, el mtodo factora es el mtodo create() de la inter-
faz Factor y:
11: typeinfo/factory/Factory . java
package typeinfo.factory;
public interface Factory<:T> { T create (); } 111:-
El parmelro genrico T pennite a create() devolver un lipa diferenle por cada implemenlacin de Factory. ESlo hace uso
tambin de los tipos de retomo covariantes.
En este ejemplo, la clase base Part contiene un contenedor List de objetos factora. Las factoras correspondientes a los
lipos que deben generarse medianle el mtodo createRandom( ) se "registran" ante la clase base aadindolos a la liSia
partFactori es:
11 : typeinfo/ RegisteredFactories.java
1/ Registro de factoras de clases en la clase base.
import typeinfo.factory.*;
import java.util .* ;
class Part {
public String toString () {
return getClass() .getSimpleName{);
static List<:Factory<:? extends Part partFactories
new ArrayList<:Factory<:? extends Part();
static {
II Collections.addAll( ) genera una advertencia "unchecked generic
/ I array creation ... for varargs parameter".
partFactories.add(new FueIFilter.Factory()) j
partFactories.add(new AirFilter.Factory());
partFactories . add(new CabinAirFilter.Factory());
partFactories.add(new OilFilter.Factory());
partFactories.add(new FanBelt.Factory());
partFactories.add{new PowerSteeringBelt.Factory());
partFactories.add(new GeneratorBelt.Factory());
private static Random rand = new Random{47) j
public static Part createRandom () {
int n = rand.nextlnt{partFactories.size());
return partFactories. get (n) . create () ;
372 Piensa en Java
class Filter extends Part {}
class FuelFilter extends Filter
// Crear una factora de clases para cada tipo especfico:
public static class Factory
implements typeinfo.factory.Factory<FuelFilter>
public FuelFilter create() { return new FuelFilter() i
class AirFilter extends Filter {
public static class Factory
implements typeinfo.factory.Factory<AirFilter>
public AirFilter create{) { return new AirFilter( );
class CabinAirFilter extends Filter {
public sta tic class Factory
implements typeinfo.factory . Factory<CabinAirFilter>
public CabinAirFilter create () {
return new CabinAirFilter();
class oilFilter extends Filter {
public static class Factory
implements typeinfo .factory.Factory<OilFilter>
public OilFilter crea te () { return new Oi lFil ter () i
class Belt extends Part {}
class FanBelt extends Belt
public static class Factory
implements typeinfo.factory.Factory<FanBelt>
public FanBelt create() ( return new FanBelt() i
class GeneratorBelt extends Belt (
public static class Factory
implements typeinfo.factory.Factory<GeneratorBelt>
public GeneratorBelt crea te () (
return new GeneratorBelt();
class PowerSteeringBelt extends Belt {
public static class Factory
implements typeinfo . factory.Factory<PowerSteeringBelt>
public PowerSteeringBelt create () {
return new PowerSteeringBelt();
public class RegisteredFactories {
public static void main(String [] argsl {
far(int i = O; i < 10; i++)
System out.println(Part.createRandom());
/ * Output:
GeneratorBelt
CabinAirFilter
Gene ratorBelt
AirFilter
PowerSteeringBelt
CabinAirFilter
FuelFilter
PowerSteeringBelt
powerSteeringBelt
FuelFilter
*///,-
14 Informacin de tipos 373
No todas las clases de la jerarqua deben instanciarse; en este caso, Filter y Belt son simpl emente clasificadores, por lo que
no se crea ninguna instancia de ninguno de ellos, sino slo de sus subclases. Si una clase debe ser creada por
createRandom(), contendr una clase Factory ntema. La nica forma de reutilizar el nombre Factory, como hemos visto
antes, es mediante la cualificacin typeinfo.factory.Factory.
Aunque podemos utili zar Colleetions.addAIl( ) para aadir las factoras a la li sta, el compilador se quejar, generando una
advertencia relati va a la "creacin de una matri z genrica" (lo que se supone que es imposible, como veremos en el Captulo
15, Genricos), por lo que hemos preferido invocar add(). El mtodo createRandom() selecciona aleatoriamente un obje-
to factora de partFactories e invoca su mtodo create() para generar un nuevo objeto Part.
Ejercicio 14: (4) Un constructor es un tipo de mtodo de factora. Modifique RegisteredFactories.j ava para que en
lugar de utilizar una factora explcita, el objeto clase se almacene en el contenedor List, uti li zndose
newlnstanee() para crear cada objeto.
Ejercicio 15: (4) Implemente un nuevo PetCre.tor utli zando factoras regi stradas y modifique el mtodo envoltorio de
la seccin "Utili zacin de literales de clase" para que emplee este nuevo objeto en lugar de los otros dos.
Haga los cambios necesarios para que el resto de los ejemplos que utili cen Pets.java sigan funcionando
correctamente.
Ejercicio 16: (4) Modifique la jerarqua Coffee del Captulo 15, Genricos, para utilizar jerarquas registradas.
instanceof y equivalencia de clases
Cuando tratamos de extraer iufonnacin sobre los tipos, existe una diferencia imporlante entre ambas fonnas de instancc-
of (es decir, instanceof o isInst.nee(), que produce resultados equivalentes) y la comparacin directa de los objetos Class.
He aqu un ejemplo que ilustra la diferencia:
11 : typeinfo/Fami1yVsExactType.java
II La diferencia entre instanceof y los objetos clase
package typeinfo
import static net.mindview.uti1 . Print.*
c1ass Base {}
c1ass Derived extends Base {}
pub1ic c1ass Fami1yVsExactType
static void test (Object x) {
print (IITesting x of type + x. getClass () ) ;
print ("x instanceof Base + (x instanceof Base;
374 Piensa en Java
print (" x instanceof Deri ved "+ (x instanceof Deri ved) ) ;
print ("Base . islnstance (xl "+ Base. class. islnstance (x l) ;
print("Derived.islnstance{x) " +
Derived.class.islnstance(x)) ;
print{"x.getClass() == Base.class " +
(x.getClass() == Base.class));
print("x.getClass() == Derived.class 11 +
(x .getClass () == Derived.class));
print (!1 x. getClass () . equals (Base . class)) n +
(x .getClass () .equals (Base.class))) i
print("x.getClass() .equals(Derived.class)) " +
(x . getClass () . equals (Deri ved. class) ) ) ;
public static void main (String [] argsl {
test (new Base());
test(new Derived());
/ * Output:
Testing x of type class typeinfo.Base
x instanceof Base true
x instanceof Derived false
Base. islnstance (x) true
Derived. islnstance (x) false
x.getClass() == Base.class true
x.getClass() == Derived.class false
x.getClass () .equals(Base.class ) true
x.getClass () .equals (Derived.class false
Testing x of type class typeinfo.Derived
x instanceof Base true
x instanceof Derived true
Base. islnstance (x ) true
Derived.islnstance(x) true
x.getClass() == Base.class false
x.get Class() == Derived.class true
x .getClass () . equals (Base. class )) false
x.getClass() .equals (Derived.class )) true
* ///,-
El mtodo teste ) realiza una comprobacin de tipos con su argumento, utilizando ambas fonnas de instanceof. Despus,
obtiene la referencia al objeto Class y emplea = y equals() para comprobar la igualdad de los objetos Class . Como cabra
esperar, instaneeoC e islnstanee() producen exactamente los mi smos resultados, al igual que equals() y =. Pero las prue-
bas muestran que se obtienen diferentes conclusiones. Basndose en el concepto de tipos, instanceof dice: "Perteneces a
esta clase o a una clase derivada de sta?". Sin embargo. si comparamos los objetos Class utilizando =, no entran en juego
los conceptos de herencia: o son tipos exactamente iguales o no lo son.
Reflexin: informacin de clases en tiempo de ejecucin
Si no conocemos el tipo concreto de un objeto, el mecanismo RTIl nos los dir. Sin embargo, existe una limitacin: el tipo
debe ser conocido en tiempo de compilacin, para poder detectarlo utilizando RTfI y para poder hacer algo ti l con la infor-
macin. Dicho de otro modo, el compilador debe conocer todas las clases con las que estemos trabajando.
A primera vista, esto no parece que sea una limitacin importante, pero suponga que nos entregan una referencia a un obje-
to que no se encuentra en nuestro espacio de programa. De hecho, suponga que la clase del objeto no est ni siquiera dis-
ponible para nuestro programa en tiempo de compil acin. Por ejemplo, suponga que extraemos una serie de bytes de un
archivo de disco o de una conexin de red, y nos dicen que esos bytes representan una clase. Dado que esta clase aparece
despus de que el compilador haya generado el cdigo de nuestro programa, cmo podramos utilizar esta clase?
En un entorno de programacin tradicional, este escenario parece un poco futurista. Sin embargo, a medida que nos despla-
zamos hacia un mundo de programacin ms amplio, aparecen casos de gran importancia en los que lo que sucede es pre-
14 Informacin de lipos 375
cisamente esto. El primero de esos casos es la programacin basada en componentes. en la que constnlimos los proyectos
utilizando herramientas RAD (Rapid Applica/ion Deve/opmen/. desarrollo rpido de aplicaciones) dentro de un entorno IDE
(Integrafed Developmenr Environmem, entorno integrado de desarrollo). que fanna parte de una herramienta de generacin
de aplicaciones. Se trata de un enfoque visual para la creacin de programas, mediante el que se desplazan hasta un fonnu-
lario una serie de iconos que representan componentes. Estos componentes se configuran entonces estableciendo algunos de
SUS valores durante el desarrollo. Esta configuracin en tiempo de diseo requiere que todos los componentes sean instan-
ciables, que expongan hacia el exterior partes de s mismos y que pennitan que sus propiedades se lean y se modifiquen.
Adems. los componentes que gestionan sucesos GUI (Graphica/ User IIl/elface) deben exponer la informaci n acerca de
los mtodos apropiados, de modo que el entamo lOE pueda ayudar al programador a la hora de sustituir dichos mtodos de
tratamiento de sucesos. La reflexin proporciona el mecanismo para detectar los mtodos di sponibles y generar los nom-
bres de los mtodos. Java proporciona una estrucrura para la programacin basada en componentes mediante JavaBeans
(este tema se describe en el Capitulo 22, Imelfaces grficas de usuario).
Otra razn importante para descubrir la infonnacin de clases en tiempo de ejecucin es tener la posibilidad de crear y eje-
cutar objetos en platafomlas remotas, a travs de una red. Esto se denomina invocacin remota de melodos (RM-I, RemOle
Method Invocation), y pennite a un programa Java tener objetos distribuidos entre muchas mquinas. Esta distribucin
puede tener lugar por diversas razones. Por ejemplo, quiz estemos realizando una tarea que requiera clculos intensivos y,
para acelerar las cosas, podemos intentar descomponerla y asignar panes del trabajo a las mquinas que estn inactivas. En
otras si tuaciones, puede que queramos colocar el cdigo que gestiona tipos concretos de tareas (por ejemplo, "reglas de
negocio" en una arquitectura cliente/servidor multinivel) en una mquina concreta, de modo que la mquina se convierta en
un repositorio comn que describa dichas acciones y que pueda ser fci lmente modificado para que los cambios afecten a
todo el sistema (se trata de un concepto bastante interesante, ya que la mquina existe exclusivamente para facilitar la modi-
ficacin del software). En la misma lnea, la informtica distribuida tambin soporta la utilizacin de hardware especia-
lizado que puede resultar adecuado para una tarea concreta, por ejemplo, en inversiones de matrices. pero inapropiado o
demasiado caro para la programacin de propsito general.
La clase Class sopona el concepto de reflexin, junto con la biblioteca java.lang.rcOcct que contiene las clases Field,
Mcthod y Constructor (cada una de las cuales implementa la interfaz Member). Los objetos de estos tipos son creados por
la mquina JVM en tiempo de ejecucin para representar el miembro correspondiente de la clase desconocida. Entonces,
podemos utilizar los objetos Constructor (constructores) para crear nuevos objetos, los mtodos get( ) y set() para leer y
modificar los campos asociados con los objetos Field y el mtodo invoke() para invocar un mtodo asociado con un obje-
to Method. Adems, podemos invocar los mtodos de utilidad getFields( ). getMethods( ). gctConstructors( ), etc., con
el fin de obtener como resultado matrices de objetos que representen los campos, mtodos y constructores (puede averiguar
ms detalles examinando la clase Class en la documentacin del JDK). As, la informacin de clase para objetos annimos
puede determinarse completamente en tiempo de ejecucin y no es necesario tener ninguna infonnacin en tiempo de com-
pilacin.
Es importante comprender que no hay ninguna especie de mecanismo mgico en la reflexin. Cuando se utili za la refl exin
para interactuar con un objeto de un tipo desconocido, la mquina NM simplemente examinar el objeto y comprobar que
pertenece a una clase concreta (al igual que con el mecani smo RTTl normal). Antes de poder hacer nada con l, es necesa-
rio cargar el objeto Class. Por tanto, el archivo .class para ese tipo concreto deber seguir estando disponible para la JVM,
bien en la mquina local o a travs de la red. Por tanto, la verdadera diferencia entre RTf 1 y la reflexin es que, con la RTf!.
el compilador abre y examina el archivo .c1ass en tiempo de compilacin. Dicho de otra fonna, podemos invocar todos Jos
mtodos de un objeto de la forma "nornlal". Con el mecanismo de reflexin, el archivo .c1ass no est disponible en tiempo
de compilacin, sino que el que lo abre y examina es el entorno de tiempo de ejecucin.
Un extractor de mtodos de clases
Nom13lmente, no vamos a necesitar utilizar las herramientas de reflexin directamente, pero s que pueden resultar tiles
cuando necesitemos crear cdigo ms dinmico. La reflexin se ha incluido en el lenguaje para soportar otras caractersti-
cas de Java, como la serializacin de objetos y JavaBeans (ambos temas se tratan posterionnente en el libro). Sin embargo,
hay ocasiones en las que resulta muy ti l extraer dinmicamente la infonnacin acerca de una clase.
Consideremos el caso de un extractor de mtodos de clases. Examinando el cdigo fuente de la definicin de una clase o la
documentacin del JDK, slo podemos conocer los mtodos definidos o sustiruidos dentro de dicha definicin de clase. Pero
puede haber otra docena de mtodos disponibles que procedan de las clases base. Localizar estos mtodos es muy tedioso
376 Piensa en Java
y requiere mucho ti empo]. Afortunadamente, el mecanismo de reflexin proporciona una fonna de escribir una herramien_
tas simple que nos muestre automticamente la interfaz completa. He aqu la fonna en que funciona:
jj: typeinfojShowMethods.java
jj Utilizacin de la reflexin para mostrar todos los mtodos de una clase,
jj incluso aunque los mtodos estn definidos en la clase base.
II {Args, ShowMethods}
import java.lang.reflect. *;
import java.util.regex.*;
import static net.mindview.util.Print.*;
public class ShowMethods {
private static String usage
"usage:\nll +
"ShowMethods qualified.class.name\n
ll
+
"To show all methods in class or:\n" +
"ShowMethods qualified.elass.name word\n
ll
+
"To search for methods involving 'word
'
'';
private static Pattern p = Pattern . compile{II\\w+\\ ." )
public statie void main {String (] args) {
if (args .length < 1) {
print(usage)
System.exit (O)
int lines = O;
try {
Class<?> c = Class.forName{args[O]);
Method(] methods = e . getMethods() i
Construetor(] ctors = e.getConstruetors();
if(args.length == 1 ) {
for(Method method methods)
print{
p. mateher (met hod. toString () ) . replaeeAll (" " ) ) i
for{Construetor etor : ctors)
print {p . mateher (etor. toString () ) . replaceAll (II") )
lines = methods.length + ctors.length
else {
for(Method method : methods)
if(rnethod.toStringll .indexOf(args[l]) != -1) {
print(
p. matcher (method. toString () ) . replaceAll (" 11) )
lines++
for(Construetor etor : etors)
if(ctor.toString() . indexOf(args[l]) != -1 ) {
print(p.mateher(
etor.toString() .replaeeAll("");
lines++
eateh(ClassNotFoundExeeption e)
print ( liNo such class: " + el
I Especialmente en el pasado. Sin embargo. SUIl ha mejorado enonnemente su documentacin HTML sobre Java, por lo que ahora es ms fcil consultar
los mtodos de las clases base.
} /* Output,
public static void main(String[])
public native int hashCode()
public final native Class getClass ()
public final void wait(long,int ) throws InterruptedException
public final void wait() throws InterruptedEx ception
public final native void wait (long) throws InterruptedException
public boolean equals{Object)
public String toString( )
public fina l native void notify{)
public final native void notifyAll()
public ShowMethods()
* / / / >
14 Informacin de tipos 377
Los mtodos getMethods( ) y getConstructors( ) de Class devuel ven una matri z de tipo Method y una matriz de tipo
Constructor, respectivamente. Cada una de estas clases tiene mtodos adicionales para diseccionar los nombres, argumen-
toS y valores de retomo de los mtodos que representan. Pero tambin podemos utili zar toString(), como se hace en el ejem-
plo, para producir una cadena de caracteres con toda la signatura del mtodo. El resto del cdigo extrae la informacin de
la lnea de comandos, determina si una signatura concreta se corresponde con la cadena buscada (ut ili zando indexOf( y
elimina los cualificadores de los nombres utilizando expresiones regulares (presentadas en el Captulo 13, Cadenas de
caracteres).
El resultado producido por Class.forName() no puede ser conocido en tiempo de compilacin, y por tanto toda la infonna-
cin de signaturas de mtodos se est extrayendo en tiempo de ejecucin. Si analiza la documentaci n del JDK sobre el
mecanismo de reflexin, ver que existe el suficiente soporte como para poder realizar una invocacin de un mtodo sobre
un objeto que sea totalmente desconocido en ti empo de compilacin (ms adelante en el libro se proporcionan ejemplos de
esto). Aunque inicialmente pueda parecer que no vamos a llegar nunca a necesitar esta funcionalidad, el valor de los meca-
ni smo de reflexin puede resultar ciertamente sorprendente.
La salida anterior se genera mediante la linea de comandos:
java ShowMethods ShowMethods
Puede ver que la salida incluye un constructor predetenninado pblico, an cuando no se haya definido ningn conslmctor.
El constmctor que vemos es el que el compilador sinteti za de forma automt ica. Si luego ejecutamos ShowMethods con
una clase no pblica (es decir, acceso de paquete), el constructor predeterminado sintetizado no aparecer en la salida. El
constructor predetenninado sinteti zado recibe automticamente el mi smo acceso que la clase.
Otro experimento interesante consiste en invocar java ShowMethods java.lang.String con un argumento adicional de tipo
char, int, String, etc.
Esta herramienta puede ahorramos mucho tiempo mientras programamos, en aquellos casos en los que no recordemos si
una clase dispone de un mtodo concreto y no tengamos ganas de examinar el ndice o la jerarqua de clases en la documen-
tacin del JDK, o bien si no sabemos, por ejemplo, si dicha clase puede hacer algo con, por ejemplo, obj etos de tipo Color.
El Captulo 22, In/e/faces grficas de usuario, conti ene una versin GUl de este programa (personalizada para extraer infor-
macin para componentes Swing), por lo que puede dejar ese programa ejecutndose mientras est escri biendo cdigo para
poder realizar bsquedas rpidas.
Ejercicio 17: (2) Modifique la expresin regular de ShowMethods.java para eliminar tambin las palabras clave
native y final (consejo: utilice el operador OR "' ).
Ejercicio 18: (1) Defina ShowMethods como una clase no pblica y verifique que el constructor predetenninado sinte-
tizado no aparece a la salida.
Ejercicio 19: (4) En ToyTest.java, utilice la renexin para crear un objeto Toy utilizando el constructor no predetenni-
nado.
Ejercicio 20: (5) Examine la interfaz de java.lang.Class en la documentacin del IDK que podr encontrar en
hllp:l/java.sun.com. Escriba un programa que tome el nombre de una clase como un argumento de la lnea
de comandos, y luego utilice los mtodos Class para volcar toda la infonnacin di sponible para esa clase.
Compruebe el programa con una cJase de la biblioteca estndar y con una clase que usted mi smo defina.
378 Piensa en Java
Proxies dinmicos
El patrn de di seo Proxy es uno de los patrones de di seo bsicos. Se trata de un objeto que insertamos en lugar del obje
te ureal" para proporcionar operaciones adicionales o diferentes, estos objetos nonnalmente se comunican con un objeto
"real ", de manera que un proxy acta tpicamente como un intennediario. He aqu un ejemplo trivial para mostrar la estmc
tura de un proxy:
jj : typeinfo/SimpleProxyDemo.java
import static net.mindview. util.Print.*;
interface Interface {
void doSomething()
void somethingElse(String arg)
class RealObject implements Interface {
public void doSomething () ( print ( ldoSomething" ) ;
public void somethingElse (St ring arg) (
print("somethingElse 11 + arg) i
class SimpleProxy implements Interface {
private Interface proxied
public SimpleProxy (Interface proxied) (
this.proxied = proxied;
public void doSomething()
print{"SimpleProxy doSomething")
proxied.doSomething() ;
public void somethingElse (String arg) {
print (IISimpleProxy somethingElse 11 + arg );
proxied . somethingElse (arg) i
class SimpleProxyDemo {
public static void consumer (Interface iface) (
iface.doSomething() ;
iface. somethingElse ( "bonobo
lt
) ;
public static void main(String[] args) (
consumer{new RealObject(;
consumer(new SimpleProxy(new RealObject();
1* Output:
doSomething
somethingElse bonobo
SimpleProxy doSomething
doSomething
SimpleProxy somethingElse bonobo
somethingElse bonobo
, /// ,-
Puesto que consumer() acepta una Interface, no puede saber si est conteniendo un objeto real RealObj ect o un Proxy,
porque ambos implementan Interface. Pero el Proxy, que se ha insertado entre el cliente y el objeto ReaIObjec!, reali za
operaciones y luego invoca el mtodo idntico de RealObj ec!.
14 Informacin de tipos 379
Un prOJ.y puede ser til siempre que queramos incluir operaciones adicionales en un lugar distinto que el propio "objeto
fea!" , y especialmente cuando queramos poder cambiar fcilmente entre una situacin en la que se usen esas operaciones
adicionales Y otra en la que no se empleen, y viceversa (el objeto de utilizar patrones de diseo consiste en encapsular los
cambios, as que slo si se tienen que efectuar modificaciones para justificar el uso de un patrn). Por ejemplo, qu suce-
de si quisiramos controlar las Llamadas a los mtodos del objeto RealObject , o medir la carga de procesamiento asociada
a dichas llamadas? Este tipo de cdigo no conviene incorporarlo en la aplicacin, por lo que un proxy nos pennite aadirlo
y eliminarlo fcilmente.
El concepto pro.\y dinmico de Java lleva el concepto de pro.\y un paso ms all, tanto porque crea el objeto dinmicamen-
te cuanto porque gestiona dinmicamente las llamadas a los mtodos para los cuales hemos insertado un pro.\y. Todas las
llamadas realizadas a un proxy dinmico se redirigen a un nico gestor de invocaciones, cuya tarea consiste en descubrir
qu es cada llamada y en decidir qu hacer con ella. He aqu el programa SimpleProxyDemo.java reescrito para utilizar un
prD.\y dinmico:
JI : typeinf o/SimpleDynamicProxy . java
import java .lang.reflect. *
class DynamicProxyHandler implements InvocationHandler
private Obj ect praxied
public Dynami cProxyHandler (Object proxied) {
this .proxied = proxied
publ i c Object
invoke(Object proxy, Method method, Obj e ct[) args)
throws Throwable {
System.out.println(" **** proxy: " + proxy.getClass( ) +
", methad: 11 + methad + " , args : 11 + args)
if(args != null)
for {Object arg : args)
System. out .println(" 11 + arg)
return methad .invoke(proxied, args);
class SimpleDynamicProxy {
public static void consumer{Interface iface) {
iface.doSomething{)
iface.somethingElse{lbonobo")
public static void main{String[] args)
RealObject real = new RealObject()
consumer (real) i
IJ Insertar un proxy y llamar de nuevo:
Interface proxy = ( Interface )Proxy.newProxyInstance(
Interface.class.getClassLoader() ,
new Class [J { Interface. class },
new DynamieProxyHandler(real));
consumer(praxy)
1* Output: (95% match)
doSamething
somethingElse bonabo
**** proxy: class $ProxyO, methad: public abstraet void
Interfaee.doSomething (), args: null
doSomething
**** praxy: class $ProxyO, methad: public abstraet vaid
Interface. samethingElse (java .lang.String) , args:
[Ljava.lang.Object@42e816
banaba
380 Piensa en Java
somethingElse bonobo
, /// ,-
Para crear un proxy dinmico se invoca el mtodo estti co Proxy.ncwProxyInstance(), que requiere un cargador de clases
(generalment e, podemos pasarle un cargador de clases de un objeto que ya haya sido cargado), una li sta de interfaces (no
clases ni clases abstractas) que queramos que el proxy implemente y una impl ementaci n de la interfaz InvocationHandler
(gestor de invocaci ones). El prmy dinmico rediri gir todas las llamadas al gestor de invocaciones, de modo que al cons
tmclOr para el gcslOr de invocac iones usualmente se le entrega la referencia al obj eto "real" para que pueda redirigirle las
solicintdes una vez que haya terminado de llevar a cabo su tarea intermediari a.
Al mtodo invoke( ) se le pasa el obj eto prmy. en caso de que necesitemos di stinguir de dnde viene la solicitud, (aun-
que en muchos casos esto no nos preocupar). Sin embargo, tenga cuidado cuando invoque mtodos del pro.\y dentro de
invoke(), porque las ll amadas a travs de la interfaz se redirigen a travs del proxy.
~ general. lo que haremos ser reali zar la operacin intermediario y luego usar Method.invoke() para rediri gir la soli ci-
Jd hacia el objeto real pasndole los argument os necesarios. Puede que esto parezca a primera vista algo limitado, como si
lo se pudieran realizar operaciones genricas. Sin embargo, podemos filtrar ciertas llamadas a mtodos, dejando pasar las
tras directamente:
11: typeinfo/SelectingMethods . java
II Bsqueda de mtodos concretos en un proxy dinmico.
import java.lang.reflect. * ;
import static net.mindview.util.Print .* ;
class MethodSelector i mplements InvocationHandler
private Object proxied;
public MethodSelector{Object proxied) (
this.proxied = proxied;
public Object
invoke{Object proxy, Method method, Object(] args)
throws Throwable {
if (method . getName () . equals ("interesting") )
print ( nproxy detected the interesting method");
return method. invoke (proxied, args) i
interface SomeMethods
void boringl();
void boring2();
void interesting(String arg);
void boring3 () ;
class Implementation implements SomeMethods {
public void boringl () { print (
lI
boringl " ) i }
public void boring2() ( print("boring2") i }
public void interesting (String arg) {
print (" interesting " + arg);
public void boring3 () ( print (Uboring3") ;
class SelectingMethods (
public static void main(String(] args) {
SomeMethods proxy= (SomeMethods)Proxy.newProxylnstance (
SomeMethods . class . getClassLoader() ,
new Class[] { SomeMethods.class },
new MethodSelector(new Implementation() )) ;
proxy.boringl() i
proxy.boring2() ;
proxy. interesting ("bonoba") i
proxy.boring3 {) ;
/ * Output:
boringl
boring2
Proxy detected the interesting methad
interesting banaba
boring3
* /// ,-
14 Informacin de tipos 381
Aqu. si mplemente examinamos los nombres de los mtodos, pero tambin podramos examinar los aspeclOs de la signatu-
ra del mtodo, incluso podramos buscar valores concretos de los argumentos.
El proxy dinmico no es una herramienta para utilizarla todos los das. pero pennite resolver ciertos tipos de problemas muy
elegantemente. Puede obtener ms detalles acerca del patrn de diseo Pro.\y y de otros patrones de diseo en Thinking in
Palterns (,'ase H'H'wA1indView.net) y Design Palterns, de Erich Gamma el al. (Addison- Wesley, 1995).
Ejerci cio 21: (3) Modifique SimpleProxyDemo.java para que mida los tiempos de ll amada a los mtodos.
Ejerci cio 22: (3) Modifique Simpl eDynamicProxy.java para que mida los tiempos de ll amada a los mtodos.
Ejerci cio 23: (3) Dentro de invoke( ) en Simpl eDynamic Proxy.java, trate de imprimir el argumento proxy y explique
lo que sucede.
Proyecto:
2
Escriba un sistema utilizando proxies dinmicos para implementar transacciones, donde el proxy se encar-
gue de confirmar /0 transaccin si la llamada realizada al objeto real tiene xito (no genera ninguna excep-
cin), debiendo anular /a transaccin si la llamada falla. La continnacin y anulacin deben funcionar
como un archivo de texto ex temo, que se encuentra fuera del control de las excepciones Java. Tendr que
prestar atencin a la atomicidad de las operaciones.
Objetos nulos
Cuando se utili za el valor predetenninado null para indicar la ausencia de un objeto, es preciso comprobar si las referencias
son iguales a null cada vez que se utiliza. Esta labor puede llegar a ser muy tediosa y el cdigo resultante es muy comple-
jo. El problema es que nuH no tiene ningn comportamiento propio, salvo generar una excepcin NullPointerExcepti on si
se intenta hacer algo con el valor. Algunas veces. resulta ti l introducir la idea de un objeto nu/0
3
, que aceptar mensajes en
lugar del objelO al cual "represema", pero que devolver valores indicando que no existe ah ningn objeto "real". De esta
fomla, podcmos asumir que todos los objetos son v lidos y no tenemos porqu desperdiciar tiempo de programacin com-
probando la igualdad con nuJl (y leyendo el cdigo resultante).
Aunque resulta di vertido imaginarse un lenguaje de programacin que cree automticamente objetos nulos por nosotros, en
la prctica no tiene sentido usarlos en todas partes; en ocasiones, ser adecuado realizar las comprobaciones de valor l1ull ,
en otros casos podremos asumir razonablemente que no vamos a encontrarnos con el valor null , e incluso, en otras ocasio-
nes, ser perfectamente aceptable detectar las aberraciones a travs de NullPointerException. El lugar donde los objetos
nulos parecen ser ms tiles es en "el lugar ms prximo a los datos", con objetos que representen entidades cn el espacio
del problema. Como ejemplo simple, muchos sistemas dispondrn de una clase Per sono y hay situaciones en el cdigo en
las que no di sponemos de una persona real (o s di sponemos de ella. pero no tenemos todava toda la infomlacin acerca de
dicha persona), por lo que tradicionalmente utilizaramos una referencia null y comprobaramos si las referencias son nulas.
En lugar de ello, podemos crear un objeto nulo, pero an cuando el objeto nulo responder a lodos los mensajes
2 Los proyeclOs son sugerencias que pueden utilizarse. por cjcmplo. como uabajo!> dc clasc. Las soluciones a los proyectos no se incluyen en la gua de
soluciones .
.3 Descubieno por Bobby Woolfy Bmce Andcrson. Puede verse como un caso especial del patrn de diseo basado en eslr(l/egill. Una variante del ObjelO
Nlllo es el patrn de disello de t/erador Nlllo. que hace que la interaccin a lravs de los nodos en una jerarqua sea transparente para el clien-
te (el cliellle puede elllonces utilizar la misma lgica para iterar a travs de lajer.:rqua compuesta y a traves de los nodos hoja).
382 Piensa en Java
a los que el objeto "real " respondera, sigue siendo necesario disponer de una manera de comprobar si hay valores nul os.
La forma ms simple de hacer esto consiste en crear una interfaz de marcado:
//: net/mindview/util/Null . java
package net . mindview.util
public interface Null {) 111,-
Esto permite a insta nceof detectar el obj eto nulo, y lo ms importante, no requi ere que afiadamos un mrodo isNull () a
todas nuestras cl ases (lo cual sera, despus de todo. simplemente otra forma de utili zar mecanismos RTTI, por qu no uti-
li zar la funcionalidad int egrada en su lugar?).
// : typeinfo/Person.java
// Una clase con un objeto Null .
import net.mindview.util.*
class Person {
public final String first
public final String last;
public final String address
II etc.
public Person(String first, String last, String address){
this.first = first
this.last = last
this.address = address
public String toString () {
return "Person: " + first + " " + last + " " + address;
public sta tic class NullPerson
extends Person implements Null
private NullPerson() { super("None", "None", "None")
public String toString() { return "NullPerson"; }
public static final Person NULL = new NullPerson()
/! 1,-
En general. el objeto nulo ser un objeto simple (no una serie de objetos agrupados en contenedores), por lo que aqu se crea
como una instancia esttica final. Esto funciona porque Perso" es inmutable: slo podemos fijar los va lores en el construc-
tor, y luego leer dichos valores, pero no modi fi carlos (porque los propios String son inherentemente inmutables). Si desea
modificar un objeto NullPerson, slo puede susti tuirl e con un nuevo objeto Persono Observe que disponemos de la opcin
de detectar el genrico Null o el ms especfico NullPerson utilizando instanceof, pero como se trata de un va lor simple
tambin podemos utilizar equals( ) o incluso = para comparar con Pcrson.NULL.
Ahora suponga que estuviramos en la poca dorada de las empresas de Internet y que alguien hubiera invertido una gran
cantidad de dinero en una maravi ll osa idea que hubiramos tenido. imagine que estamos listos para recl utar personal pero
que. mientras esperamos a que las vacantes sean cubiertas. podemos util izar objetos nul os Perso" para asignarlos a cada
puesto de lrabajo (Postioll ):
// : typeinfo/Position.java
class Position {
private String title
private Person person
public Position (String jobTitle, Person employeel {
title = jobTitle
person = employee;
if(person == null)
person = Person.NULL
public Position{String jobTitle)
title = jobTitle;
person = Person . NULL;
public String getTitle() { return title;
public void setTitle (String newTitle) {
title = newTitle
publ ic Person getPerson () { return person;
public void setPerson(Person newPerson) {
persan = newPerson;
i f {pe rson == null)
person = Person.NULL;
public String toString(}
return "Position: " + title + " It + person
}
111 >
14 Informacin de tipos 383
Con Position, no tenemos necesidad de crear un objeto nulo, porque la existencia de Person.NULL implica un objeto
Position nulo (es posible que ms adelante descubramos que s se necesita aadir un objeto nulo explcito para Position.
pero hay una regla que dice que siempre debemos implementar la solucin ms simple que funcione en nuestro primer dise-
o, y esperar a que algn aspecto del programa requiera que aadamos la caracterstica adicional, en lugar de asumir desde
el principi o que esa caracterstica es necesaria).4
La clase Staff puede ahora buscar los objetos nulos a la hora de rellenar las vacantes:
1/ : typeinfo/Staff. java
import java . util .*
public class Staff extends ArrayList<Position> {
public void add{String title, Person person) {
add(new Position{title, person) )
public void add{String . titles)
for{String title : titles)
add{new Position(title))
public StaffIString . .. titlesl ( addltitles); }
public boolean positionAvailable (String title) {
for(Position position this)
if (pos ition.getTitle () .equals(title ) &&
position.getPerson() == Person.NULL)
return true;
return false
public void fillPosition{String title, Person hire) {
for(Position position this)
if(position.getTitle() .equals (title) &&
position.getPerson() == Person.NULL)
position. setPerson (hire) ;
return
throw new RuntimeException(
"Position " + title + " not available");
public static void main (String [1 args) {
Staff staff = new Staff("President", "CTOII,
"Marketing Manager", "Product Manager",
"Project Lead", "Software Engineer",
4 Esa tendencia a implementar la solucin ms simple posible es una de las recomendaciones de Extreme Programmillg (XP).
384 Piensa en Java
"Software Engineer", "Software Engineer",
"Software Engineer", "Test Engineer",
"Technical Writer");
staff.fillPosition("President",
new Person(IOMe", "Last", "The TOp, Lonely At"))
staff. fillPosition ("Project Lead",
new Person( nJanet", "Planner", "The Burbs"));
if(staff.positionAvailable("Software Engineer"l)
staff. fillPosition ("Software Engineer",
new Person ( "Bob", "Coder", "Bright Light City"));
System.out.println(staffl
1* Output:
[Position: President Person: Me Last The Top, Lonely At, Position: CTO NullPerson,
Position: Marketing Manager NullPerson, Position: Product Manager NullPerson, Position:
Project Lead Person: Janet Planner The Burbs, Position: Software Engineer Person: Bob
Coder Bright Light City , Position: Software Engineer NullPerson, Position: Software
Engineer NullPerson, Position: Software Engineer NullPerson, Position: Test Engineer
NullPerson, Position: Technical Writer NullPerson]
* /// >
Observe que sigue siendo necesario comprobar la exi stencia de objetos nulos en algunos lugares, lo cual no difiere Illucho
de comprobar la igualdad con el valor null , pero en otros lugares (como en las conversiones toString( ), en este caso).
no es necesario realizar comprobaciones adicionales; podemos limitarnos a asumir que todas las referencias a objetos son
vlidas.
Si estamos trabajando con interfaces en lugar de con clases concretas. es posibl e utilizar un objeto DynamicProxy para crear
automticamente los obj etos nulos. Suponga que tenemos una interfaz Robot que define un nombre. un modelo y una li sta
List<Operation> que describe lo que el Robot es capaz de hacer. Operation contiene una descripcin y un comando (es
un tipo del patrn de disetio basado en comandos):
11 : typeinfo/Operation.java
public interface Operation
String description()
void command () ;
/// , -
Podemos acceder a los servicios de un objeto Robot invocando operations( ):
// : typeinfo/Robot.java
import java . util .* ;
import net.mindview.util.*;
public interface Robot
String name () ;
String model () ;
List<Operation> operations()
class Test {
public static void test (Robot r) {
if(r instanceof Null)
System.out.println(" (Null Robot} ")
System. out.println("Robot name: 11 + r . name(});
System. out . println ("Robot model: " + r. model () } ;
for(Operation operation : r.operations(}) {
}
/l/o-
System.out . println{operation . description()) ;
operation.command() ;
Esto incorpora tambi n una clase ani dada para realizar las pruebas.
Ahora podemos crear un objeto Robot espec iali zado:
/1: typeinfojSnowRemovalRobot . java
import java.util. *;
public class SnowRemovalRobot implements Robot {
private String name;
public SnowRemovalRohot (String name) { this . name "" name;}
publ ic St ring name () { return name ; }
publ ic String model () { return "SnowBot Se ries 11 " i }
public Lisc<Operation> operations () {
return Arrays . asList{
new Operatian() {
public String description()
return name + " can shove l snow";
public void cornmand{)
System. out .prin tln(name + " s hoveling snow" );
},
ne w Operatian ()
public String description ()
return name + " can chip ice";
public void command()
}
},
System.out.println(name + I! chippi ng ice " );
new Operation()
public String desc ri ption(}
return name + " can clear the roof";
public void command()
System. out.println(name + " cl e aring roof " ) i
1;
public static va id main (String [J args) {
Robot . Test.test(new SnowRemovalRobot("Slusher")} i
/ * Output:
Robot name: Slusher
Robot model : SnowBot Series 11
Slusher can shovel snow
Slusher shoveling snow
Slushe r can chip ice
Slusher chipping ice
Slusher can clear the roof
Slushe r clearing roof
*/// , -
14 Informacin de tipos 385
Existirn presumiblemente muchos tipos diferentes de objetos Robot , y lo que nos gustara es que cada objeto nulo hiciera
algo especial para cada tipo de Robot; en este caso, incorporar infonnacin acerca del tipo exacto de Robot que el objeto
nul o representa. Esta informacin ser capturada por el pro.\y dinmico:
11 : t ypeinfo/NullRobot.java
/1 Util i zaci n de un pr oxy dinmico para c rear un objeto nulo .
386 Piensa en Java
import java.lang.reflect.*;
import java.util.*;
import net.mindview.util.*
class NullRobotProxyHandler implements InvocationHandler
private String nullName;
private Robot proxied = new NRobot();
NullRobotProxyHandler(Class<? extends Robot> type )
nullName = type .getSimpleName () + Ir NullRobot";
private class NRobot implements Null, Robot {
public String name() { return nullName }
public String model () { return nullName }
public List<Operation> operations () {
return Collections.emptyList{);
public Object
invoke{Object proxy, Method method, Object[] args)
throws Throwable {
return method. invoke {proxied, args)
public class NullRobot
public sta tic Robot
newNullRobot{Class<? extends Robot> type)
return (Robot)Proxy.newProxylnstance(
NullRobot.class .getClassLoader() ,
new Class[] { Null.class, Robot.class },
new NullRobotProxyHandler(type i
public static void main(String(] args)
Robot [) bots = {
} ;
new SnowRemovalRobot ( "SnowBee"),
newNullRobot{SnowRemovalRobot.classl
for(Robot bot : bots)
Robot.Test.test(bot) ;
1* Output :
Robot name: SnowBee
Robot model: SnowBot Series 11
SnowBee can shovel snow
SnowBee shoveling snow
SnowBee can chip ice
SnowBee chipping ice
SnowBee can clear the roof
SnowBee clearing roof
[Null Robot)
Robot name: SnowRemovalRobot NullRobot
Robot model: SnowRemovalRobot NullRobot
* /// >
Cuando se necesita un objeto Robot nulo, simplemente se invoca newNullRobot( ), pasndole al mtodo el tipo de Robot
para el que queremos que acte como proxy. El prox)' satisface los requisitos de las interfaces Robot y Null. y proporciona
el nombre especfico para el que acta como pro.\)'.
14 Informacin de tipos 387
Objetos maqueta y stubs
Hay dos tipos \ ariantes del objeto nulo: el objeto maq/leta y el SI/lb. Al igual que el objeto nulo. ambos tipos de objetos se
utilizan en lugar del objeto "real" que emplear el programa terminado. Sin embargo. tanto el objeto maqueta como el sllIb
pretenden ser objetos vivos que entregan infonl18Cin real en lugar de ser un sustituto un poco ms inteligente de null , como
es el caso del objeto nulo.
La diferencia entre el objeto maqueta y un s/IIb es bastante sutil. Los objetos maqucla tienden a ser ligeros (poco comple-
jos) y tienen capacidad de auto-comprobacin. y usualmente se crean muchos de ellos para gestionar di stintas situaciones
de prueba. Los swbs son tpicamente ms pesados y a menudo se reutilizan entre una pmeba y otra. Los stubs pueden con-
figurarse para cambiar de comportamiento. dependiendo de cmo se los invoque. Por tanto. un slIIb es un objeto sofisti ca-
do que lleva a cabo una de esas tareas; mientras que para hacer esas mismas tareas con objetos maqueta lo que nomlalmente
haramos es crear muchos objetos maqueta pequClios y simples.
Ejercicio 24: (4) Aliada objetos nulos a RegistercdFactories.java.
Interfaces e informacin de tipos
Un objetivo clave de la palabra clave interface es pemlitir al programador aislar componentes. reduciendo as el acopIa-
miento. Si escribimos el cdigo basndonos en interfaces. conseguimos este objetivo. pero con la informacin de tipos es
posible saltarse los controles: las interfaces no Son una garanta de desacoplamiento. He aqu un ejemplo comenzado con
una interfaz:
11 : typeinfo/ interfacea/ A.java
package typeinfo.interfacea
public interface A {
void f 1) ;
} 111 -
Esta mterfaz se implementa a continuacin y podemos '"er fcilmente cmo saltamos los controles para obtener el tipo real
de implementacin:
11 : typeinfo/ lnterfaceVio lation.java
11 Sorteando una interfaz.
import typeinfo.interfacea.*;
class B implements A
public void f 1) {}
public void 9 1) {}
public class InterfaceViolation {
public static void main (String [) args) {
A a = new B ()
a . f 11 ;
11 a.g() 11 Error de compilacin
System.out . println(a.getClass() .getName());
ifla instanceof E) {
B b = lB) a;
b . gl) ;
1* Output:
Uti li zando RTfl , descubrimos que a ha sido implementado como R. Proyectando sobre R, podemos invocar un mtodo que
no se encuentre en A.
388 Piensa en Java
Esto es perfectamellle legal y aceptable. pero puede que no queramos que los programadores de clientes hagan esto, ya que
esto les da la opornmidad para acoplarse ms estrechamente con nuestro cdigo de lo que querramos. En otras palabras,
podramos pensar que la palabra clave interface nos est protegiendo. pero en realidad no es as, y el hecho de que utili cc-
mas B para implementar A en este caso es algo de dominio pblico.
5
Una solucin consiste simplemente en decir que los programadores sern los responsables si deciden utilizar la clase real
en lugar de la interfaz. Esto es probablemente razonable en muchos casos, pero si ese "probablemente" no es suficiente, Con-
viene apli car otros controles ms estrictos.
La tcnica ms sencilla consiste en uti lizar acceso de paquete para la implemclllacin, de modo que los clientes situados
fuera del paquete no puedan verla:
jj : typeinfo/packageaccess/HiddenC.java
package typeinfo.packageaccess;
import typeinfo.interfacea .*
import static net.mindview.util.Print.*;
class C implements A {
public void f () { print ( "public C. f () " ) ;
public void g{) { print("public C.g()");
void u() { print("package C.u()"); )
protected void v () { print ( "protected c. v () " ) ;
priva te void w() { print("private C.W{) H) }
public class HiddenC {
public static A makeA () { return new C () }
) /// , -
La nica parte pblica de este paquete, HiddenC, produce una interfaz A cuando se la invoca. Lo que es interesante acerca
de este ejemplo es que incluso si devolviramos un objeto C desde makeA( ), seguiramos sin poder utilizar ninguna otra
cosa distinta de A desde fuera del paquete, ya que no podemos nombrar e fuera del paquete.
Ahora, si tratamos de efectuar una especializacin sobre e, no podemos hacerlo, porque no hay ningn tipo 'C' disponible
fuera del paquete:
11 : typeinfo/Hiddenlmplementation.java
II Sorteando el acceso de paquete.
import typeinfo . interfacea.*
import typeinfo.packageaccess.*
import java.lang.reflect.*;
public class Hiddenlmplementation
public static void main{String(] args) throws Exception (
A a = HiddenC.makeA{);
a . E () ;
System. out. println (a. getClass () . getName () )
II Error de compilacin: no se puede encontrar el smbolo ICI:
1* if(a instanceof C) {
e e = (C) a;
c. 9 () ;
* /
II Caramba! La reflexin nos permite invocar g():
callHiddenMethod (a, "glI );
II E incluso mtodos que son menos accesibles!
5 El caso mas famoso cs el sistema operativo Windo\Vs. que tena una API publica con la que se supona que haba que desarroll ar programas y un conjun-
to no publicado pero visible de funciones que podiamos descubrir e invocar. Para resolver los problemas. los programadores utilizaban las funciones ocut-
tas de la API , lo que forz a Microsoft a mumcnerlas como si flleran pane de la API pblica. Esto se convini6 en una fuente de grandes costes y de enonne
trabajo para la empresa.
callHiddenMethod(a, "u");
callHiddenMethod (a, "v");
callHiddenMethod (a, "w");
static void callHiddenMethod(Object a, String methodName)
throws Exception {
Method 9 = a. getClass () . getDec!aredMethod (methodName) ;
g.setAccessibl e{true ) ;
g. invoke (a) ;
/* Output:
public c. f ()
typeinfo.packageaccess.C
public c . 9 ()
package c. u ()
protected C. v ()
prvate C. w ()
*///,-
14 Informacin de tipos 389
Como puede ver, sigue siendo posible meterse en las entraas e invocar lodos los mtodos utili zando el mecani smo de refle-
xi n. Incluso los Intodos pri vados! Si se conoce el nombre del mtodo, se puede invocar setAccessible(true) sobre el obje-
to Method para hacerl o nvocabl e, como podemos ver en caIlHiddenMethod().
Podramos pensar que es posible impedi r esto distribuyendo slo el cdigo compil ado, pero no es una solucin. Basta con
ej ecut ar j avap, que es el descompil ador inclui do en el JDK. He aqu la lnea de comandos necesari a:
j avap -pri vate e
El indi cador -pri vate especifica que deben mostrarse todos los miembros, incluso los privados. He aqu la salida que se
obti ene:
class typeinfo.packageaccess.C extends
java.lang.Object impl ements typeinfo.interfacea.A
typeinfo . packageaccess . C{) ;
public void f () ;
public void g();
void u () ;
protected void v() i
private void w() i
Por tanto, cua lquiera puede obt ener los nombres y signaturas de los mtodos ms pri vados e invocarl os.
Qu sucede si impl ementamos la interfaz con una clase int erna privada? He aqu un ej empl o:
11: typeinfo/lnnerlmplementation . java
II Las clases interna privadas no pueden ocultarse del mecanismo de
II reflexin.
import typeinfo.interfacea.*;
import static net . mindview.util.Print.*
class InnerA {
private static class e implements A {
public void f (1 { print ( "public c. f () "1 ;
public void 9 (1 { print ("public c. 9 () ") ;
void u() ( print("package e.u()"I ; )
protected void v () { print ("protected c . v () ") ;
private void w() { print("private C.w() "); }
public static A makeA() { return new C(); }
390 Piensa en Java
public class Innerlmplementation
public static void main(String[] args) chrows Exception {
A a = InnerA.makeA()
a. f 1) ;
Syscem.out . println(a . getClass(} . getName())
II La reflexin sigue permitiendo entrar en la clase privada:
Hiddenlmplementation . callHiddenMethod (a, "g")
Hiddenlmplementation . callHiddenMethod (a I "u " )
Hiddenlmplementation . callHiddenMethod (a I "v")
Hi ddenlmplementat ion . callHi ddenMethod (a I "w")
1* Oucput :
public e. f l)
I nne rA$C
public e . g 11
package C. u()
protected C. v ()
private C.w ()
' jjj >
Esta solucin no nos ha penni tido ocultar nada a oj os del mecanismo de reflexin. Qu sucedera con una clase annima?
11 : typeinfo/ Anonyrnouslmplementation.java
II Las c lases internas annimas no pueden ocultarse del mecanismo de
II reflexi n.
import typeinfo . interfacea . *
import stat i c net . mindvi ew.util.Print.*
class AnonyrnousA {
public static A makeA ()
return new A () {
} ;
publi c void f 11 ( print I "public e. f 11 ,, ) ;
public void g il ( print I "publi c e . g il " ) ;
vo id u 11 ( print I "package e. u 1) " ) ; }
protected void v () { print ( "prot ected C. v () " )
private void w() { print ( "private C.w () " ) i }
public class Anonyrnouslmplementati on {
publi c stat ic void main (String[) args ) throws Exception {
A a = AnonymousA.makeA () i
a. f 11 ;
System. out . println (a.getClass () .getName ())
II La reflexin sigue pudi endo entrar en la clase annima :
Hiddenlmplementation.caIIHiddenMethod (a, "g" ) ;
Hiddenlmplementation. callHi ddenMethod (a , "u" ) ;
Hi ddenlmplementation. callHiddenMethod (a, "VII ) ;
Hiddenl mplementa tion. cal l HiddenMethod ( a, IIW
tl
) ;
1* Output:
public e . f l)
AnonymousA$l
public e . g l)
package C. u ()
protected C. v ()
private C. w()
' //j , -
14 Informacin de tipos 391
Parece que no existe ninguna faffila de impedir que el mecani smo de reflexin entre e invoque los mtodos que no tienen
acceso pblico. Esto tambin se cumple para los campos. inc luso para los campos pri vados:
JI : typeinfo/ ModifyingPrivateFields.java
import java.lang.reflect.*i
class WithPrivateFinalField
private int i = 1;
private final String s = "1 'm totally safe" i
private String 52 = "Am 1 safe?" i
public String toString () {
return 11 i = 11 + i + ", 11 + S + 11 + 52;
public class ModifyingPrivateFields {
public static void main(String[] argsl throws Exception {
WithPr ivateFinalField pf = new WithPrivateFinalField();
System. out . println(pf) i
Field f = pf. getClass () . getDeclared Field (" i " ) ;
f . setAccessible(true) ;
System. out.println("f . get 1nt( pf l: " + f . get1nt( pf l);
f . set lnt(pf,47};
System. out.println(pf) ;
f = pf. getCl ass() .get DeclaredField("s");
f . setAccessible(true) ;
System.out . println("f . get(pf } , " + f . get(pf}};
f . set (pf, "No , you' r e not ! " ) ;
Syst em.out.println(pf ) ;
f = pf. getClass () . getDeclaredField ( 11 s2 11 1 ;
f . s etAccessi bl e (true) ;
System. out.pr int ln ( lIf. ge t( pf ) : 11 + f. ge t( pf ));
f . set (pf, "No, you' re not! " ) ;
System.out . println(pf ) i
/ * Output :
i = 1, 1 'm totally safe, Am I saf e?
f .getInt (pf) , 1
i = 47, I ' m totally saf e, Am I safe?
f .get (pf) : 1 'm totally saf e
i = 47, I ' m totally safe, Am I safe?
f.get(pf) : Am 1 safe?
i = 47, I' m totally safe, No , you 're not !
*/// , -
Sin embargo, los campos de tipo final s que estn protegidos frente a los cambios. El si stema de tiempo de ejecucin acep-
ta los intentos de cambio sin quejarse, pero no se produce cambio alguno.
En general , todas estas violaciones de acceso no constituyen un problema grave. Si alguien utiliza una de estas tcnicas para
invocar mtodos que se han marcado como privados o con acceso de paquete (lo cual indica claramente que no deberan
invocarse), entonces es dificil que esas personas puedan quejarse si decidimos cambiar posterionnente algunos aspectos de
esos mtodos. Por otro lado, el hecho de que siempre exista una puerta trasera para entrar en una clase nos pennite resolver
ciertos tipos de problemas que en otro caso seran dificiles o imposibles, y los beneficios del mecanismo de reflexin son,
por regla general , incuestionables.
Ejercici o 25: (2) Defina una clase que contenga mtodos privados, protegidos y con acceso de paquete. Escriba cdigo
para acceder a dichos mtodos desde fuera del paquete de la clase.
392 Piensa en Java
Resumen
RTTI nos pennite descubrir la infonnaci n de tipos a partir de una referencia annima a una clase base. Es por ello que se
presta a una inadecuada utilizacin por los usuarios menos expertos, ya que resulta ms fcil de comprender que las llama-
das polimrficas a mtodos. Para las personas que tienen experiencia previa en lenguajes procedimentales, resulta difcil
organizar los programas en conjuntos de instrucciones switch. Este tipo de estmctura puede implementarse fcilmente con
RTTI perdindose as el importante valor que el polimorfi smo aade al desarrollo y el mantenimiento del cdigo. La inten-
cin de la programacin orientada a objetos es utili zar llamadas polimrficas a mtodos siempre que se pueda y RTTI slo
cuando no haya ms remedio.
Sin embargo, las llamadas polimrficas a mtodos, tal como est prevista en el lenguaje, requiere que tengamos control de
la definicin de la clase base, porque en algn punto dentro del proceso de extensin del programa podemos llegar a descu-
brir que la clase base no incluye el mtodo que necesitamos. Si la clase base proviene de una biblioteca desalTollada por
algn otro programador, una solucin es RTTI: podemos heredar un nuevo tipo y ailadir el mtodo adicional que necesita-
mos. En el resto del cdigo podemos entonces detectar ese tipo concreto que hemos aadido y llamar a ese mtodo espe-
cial. Esto no destmye el polimorfismo y la extensibilidad del programa, porque el aiiadir un nuevo tipo no requiere que
andemos a la caza de instmcciones switch en nuestro programa. Sin embargo, cuando aadamos cdigo que dependa de la
nueva funcionalidad aiiadida, nos veremos obligados a util izar RTTI para detectar el tipo concreto que hayamos definido.
Aadir una funcionalidad en una clase base puede implicar que, a cambio de obtener exclusivamente un beneficio en esa
clase concreta, todas las dems clases derivadas de la misma debern ca.rgar con un esqueleto de mtodo completamente
carente de significado. Esto hace que la interfaz sea menos clara y resulta bastante molesto para aquell os que se ven obli-
gados a sustituir mtodos abstractos cuando derivan otra clase a partir de esa clase base. Por ejemplo, considere una jerar-
qua de clases que representa instrumentos musicales. Suponga que desea limpiar las vlvulas de las boquillas de todos los
instrumentos apropiados de su orquesta. Una opcin es incluir un mtodo IimpiarValvula() en la clase base Instrumento,
pero esto resulta confuso, porque implicara que los instrumentos de Percusin, Cuerda y Electrnicos tambin tienen
boquillas y v lvulas. RTTI proporciona una solucin mucho ms razonable, porque nos pennile colocar el mtodo en la
clase especfica donde resulta apropiado (Viento, en este caso). AlmLsmo tiempo, podemos descubrir que existe una solu-
cin ms lgica, que en este caso consistira en incluir un mtodo prepararlnstrumento(). Sin embargo, puede que no vea-
mos esa solucin cuando estemos tratando por primera vez de resolver el problema y, como consecuencia, podramos asumir
errneamente que es necesario utilizar RTTI.
Finalmente, RTTI pennite en ocasiones resolver problemas de eficiencia. Suponga que nuestro cdigo utili za apropiadamen-
te el polimorfismo, pero resulta que uno de los objetos reacciona a este cdigo de propsito general de una manera terrible-
mente poco eficiente. Podemos detectar ese tipo concreto de objeto utilizando RTTI y escribir cdigo especfico para
mejorar la eficiencia. No caiga en la tentacin, sin embargo, de estructurar sus programas demasiado pronto pensando en la
eficiencia. Se trata de una trampa bastante tentadora. Lo mejor es conseguir primero que el programa funcione y luego deci-
dir si est funcionando lo suficientemente rpido. Slo entonces deberemos abordar los problemas de eficiencia con una
herramienta de perfilado (consulte el suplemento en htrp://MindView.net/Books/Be((erJava).
Tambin hemos visto que el mecani smo de reflexin abre un nuevo mundo de posibilidades de programacin, pemlitiendo
un esti lo de programaci n mucho ms dinmico. Existen programadores para los que la naturaleza dinmica del mecanis-
mo de reflexin resulta bastante perturbadora. El hecho de que podamos hacer cosas que slo pueden comprobarse en tiem-
po de ejecucin y de las que slo se puede informar mediante el mecanismo de excepciones, parece, para las mentes
cmodamente acostumbradas a la seguridad de las comprobaciones estticas de tipos, algo bastante pernicioso. Algunas per-
sonas sostienen incluso, que el introducir la posibilidad de una excepcin en tiempo de ejecucin es una indicacin clara de
que dicho tipo de cdigo debe evitarse. En mi opinin, esta sensacin de seguridad no es ms que una ilusin, siempre hay
cosas que pueden suceder en tiempo de ejecucin y que pueden generar excepciones, incluso en un programa que no con-
tenga ningn bloque try ni ninguna especificacin de excepcin. En lugar de ello, en mi opinin, la existencia de un mode-
lo coherente de infonnacin de errores nos permite escribi r cdigo dinmico utilizando los mecanismos de reflexin. Por
supuesto, merece la pena tratar de escribir cdigo que pueda comprobarse estticamente ... siempre que se pueda. Pero creo
que el cdigo dinmico es una de las caractersticas ms importantes que diferencia a Java de otros lenguajes como C++.
Ejercicio 26: (3) Implemente un mtodo IimpiarV.lvula() como el descrito en este resumen.
Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrni co Tll e Thillki"g il1 1am AI1/1o/ated SO/liriO" Gllide, disponible para
la venta en l\'\\'W.Alind J'ie\\:l1et.
Genricos
Los mtodos y clases ordinarios funcionan con tipos especficos: con tipos primitivos o con
clases. Si lo que queremos es escribir cdigo que pueda utilizarse con un tipo ms amplio
de tipos, esta rigidez puede resultar demasiado restrictiva. I
Una de las fonnas en que los lenguajes orientados a objetos penniten la generalizacin es a travs del polimorfismo. Por
ejemplo, podemos escribir un mtodo que tome un objeto de una clase base como argumento, y luego utilice dicho mtodo
con cualquier clase derivada de dicha clase base. Con ello, el mtodo ser algo ms general y podr ser utilizado en ms
lugares. Lo mismo cabe decir dentro de las clases: en cualquier lugar donde utilicemos un tipo especfico, un tipo base pro-
porcionar mayor flexibilidad. Por supuesto, podemos extender todas las clases salvo aquellas que hayan sido definidas
como finales
2
, por lo que esta flexibilidad se obtiene de manera automtica la mayor parte de las veces.
En ocasiones, limitarse a una nica jerarqua puede resultar demasiado restrictivo. Si el argumento de un mtodo es una
interfaz en lugar de una clase, las limitaciones se relajan de modo que ahora se incluirn todas aquellas clases que imple-
menten la interfaz, incluyendo clases que todava no hayan sido desarrolladas. Esto proporciona al programador de clientes
la opcin de implementar una interfaz para adaptarse a nuestra clase o mtodo. Con esto, las interfaces nos penlliten esta-
blecer un vnculo entre jerarquas de clases, siempre y cuando tengamos la opc in de crear una nueva clase para implemen-
tar ese vnculo.
Algunas veces, incluso una interfaz resulta demasiado restrictiva. Las interfaces siguen requiriendo que nuestro cdigo fun-
cione con esa interfaz concreta. Podramos escribir cdigo todava ms general si el lenguaje nos permitiera decir que ese
cdigo funciona con "algn tipo no especificado", en lugar de con una interfaz o clase especficas.
En esto se basa el concepto de genricos, un cambio de los ms significativos en Java SES. Los genri cos implementan el
concepto de tipos parametrizados, que penlliten crear componentes (especialmente contenedores) que resultan fciles de
util.izar con mltiples tipos. El trmino "genrico" significa "peI1eneciente o apropiado para grandes grupos de clases". La
intencin original de los genricos en los lenguajes de programacin era dotar al programador de la mayor capacidad expre-
siva posible a la hora de escribir clases o mtodos, relajando las rest ricciones que afectan a los tipos con los que esas clases
o mtodos pueden funcionar. Como veremos en este captulo, la implementacin de los genricos en Java no tiene un alcan-
ce tan grande; de hecho, podramos cuestionamos si el trmino "genrico" resulta si quiera apropiado para esta funcionali-
dad de Java.
Si no ha visto antes ningn mecani smo de tipos parametri zados, los genricos de Java le parecern, probablemente, una
mejora sustancial del lenguaje. Cuando se crea una instancia de un tipo parametri zado, el lenguaje se encarga de realizar las
proyecciones de los tipos por nosotros y la correccin de los tipos se garantiza en tiempo de compilacin. Evidentemente,
parece que este mecanismo es toda una mejora.
Sin embargo, si el lector ya tiene experiencia con algn mecanismo de tipos parametrizados, como por ejemplo en C++,
encontrar que no se pueden hacer con los genricos de Java todas las cosas que cabra esperar. Mientras que utilizar un tipo
genrico desarrollado por alguna otra persona resulta bastante sencillo, a la hora de crear nuestros propios genricos nos
I Quiero dar las gracias a Angelika Langer por su liSIa de preguntas frecuentes Jm'G Gellercs FAQ (vase II'wl\: !allge,:camefOl.de), as como por sus otros
escritos (hechos en colaboracin con Klaus Kreft). Esos trabajos han resultado enonnementc valiosos de cara a la preparacin de este captulo.
2 O clases que dispongan de un consfructor privado.
394 Piensa en Java
encontraremos con diversas sorpresas. Uno de los aspeclOs que trataremos de explicar en este capnllo son los motivos por
los que la funcionali dad se ha implementado en Java en la manera en que se ha hecho.
No queremos decir que los genricos de Java sean intiles. En muchos casos, consiguen que el cdigo sea ms directo e
incluso ms elegante. Pero, si el leclOr ha utilizado anteriomlente algn lenguaje donde est implemenrada una versin ms
pura de los genricos, puede que la solucin de Java le desilusione. En este captulo. vamos a examinar tanto las fortalezas
como las debilidades de los genricos de Java, con el fin de que el lector pueda utilizar esta nueva funcionalidad de mane-
ra ms efectiva.
Comparacin con C++
Los diseadores de Java han dejado claro que buena parte de la inspiracin del lenguaje proviene de C++. A pesar de ello,
resulta perfectamente posible ensear a programar en Java sin hacer apenas referencia a C++. y en este libro hemos inten-
tado hacerlo as, salvo en aquellos casos en los que la comparacin puede faci litar entender mejor el lenguaje.
Los genricos requieren que realicemos una comparacin ms detallada con C++ por dos razones. En primer lugar, com-
prender ciertos aspectos de las plantillas C++ (la principal inspiracin de los genricos, incluyendo su sintaxi s bsica) nos
pennitir entender los fundamentos del concepto, as como (y esto es particularnlente importante) las limitaciones que afec-
tan a lo que se puede hacer con los genricos de Java, y los mot ivos subyacentes de la existencia de esas limitaciones. El
objet ivo ltimo es que el lector comprenda claramente dnde estn los lmites. porque entendiendo esos lmites se puede
llegar a ser un programador ms eficiel1le. Sabiendo lo que no puede hacerse. podemos emplear mejor aquell as cosas que s
podemos hacer (en parte porque no nos vernos obli gados a perder tiempo rompindonos la cabeza contra una pared).
La segunda razn es que existen muchas concepciones errneas en la comunidad Java acerca de las plantillas C++, y estos
conceptos errneos pueden aumentar nuestra confusin acerca del objet ivo de los genricos.
Por tanto, vamos a introducir unos cuantos ejemplos de plantillas C++ en este captulo, aunque tratando siempre de limitar
al mximo las expl icaciones acerca del lenguaje C++.
Genricos simples
Una de las razones iniciales ms fuertes para introducir los genricos era crear clases de contenedores, de las que ya hemos
hablado en el Captulo 11 , Almacenamiellfo de objetos (hablaremos ms acerca de estas clases en el Captulo 17. Anlisis
detallado de los contenedores). Un contenedor es un lugar en el que almacenar objetos mientras trabajamos con ellos.
Aunque esto tambin es cierto para las matrices, los contenedores tienden a ser ms fl exibles y sus caractersticas son di s-
tintas a las de las matrices si mples. Casi todos los programas requieren que almacenemos un grupo de objetos mientras los
utilizamos, por lo que los contenedores son una de las bibliotecas de clases ms inherentemente reutilizables.
Examinemos una clase que almacena un nico objeto. Por supuesto. la clase podra especificar el tipo exacto del objeto de
la fonna siguiente:
11 : generics/Holderl.java
class Automobile {}
public class Holderl
private Automobile a
public Holderl(Automobile a) {this . a a;}
Automobile get {) { return a }
///>
Pero esta herramienta no es muy reutili zable, ya que no puede emplearse para almacenar ninguna otra cosa. Preferiramos
no tener que escribir una nueva clase de este estilo para cada tipo con el que nos encontremos.
Antes de Java SES, lo que haramos simplemente es hacer que la clase almacenara un objeto de tipo Object:
11 : generics/Holder2.java
public c!ass Holder2 {
private Object a
public Holder2(Object al { this . a = a; }
public void set(Object al { this.a = a }
public Object get () { return a }
public static void main(String[] args) {
Holder2 h2 = new Holder2{new Automobile());
Automobile a = (Automobile)h2.get();
h2.set("Not an Automobile");
String s = (String) h2 .get ();
h2 . set{1); // Se transforma automticamente en Integer
Integer x = (Integer )h2 .get () ;
15 Genricos 395
Ahora, la clase Holder2 puede almacenar cualquier cosa y, en este ejemplo, un nico objeto Holder2 almacena tres tipos
di stintos de objetos.
Hay algunos casos en los que queremos que un contenedor almacene mltiples tipos de objetos, pero lo ms normal es que
slo coloquemos un tipo de objeto en cada contenedor. Una de las principales motivaciones de los genricos consiste en
especificar el tipo de objeto que un contenedor almacena, y hacer que dicha especificacin quede respaldada por el compi-
lador.
Por tanto, en lugar de emplear Object, lo que querramos es poder utilizar un tipo no especificado, lo que podremos deci-
dir en algn momento posterior. Para hacer esto, incluimos un parmetro de tipo entre corchetes angulares despus del nom-
bre de la clase y luego, al utili zar la clase, sustituimos ese parmetro por un tipo real. Para nuestra clase contenedora anterior,
la tcni ca consistira en lo siguiente, donde T es el parmetro de tipo:
11: generics/Holder3.java
public class Holder3<T>
private T a
public Holder3 (T al { this.a = a; }
public void set(T al { this.a = a }
public T get(1 { return a; }
public static void main(String[] argsl
Holder3<Automobile> h3 =
new Holder3<Automobile>{new Automobile())
Automobile a = h3.get() II No hace falta proyeccin
II h3.set("Not an Automobile") II Error
// h3.set(11; // Error
Ahora, cuando creemos un objeto Holder3, deberemos especificar el tipo que queramos almacenar en el mi smo, utilizando
la sintaxis de corchetes angulares, como puede verse en main(). Slo podemos introducir en el contenedor objetos de dicho
tipo (o de alguno de sus subtipos, ya que el principio de sust inlcin sigue funcionando con los genricos). Y al extraer del
contenedor un valor, dicho valor tendr automticamente el tipo correcto.
sta es la idea fundamental de los genricos de Java: le decimos al compi lador qu tipo queremos usar y el compilador se
encarga de los detalles.
En genera l, podemos tratar los genricos como si fueran otro tipo ms que en lo nico que se diferencia de los tipos nonna-
les es en que tiene parmetros de tipo. Pero, como veremos en breve, podemos ut ilizar los genricos simplemeOle nombrn-
dolos junto con su lista de argumentos de tipo.
Ej ercicio 1:
Ejercicio 2:
(1) Utilice HolderJ con la biblioteca typeinfo. pets para demostrar que un objeto Holder3 que se haya
especificado para almacenar un tipo base, tambin puede almacenar un tipo derivado.
(1) Cree una clase contenedora que almacene tres objetos del mismo tipo, junto con los mtodos para
almacenar y extraer dichos objetos y un constructor para inicializar los tres.
396 Piensa en Java
Una biblioteca de tuplas
Una de las cosas que a menudo hace falta hacer es devolver mltiples objetos de una llamada a mtodo. La instmccin
return slo pennite especificar un nico objeto, por lo que la respuesta consiste en crear otro objeto que almacene los ml-
tiples objetos que queramos devolver. Por supuesto, podemos escribir una clase especial cada vez que nos encontremos COn
esta situacin, pero con los genricos es posible resolver el probl ema de tilla vez y ahorrarnos un montn de esfuerzo en el
fulUro. Almisl110 tiempo estaremos garantizando la seguri dad de los tipos en tiempo de compil acin.
Este concepto se denomina tupla, y consiste simplemente en un grupo de objetos que se envuelven juntos dentro de otro
objeto ni co. El receptor del objeto estar autorizado a leer los elementos, pero no a introducir otros nuevos (este concepto
tambin se conoce como Objeto de tramlerel1cia de datos o Mensajero).
Las tuplas pueden tener, nom1almente, cualqui er longi tud, y cada objeto de la nlpla puede tener un tipo distinto. Sin embar-
go, lo que 110S interesa es especificar el tipo de cada objeto y garantizar que, cuando el receptor lea los va lores, obtenga el
tipo correcto. Para tratar con el problema de las mltiples longinldes, podemos crear mltiples tuplas diferentes. He aqu
una que almacena dos obj etos:
// : net/mindview/ util / TwoTuple.java
package net.mindview.uti l ;
public class TwoTuple<A,B>
public final A first
public final B second
public TwoTuple (A a, B b ) { first = a second b;}
public String toString () {
return " ( " + first + + second + " ) It;
)
/// ,-
El constmctor captura el objeto que hay que almacenar y toString() es una funcin de utilidad que pennite mostrar los valo-
res de ull a li sta. Observe que una tupla conserva implcitamente sus elementos en orden.
Al examinar el ejemplo por primera vez, podria pensarse que viola los principios comunes de seguridad en la programacin
Java. No deberan ser first y second pri vados, y no debera accederse a ellos ni camente con los mtodos denominados
getFirst( ) y getSecond( )? Considere la seguridad que se obtendra en dicho caso: los cl ientes podran seguir leyendo los
objetos y hacer lo que quisieran con los mi smos, pero no podran asignar first o second a ninguna otra cosa. La declaracin
final nos proporciona esa mi sma seguridad, pero la fonna empleada en el ejemplo es ms corta y ms simpl e.
Otra observacin de di seiio imponante es que puede que queramos permitir a un programador de clientes que haga apuntar
a first o second a algn otro objeto. Sin embargo, es ms seguro dejar el ejemplo tal cual est, y limitarse a obli gar al usua-
ri o a crear un nuevo objeto TwoTuple si desea di sponer de uno que tenga diferentes elementos.
Las tuplas de mayor longitud puede crearSe mediante herencia. Como podemos ver, aadir ms parmetTos de tipo resulla
bastante si mple:
// : net / mindview/ util/ThreeTuple.java
package net.mindview.util
public class ThreeTuple<A,B,C> extends TwoTuple<A,B> {
public final C third
publie ThreeTuple lA a, B b, e e l {
super (a, b ) ;
third = c
public String toString () {
return It ( " + first +
}
/// , -
" + second +
/ / : net / mindview/ util / FourTuple.java
package net.mindview.util
" + third +" ) It
public class FourTuple<A,B,C,D> extends ThreeTuple<A,B,C>
public final D fourth
public FourTuple(A a, B b , e e, D d)
super (a, b, el j
fourth = d;
public String toString () {
return "(" + first + ", + second +
third + 11 I 11 + fourth + ") 11 i
}
;;;,-
jI: net/mindview/util/FiveTuple.java
package net.mindview.util;
public class FiveTuple<A,B,C,D,E>
extends FourTuple<A,B, C,D> {
public f inal E fifth;
" +
public FiveTuple(A a, B b , e e, D d, E el {
super(a, b , e, d);
fi f th = e
public String toSt r ing () {
return ,,(" + first + ", + second + ", 11 +
third + ", " + fourth + 11 I " + fifth + " ) " j
}
;;;,-
15 Ge nricos 397
Para utili zar una tupla, simplemente definimos la tupla de la longitud adecuada como valor de retomo para nuestra funcin
y luego la creamos y la devolvemos en la instruccin return:
1/: generics/TupleTest . java
import net . mindview.util .*
elass Amphibian {}
elass Vehiele {}
public class TupleTest
static TwoTuple<String,Integer> f()
/1 El mecanismo de autoboxing convierte int en Integer:
return new TwoTuple<String, Integer> ("hi", 4 7) ;
static ThreeTuple<Amphibian, String, Integer> 9 () {
return new ThreeTuple<Amphibian, String, Integer>(
new Amphibian(), "hi " , 47);
static
FourTuple<Vehicle,Amphibian,String,Integer> h()
return
new FourTuple<Vehicle,Amphibian,St ring,Integer>(
new Vehicle(), new Amphibian (}, "hi n, 47) i
static
FiveTuple<Vehicle,Amphibian,String,Integer,Double> k()
return new
FiveTuple<Vehicle,Amphibian,Str i ng, Integer, Double> (
new Vehicle(), new Amphibian(), "hi " , 47, 11 . 1)
public static void main(String[] args}
TwoTuple<String,Integer> t t si f();
398 Piensa en Java
System.out.println(ttsi) i
JI ttsi.first = "there" // Error de compilacin: final
System.out.println(g()) ;
System.out.println(h()) ;
System.out.println{k()) i
/ * Output : (80% match)
(hi, 47 )
(Amphibian@l f6a7b9, hi, 47 )
(Vehicle@3Sce36, Amphibian@757aef, hi, 47 )
(Vehicle@9cab16, Amphibian@la46e30, hi, 47, 11.1)
* ///,-
Gracias a los genricos, podemos crear fcilmente cualquier tupla para devolver cualquier grupo de tipos, simplemente
escribiendo la expresin correspondiente.
Podemos ver cmo la especificacin final en los campos pblicos impide que sean reasignados despus de la construccin.
puede observarlo viendo cmo falla la instruccin ttsi.first = " there".
Las expresiones new son demasiado complejas. Posterionnente en el captulo veremos cmo simplificarlas utilizando t o ~
dos genricos.
Ejercicio 3: (1 ) Cree y pruebe un genri co Si,Tuple con seis elementos.
Ejercicio 4 : (3) Reescriba innerclasses/Sequence.java utili zando genricos.
Una clase que implementa una pila
Exami nemos algo ligeramente ms complicado: la tpica estructura de pila. En el Captulo 11 , Almacenamiento de objelos,
vi mos cmo implementar una pila utili zando un contenedor LinkedList en la clase net.mi ndview. utiI.Stack. En dicho
ejemplo, podemos ver que LinkedList ya dispone de los mtodos necesarios para crear una pi la. La clase Stack que imple-
mentaba la pila se construy componiendo una clase genrica (Stack<r con otra clase genrica (LinkedList<T . En
dicho ejemplo, observe que (con unas cuantas excepci ones que posterionnente realizaremos) un tipo genrico no es otra cosa
que un tipo nOnTIa!.
En lugar de utilizar LinkedList. podemos implementar nuestro propio mecanismo interno de almacenamiento enlazado.
11: generics / LinkedStack.java
II Una pila implementada con una estructura enlazada interna.
public class LinkedStack<T> {
private static cIass Node<U>
U item
Node<U> next
Node () { item = null next
Node (U item, Node<U> next )
this.item item;
this.next = next
nuH; }
boolean end () { return i tem "'''' null && next == null }
private Node<T> top = new Node <T>() II Indicador de fin
public void push(T item) {
top '" new Node<T> (item, top) ;
}
pUbli c T pop () (
T result = top.item
if ( !top.end ())
top = top. next i
return resul ti
public static void main (String[] args )
LinkedStack<String> lss new LinkedStack<String> () ;
for (String s : "Phasers on stun!".split ( U It } )
lss.push (s ) i
String Si
while {{s = lss.pop {) ) != null )
System. out.println (s ) ;
/ * Output:
stun!
on
Phasers
*///,-
La clase interna Node tambin es un genrico y ti ene su propi o parmetro de tipo.
15 Genricos 399
Este ejempl o hace uso de un indicador de fin para detemnar cundo la pila est vaca. El indicador de fin se crea en el
momento de construir el contenedor LinkedStack, y cada vez que se invoca push( ) se crea un nuevo objeto Node<T> y
se enlaza con el objeto Node<T> anterior. Cuando se invoca a pop(). siempre se devuelve top.item, y luego se descarta el
objeto Node<T> actua l y nos desplazamos al sigui ente, excepto cuando encontramos el indicador de fin, en cuyo caso no
noS desplazamos. De esa forma, si el cli ente contina invocando pope ), obtendr como respuesta valores nuH para indi car
que la pila est vaCa.
Ejercicio 5:
RandomList
(2) Elimine el parmetro de tipo en la clase Node y modifique el resto del cdigo en LinkedStack.java
para demostrar que una clase interna ti ene acceso a los parmetros de tipo genrico de su clase externa.
Como ejemplo adicional de contenedor, suponga que querernos di sponer de un tipo especial de li sta que seleccione aleato-
riamente uno de sus elementos cada vez que invoquemos select( ). Al hacer esto, podemos construir una herramienta que
funci one para todos los objetos, as que utili zamos genricos:
// : generics /RandornList.java
import java . util.*;
public class RandomList<T>
prvate ArrayList <T> storage = new ArrayList<T>();
private Random rand = new Random(47);
public void add(T item) { storage.add(item); }
public T select () {
return storage.get(rand.nextlnt (storage .size ()));
public static void main (String [] args) {
RandornList<String> rs = new RandomList<String>() ;
tor (String s: ("The quick brown fox jumped over " +
"the 1azy brown dog " ) . sp1it(rt " ))
rs.add(s) ;
for{int i = O; i < 11; i++ )
System. out . print (rs. select () + 11 ");
/ * Output :
brown over fox quick quick dog brown The brown lazy brown
* /// ,-
Ejercicio 6: ( 1) Utilice RandornList con dos tipos adicionales adems del que se muestra en main( ).
Interfaces genricas
Los genricos tambin funcionan con las interfaces. Por ejemplo, un generador es una clase que crea objetos. En la prcti-
ca, una especiali zac in del patrn de diseo basado en el mtodo defactoria, pero cuando pedimos a un generador que cree
400 Piensa en Java
un nuevo objeto no le pasamos ningn argumento, al contrario de lo que sucede con un mtodo de factora. El generador
sabe cmo crear nuevos objetos si n ninguna infonnacin adicional.
Tpicamente, un generador simplemente define un mtodo, el mtodo que produce nuevos objetos. Aqu , lo denominaremos
next( ) y lo incluiremos en las utilidades estndar:
1/: net/mindview/util/Generator.java
II Una interfaz genrica.
package net.mindview.util;
public interface Generator<T::> { T next (); } 11/:-
El tipo de retomo de next() se parametriza como T. Como puede ver, la utilizacin de genricos con interfaces no es dife-
rente de la util izacin de genricos con clases.
Para ilustrar la implementacin de un objeto Generator, necesitaremos al gunas clases. He aqu una jerarqua de ejemplo:
11: generics/coffee/Coffee.java
package generics.coffee;
public class Coffee {
private static long counter O;
private final long id = counter++
public String toString () {
return getClass () . getSimpleName () + " " + id
}
///,-
11: generics/coffee/Latte.java
package generics.coffee;
public class Latte extends Coffee {} 111:-
//: generics/coffee/Mocha.java
package generics.coffee;
public class Mocha extends Coffee {} 11/ :-
/1: generics/coffee/Cappuccino.java
package generics.coffee;
public class Cappuccino extends Coffee {} 111:-
11: generics/coffee/Americano.java
package generics.coffee
public class Americano extends Coffee {} /1/:-
//: generics/coffee/Breve.java
package generics . coffee;
public class Breve extends Cof fee {} /1/:-
Ahora, podemos impl ementar un objeto Generator<Coffee> que genera aleatori amente diferentes tipos de objetos Coffee:
11: generics/coffee/CoffeeGenerator.java
/1 Generar diferentes tipos de objetos Coffee:
package generics.coffee;
import java.util.*;
import net.mindview.util.*
public class CoffeeGenerator
implements Generator<Coffee::>, Iterable<Coffee::> {
private Class[] types = { Latte.class, Mocha.class,
Cappuccino. class, Americano. class, Breve. class, };
private static Random rand = new Random(47);
public CoffeeGenerator () {}
II Para iteracin:
private int size = o;
public CoffeeGenerator (int sz) { size
public Coffee next () {
try (
return (Coffee)
sz; )
types (rand. next lnt (t-ypes .length) ] . newlnstance () ;
// Informar de errores del programador en tiempo de ejecucin:
catch(Exception el {
throw new RuntimeException(el i
class Coffeelterator implements Iterator<Coffee>
int count = size;
public boo!ean hasNext () { return count :> O i }
public Coffee next () {
count--
return CoffeeGenerator . this.next() i
public void remove() { JI No implementado
throw new UnsupportedOperationException();
)
) ;
public Iterator<Coffee> iterator()
return new Coffeelterator();
public static void main(String[] args) {
CoffeeGenerator gen = new CoffeeGenerator() i
for(int i = O; i < Si i++l
System.out . println{gen.next() l;
for(Coffee e : new CoffeeGenerator(S))
System.out.println{c) ;
1*
Output:
Americano O
Latte 1
Americano 2
Mocha 3
Mocha 4
Breve 5
Americano 6
Latte 7
Cappuccino 8
Cappuccino 9
*111,-
15 Genricos 401
La interfaz Generator parametrizada garanti za que next( ) devuel va el tipo definido en el parmetro. CoffeeGenerator
tambin implementa la interfaz Iterable, por lo que se le puede usar en una instruccinforeach. Sin embargo, requiere un
"indicador de fin" para saber cundo parar, y esto se crea utilizando el segundo constructor.
He aqu una segunda implementacin de Generator<T>, que esta vez se utili za para generar nmeros de Fibonacci:
/1 : generics/Fibonacci.java
II Generar una secuencia de Fibonacci.
import net.mindview.util.*;
public class Fibonacci implements Generator<Integer>
private int count = O;
public Integer next() { return fib(count++); }
pri vate int f ib (int n ) {
if(n < 2) return 1;
return fib(n-2) + fib (n-1);
402 Piensa en Java
pub lic stat i c v o id main (String [ ] args l
Fibonacci gen = new Fibonacci () ;
f o r {int i :: O; i < 18; i++ l
System. out.print (gen.next () + " 11 ) ;
/ * Output:
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584
* jjj ,-
Aunque estamos trabajando con valores int tanto dentro como fuera de la clase, el parmetro de tipo es Integer . Esto nos
plantea una de las limitaciones de los genricos de Java. No se pueden utilizar primitivas como parmetros de tipo. Sin
embargo, Java SES ha aadi do, afortunadamente, la fu ncionalidad de conversin automtica entre primiti vas y tipos envol-
torio, para poder efectuar las conversiones fcilmente. Podemos ver el efecto en este ejemplo porque los va lores int se uti-
li zan, en general, en la clase de manera transparente.
Podemos ir un paso ms all y crear un generador de Fibonacci de tipo Iterable. Una opcin consiste en reimplementar la
clase y aadir la interfaz Iterabl e. pero no siempre tenemos control sobre el cdigo original, y no merece la pena reescribir
cdigo a menos que nos veamos obligados a hacerlo. En lugar de ello, podemos crear un adaptador para obtener la interfaz
deseada; este patrn de diseo ya fue presentado anteriomlente en el libro.
Los adaptadores pueden implementase de mltiples fomlas. Por ejemplo, podemos utili zar el mecanismo de herencia para
generar la clase adaptada:
11 : generics/ IterableFibonacci.java
11 Adaptar la clase Fibonacci para hacerla de tipo Iterable.
import java.util.*
public class IterableFibonacci
extends Fibonacci implements Iterable<Integer>
private int n
public IterableFibonacci ( int count l n = c ount
public Iterator<Integer> iterator( )
return new Iterator<Integer> () {
public boolean hasNext () { return n > O i }
public Integer next ( ) (
n--;
return IterableFibonacci.this . next ( );
public v o id remove () { li No implementado
throw new UnsupportedOperationException () i
}
} ;
public static void main (String [] args ) {
for ( int i : new IterableFibonacci(18 } )
System.out.print ( i + 11 11 ) ;
1* Output:
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584
* jjj ,-
Para utili zar IterableFibonacci en una instruccin foreach, hay que proporcionar al constructor un lmite para que
hasNexl() sepa cundo devolver false.
Ejercicio 7:
Ejercicio 8:
(2) Uti lice el mecani smo de composicin en lugar del mecanismo de herencia para adaptar Fibonacci con
el fm de hacerla de tipo Iterable.
(2) Siguiendo la fonna del ejemplo CoITee, cree una jerarqua de personajes (SloryCharacler) de su pel-
cula favorita, divindolos en buenos (GoodGuys) y malos (BadGuys). Cree un generador para
Slor yCharacler , siguiendo la fonna de CoffeeGeneralor.
15 Genricos 403
Mtodos genricos
Hasta ahora, hemos estado analizando la parametrizacin de clases enteras, pero tambin podemos parametrizar mtodos de
una clase. La propia clase puede ser o no genrica; esto no influye en la posibilidad de di sponer de mtodos genricos.
Un mtodo genri co permite que el mtodo vare independientemente de la clase. Como directriz, deberemos usar los mto-
dos genri cos "siempre que podamos". En otras palabras: si es posible hacer que un mtodo sea genrico, en lugar de que
lo sea la clase completa, probablemente el programa sea ms claro si hacemos genrico el mtodo. Adems, si un mtodo
es estti co, no tiene acceso a los parmetros genricos de tipo de la clase, por lo que si esa genericidad es necesaria en el
mtodo, deberemos definirlo como un mtodo genrico.
Para definir un mtodo gentico, simplemente colocamos una li sta de parmetros genricos delante del valor retorno del
modo sigui ent e:
jj: genericsjGenericMethods.java
public class GenericMethods
public <T> void f (T x ) {
System. out. println (x . getClass () . getName () ) ;
public statie void main(String[] args) {
GenericMethods gm = new GenericMethods() i
gm.f (" 10 );
gm. f ( 1 ) ;
gm.f(l.O) ;
gm.f(l.OF) ;
gm.f ( 'e' ) i
gm.f(gm) ;
} / * Output,
java .lang.String
java . lang.lnteger
java.lang.Double
java.lang.Float
java . lang.Character
GenericMethods
*///,-
La clase GenericMethods no est paralnetrizada, aunque es perfectamente posible parametrizar simultneamente tanto una
clase como sus mtodos. Pero en este caso, solo el mtodo f() tiene un parmetro de tipo, indicado por la lista de parme-
tros antes del tipo de retorno del mtodo.
Observe que con una clase genrica es preci so especificar los parmetros de tipo en el momento de instanciar la clase. Pero
con un mtodo genrico, usualmente no hace falta especificar los tipos de parmetro, porque el compi lador puede detenni-
nar esos tipos por nosotros. Este mecani smo se denomina inferencia del argumento de tipo. Por tanto, las llamadas a f()
parecen llamadas a mtodo nonnales, y en la prctica f() se comporta como si estuviera infinitamente sobrecargado. El
mtodo admitir incluso un argumento del tipo GenericMethods.
Para las llamadas a f() que usen tipos primitivos entra en accin el mecani smo de conversin de tipos automtica, envol-
viendo de manera transparente los tipos primitivos en sus objetos asociados. De hecho, los mtodos genricos y el mecanis-
mo de conversin automtica de tipos penniten eliminar parte del cdigo que anterionnente requera utilizar conversiones
de tipos manuales.
Ejercicio 9: ( 1) Modifique GenericMethods.java de modo que f( ) acepte tres argumentos, cada uno de los cuales
tiene que ser de un tipo parametrizado distinto.
Ejercicio 10: ( 1) Modifique el ejercicio anterior de modo que uno de los argumentos de f() no sea parametrizado.
404 Piensa en Java
Aprovechamiento de la inferencia del argumento de tipo
Una de las quejas acerca de los genricos es que aiiaden todava ms texto a nuestro cdigo. Considere el programa
holdinglMapOfLiSl.java del Capitul o 11 , Almacenamiento de objetos. La creacin del contenedor Map de List tiene el
aspecto sigui ente:
Map<Person, List<? extends Pet petPeople =
new HashMap<Person, List<? extends Pet() i
(Esta utilizacin de extends y los signos de interrogacin se explicarn posterionncntc en el captulo). Parece, por el ejem-
plo, que nos estamos repitiendo y que el compil ador debera deducir ulla de las li stas de argumentos genricos a partir de la
olra. En realidad, no puede deduci rl a, pero la inferencia del argumento de tipo en un mtodo genri co pcnnite reali zar algu-
nas simplificaciones. Por ejemplo. podemos crear una utilidad que contenga varios mtodos estticos y con la que se gene-
ren las impl ementaciones de los diversos contenedores ms comnmente utilizadas:
// : net/mindview/util/New.java
// Utilidades para simplificar la creacin de contenedores
11 genricos empleando la inferencia del argumento de tipo.
package net.mindview.util
import java.util.*
pub1ic class New {
public static <K, V> Map<K, V> map () {
return new HashMap<K,V>{);
pub1ic static <T> List<T> 1ist()
return new ArrayList<T>{);
public static <T> LinkedList<T> lList () {
return new LinkedList<T> ()
public static <T> Set<T> set ()
return new HashSet<T>();
public static <T> Queue<T> queue ()
return new LinkedList<T> () i
II Ejemplos,
public static veid main (String [] args) {
Map<String, List<String sls = New.map{) i
List<String> ls = New.list();
LinkedList<String> lIs = New.lList( ) i
Set<String> ss = New.set()
Queue<String> qs = New.queue( );
}
111 ,-
En main() podemos ver ejemplos de cmo se emplea esta herramienta: la inferencia del argumento de lipa elimina la nece-
sidad de repetir la li sta de parmetros genricos. Podemos apli car esto a holding/ MapOfList.java:
// : generics/ Simp1erPets.java
impert typeinfo.pets . *i
impert java.util. *
impert net.mindview.uti1.*;
pub1ic class Simp1erPets {
public static void main (String [] args) {
Map<Persen, List<? extends Pet petPeople
// El resto del cdigo es igual.
}
1/ 1,-
New. map () ;
15 Genricos 405
Aunque se trata de un ejemplo interesante del mecani smo de inferencia del argumento de tipo, resulta dificil detennioar las
ventajas de este mecani smo. A la persona que lea el cdigo la obligamos a analizar y a comprender esta biblioteca adicio-
nal y sus implicaciones, por lo que sera igual de productivo dejar la definicin original (que es bastante repetiti va) preci sa-
mente para simplificar. Sin embargo, si la biblioteca estndar de Java incluyera algo simil ar a la utilidad New.j ava que
hemos presentado, tendra bastante sentido utili zarla.
El mecanismo de inferencia de tipos no funciona ms que en las asignaciones. Si pasamos el resultado de una llamada a un
mtodo, tal como New.rnap(), como argumento a otro mtodo, el compi lador no intentar realizar una inferencia de tipos.
En lugar de ello, lo que har es tratar la llamada al mtodo como si el valor de retomo se asignara a una variable de tipo
Object. He aqu un ejemplo con el que se genera un error de compilacin:
JJ : genericsJLimitsOflnference.java
import typeinfo.pets. * ;
import java.util .* ;
public class LimitsOflnference
s t atic void
f (Map<Person, List<? extends Pet petPeople) {}
public static void main(String[] args) {
JJ f( New.map()); JJ No se compila
Ejerc icio 11 : ( 1) Pruebe New.j ava creando sus propias clases y verificando que New func ione adecuadamente con las
mi smas.
Especificacin explicita de tipos
Es posible especificar explcitamente el tipo de un mtodo genrico, aunque esta sintaxi s raramente es necesaria. Para hacer
esto, se coloca el tipo entre corchetes angulares despus del punto e inmediatamente antes del nombre del mtodo. A la hora
de invocar a un mtodo desde dentro de la mi sma clase, hay que utilizar thi s antes del punto; y cuando se trabaje con mto-
dos estt icos hay que emplear el nombre de la clase antes del punto. El problema mostrado en Limi tsOnnference.j ava
puede resolverse utilizando esta sintaxis:
JJ : genericsJExp licitTypeSpecification.java
import typeinfo.pets .*;
import java.util.*;
import net.mindview.util.*
public class ExplicitTypeSpecification
static void f (Map<Person, List<Pet petPeople) {}
public static void main(String[] args) {
f{New.<Person, List<Petmap ()) ;
Por supuesto, esto elimina la ventaja de utilizar la clase New para reducir la cantidad de texto tecleado, pero esta sintaxi s
adicional slo ser necesaria cuando no estemos escribiendo una instruccin de asignacin.
Ejercicio 12: (1) Repita el ejercicio anterior utilizando la especificacin explicita de tipos.
Varargs y mtodos genricos
Los mtodos genricos y las li stas de argumentos variables pueden coexistir perfectamente:
JJ: genericsJGenericVarargs . java
import java.util . *;
public class GenericVarargs
public static <T> List<T> makeList(T ... argsl
406 Piensa en Java
List<T> result = new ArrayList<T>();
for(T item : args)
result.add(item}
return resul t
public static void main(String[] args)
List<String> ls = makeList("A");
System.out.println(ls) ;
ls = makeList ( "A", "B", "e" ) ;
System.out.println(ls) ;
ls = makeList ("ABeDEFFH1JKLMNOPQRSTUVWXYZ" . split (" ") ) ;
System.out.println (ls) ;
/ * Output:
[A]
[A, B, e]
[, A, B, e, D, E, F, F, H, 1, J, K, L, M, N, O, P, Q, R, S, T, U, V, w, x, Y, z]
* /// ,-
El mtodo makeList( ) mostrado aqu tiene la mi sma funcionalidad que el mtodo java.utiI.Arr ays.asList() de la bibl io-
teca estndar.
Un mtodo genrico para utilizar con generadores
Resulta bastante cmodo utili zar Wl generador para rellenar un objeto COllection, y tambin tiene bastante sentido hacer
genrica esta operacin:
11: generics/Generators.java
11 Una utilidad para utilizar con generadores.
import generics.coffee.*;
import java.util.*;
import net.mindview. util.*
public cIass Generators {
public sta tic <T> eollection<T>
fill (eolleetion<T> eoll, Generator<T> gen, int n) {
for(int i = O; i < n; i++}
coll.add(gen.next( ;
return eoll;
public static void main(String[] args) {
eollection<eoffee> eoffee = fill (
new ArrayList<Coffee> (), new eoffeeGenerator(), 4);
for(eoffee e : coffee)
System.out . println(c) ;
eollection<lnteger > fnumbers = fill(
new ArrayList<Integer>() I new Fibonacci{) I 12);
for (int i : fnumbers)
System.out.print(i + ", ")
/ * Output:
Americano O
Latte 1
Americano 2
Mocha 3
1, 1, 2, 3, 5, 8, 13, 21, 34 , 55, 89, 144,
* /// ,-
Observe que el mtodo generico fill ( ) puede aplicarse de fomla transparente a contenedores y generadores de objetos
Coffee e Integer.
15 Gen ri cos 407
Ejercicio 13: (4) Sobrecargue el mtodo fill () de modo que los argumentos y tipos de retorno sean los subt ipos especi-
ficos de Collection: List, Queue y Set. De esta fonna, no perdemos el tipo de contenedor. Podemos uti-
lizar el mecanismo de sobrecarga para distinguir entre List y LinkedList?
Un generador de propsito general
He aqu una clase que produce un obj eto Generator para cualquier clase que disponga de un constructor predetemli nado.
Para reducir la cant idad de texto tecleado, tambin incluye un mtodo genrico para crear un objeto BasicGenerator:
/1 : net / mindview/ util / BasicGenerator.java
JI Crear automticamente un generador, dada una clase con un
/ / constructor predeterminado (sin argumentos ) .
package net . mindview.util
public class BasicGenerator<T> implements Generator<T> {
private Class<T> type
public BasicGenerator(Class<T> type) { this.type type
public T next ( ) {
try {
/ / Asume que el tipo es una clase pblica:
return type.newlnstance();
catch(Exception el {
t hrow new RuntimeException(e l
// Pr oducir un generador predeterminado dado un i ndicador de tipo:
public static <T> Generator<T> create(Class<T> t ype) {
return new BasicGenerator<T> (type) ;
}
/// , -
Esta clase proporci ona una implementacin bsica que producir objetos de una clase que ( 1) sea pblica (ya que
BasicGenerator est en un paquete separado, la clase en cuestin debe tener acceso pblico y no simplemente de paquete)
y (2) tenga un constructor predetenninado (uno que no torne ningn argumento). Para crear uno de estos obj etos
BasicGenerator, invocamos el mtodo create( ) y pasamos el indicador de tipo para el tipo que queramos generar. El
mtodo genri co create() pennite escribir BasicGenerator.create(MyType.class) en lugar de la instruccin new
BasicGenerator<MyType>(MyType.c\ass) que es ms compli cada.
Por ejemplo, he aqu una clase simpl e que dispone de un constructor predetenninado:
// : generics / CountedObject.java
public class CountedObject
private static long counter = O;
private fi nal l ong id = counter++
public long id ( ) { return id; }
public String toString( ) { return "CountedObject 11 + id}
/// , -
La clase CountedObject lleva la cuenta de cuntas instancias de s misma se han creado e informa de la cantidad total
mediante toString( ).
Utili zando BasicGenerator, podemos crear fcilmente un objeto Generator para CountedObject:
/ / : generics/ BasicGeneratorDemo . java
import ne t.mindview. util .* ;
public class BasicGeneratorDemo
public static void main(String[] args) {
Generator<CountedObject> gen =
BasicGenerator.create (CountedObject . class ) i
408 Piensa en Java
for ( int i ;;; Oi i < Si i++ )
System.out.println {gen.next ()) ;
/ * Output:
CountedObj ect O
CountedObj ect 1
CountedObject 2
CountedObject 3
CountedObject 4
*/// 0-
Podemos ver cmo el mtodo genrico reduce la cantidad de texto necesaria para crear el objeto Generator. Los genri cos
de Java nos fuerzan a pasar de todos modos el objeto Class, por lo que tambin podramos utili zarlo para la inferencia de
tipos en el mtodo ereate( ).
Ejercicio 14: ( 1) Modifique BasieGeneratorDemo.java para utilizar la fonna explicita de creacin del objeto
Generator (es decir, utilice el constructor explcito en lugar del mtodo genrico create()j.
Simplificacin del uso de las tuplas
El mecani smo de inferencia del argumento de tipo, junto con las importaciones de tipo static, nos pennite reescribir las
tuplas que hemos presentado anterionnente, para obtener una biblioteca de props ito ms general. Aqu, las tuplas pueden
crearse utilizando un mtodo esttico sobrecargado:
11: net/mindview/uti l /Tuple . java
II Biblioteca de tuplas utilizando el mecanismo de
I I inferencia del argumento de tipo.
package net . mindview.util
publie elass Tuple {
publie statie <A/B> TwoTuple<A, B> tuple {A a, B b ) {
return new TwoTuple<A,B> {a, b)
publie static <A/B/C> ThreeTuple<A,B,C>
tuple lA a, B b, e e l {
return new ThreeTuple<A,B/C> {a, b, e l i
publie static <A,a,C,D> FourTuple<A,s,C,D>
tuple lA a, B b, e e, D d i (
return new FourTuple<A,B,C,D> (a, b, e, d )
publie static <A,S,C,D/E>
FiveTuple<A,B,C/D,E> tuple (A a, B b, C e, O d, E e l
return new FiveTuple<A/s,C,D,E> (a, b, e, d, e l
)
/// 0-
He aqu una modificacin de TupleTest.java para probar Tuple.java:
11: generies/TupleTest2.java
import net . mindview.util .*
import statie net . mindview.util.Tuple.*
public class TupleTest2 {
static TwoTuple<String,Integer> f () {
return tuple(Uhi
U
, 47 l ;
statie TwoTuple f2 () { return tuple ( lIhi" I 47 ) }
static ThreeTuple<Amphibian, String I Integer> 9 () {
return tuple (new Amphibian () , "hi", 47 ) i
statie
FourTuple<Vehicle,Amphibian,String,Integer> h()
return tuple{new Vehicle () , new Amphibian(), tlhi" , 47) i
statie
Fi veTuple<Vehicle. Amphibian I String, Integer, Double> k () {
return tuple(new Vehicle(), new Amphibian{),
tlh" , 47, 11.1);
public statie void main(String(] args)
TwoTuple<String,Integer> tts : f() ;
System. out . println(ttsi) ;
System.out.println(f2()) ;
System.out.println(g()) ;
System. out.println(h()) ;
System.out.println(k{)) ;
/ * Output: (80% match)
(hi, 47)
(hi, 47)
(Amphi bian@7d772e, hi, 47)
(Vehicle@757aef, Amphibian@d9f9c3, hi, 47)
(Vehicle@la46e30, Amphibian@3e25aS , hi, 47, 11 . 1)
* /// ,-
15 Genricos 409
Observe que f() devuelve un objeto parametrizado 1\voTuple, mientras que 1'2( ) devuelve un objeto TwoTuple no para-
melri zado. El compi lador no proporciona ninguna advertencia acerca de n() en este caso porque el valor de retomo no est
siendo utilizado de fanna parametri zada; en un cierto sentido, est siendo "generalizado" a un objeto TwoTuple no para-
metrizado. Sin embargo, si quisiramos calcular el resultado de f2( ) en un objeto parametrizado TwoTuple. el compilador
generara una advertencia.
Ejercicio 15: (1) Verifique la afirmacin anterior.
Ejercicio 16: (2) Aada una tupla SixTuple a Tuple.java y prubela mediante TupleTest2,java.
Una utilidad Set
Vamos a ver otro ejemplo del uso de mtodo genrico. Considere las relaciones matemticas que pueden expresarse utili-
zando conjuntos. Estos conjuntos pueden definirse de fom18 cmoda como mtodo genrico, para utili zarlos con todos los
diferentes tipos:
ji : net / mindview/util/Sets.java
package net.mindview. util
import java .util.*
public class Sets {
public static <T> Set<T> union (Set<T> a, Set<T> b) {
Set<T> result : new HashSet<T>(a)
result . addAll(b) ;
return result
public static <T>
Set<T> intersection(Set<T> a, Set<T> b )
Set<T> result = new HashSet<T> (a) ;
result .retainAll(b) ;
return result;
II Restar subconjunto de un superconjunto:
public static <T> Set<T>
difference(Set<T> superset, Set<T> subset)
410 Piensa en Java
Set<T> result = new HashSet<T> (superset) ;
result.removeAll(subset) ;
return result;
11 Reflexivo--todo lo que no est en la interseccin:
public static <T> Set<T> complement(Set<T> a, Set<T> b)
return difference{union(a, b), intersection(a, b));
Los primeros tres mtodos dupli can el primer argumento copi ando sus referenci as en un nuevo objeto HashSet, de modo
que los conjunt os utilizados como argumentos no se modifican directamente. El valor de retorno ser, por tanto, un nuevo
objeto Seto
Los cuatro mtodos representan las operaciones matemticas de conjuntos: union() devuelve un objeto Set que conti ene la
combinacin de los dos argumentos, intersection( ) devuelve un obj eto Set que contiene los elementos comunes a los dos
argumentos, difference( ) resta los elementos subsel de superset y complement( ) devuelve un obj eto Sel con todos los
elementos que no formen parte de la interseccin. Para crear un ej emplo simple que muestre los efectos de estos mtodos,
he aqu una enumeracin que conti ene di ferentes nombres de acuarelas:
11: generics/watercolors/Watercolors.java
package generics.watercolors;
public enum Watercolors {
ZINC, LEMON_YELLOW, MEDIUM_YELLOW, DEEP YELLOW, ORANGE,
BRILLIANT_RED, CRIMSON, MAGENTA, ROS E_MADDER, VIOLET,
CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE,
COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE,
SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER,
BURNT_UMBER, PAYNES_GRAY, IVORY_BLACK
///,-
Por comodidad (para no tener que cualifi car todos los nombres) importamos esta enumeracin estticamente en el ejemplo
siguiente. Este ej empl o utili za EnumSet, que es una herrami enta de Java SES que pemlite crear conjuntos fc ilmente a par-
tir de enumeraciones (aprenderemos ms de EnumSet en el Captulo 19, n pos enumerados). Aqu , al mtodo esttico
EnumSct.rangc( ) se le pasan el primer y el ltimo elemento del rango que hay que utilizar para crear el objeto Set resul-
tante:
11: generics/WatercolorSets.java
import generics.watercolors.*;
import java.util. * ;
import static net.mindview.util.Print.*;
import static net.mindview.util.Sets.*;
import static generics.watercolors.Watercolors. * ;
public class WatercolorSets {
public static void main (String [] args) {
Set<Watercolors> setl =
EnumSet. range (BRILLIANT_RED, VIRIDIAN_HUE) i
Set<Watercolors> set2 :
EnumSet. range (CERULEAN_BLUE_HUE, BURNT_UMBER);
print("setl: " + setl) i
print (ti set2: ti + set2);
print{"union(setl, set2): " + union(setl, set2;
Set<Watercolors> subset = intersection(setl, set2);
print ( 01 intersection (setl, set2): + subset) ;
print{ "difference{setl, subset): +
differ ence(setl, subset);
print ( "differenc e (set2, subset) : +
difference(set 2, subset);
print ( "complement (set l, set2): " +
15 Genricos 411
complement{setl, set2));
/* Output: (Sample)
setl, IBRILLIANT_RED, CRIMSON, MAGENTA, ROSE_MADDER, VIOLET, CERULEAN_BLUE HUE,
PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUEJ
set2, ICERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE, PERMANENT_GREEN,
VIRIDIAN_HUE, SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER, BURNT_UMBERJ
union(setl, set2): [SAP_GREEN, ROSE_MADDER, YELLOW OCHRE, PERMANENT GREEN, BURNT UMBER,
COBALT_BLUE_HUE, VIOLET, BRILLIANT_RED, RAW_UMBER, ULTRAMARINE, BURNT_SIENNA, CRIMSON,
CERULEAN_BLUE_HUE, PHTHALO_BLUE, MAGENTA, VIRIDIAN_HUEJ
intersection(setl, set2): [ULTRAMARINE, PERMANENT_GREEN, COBALT BLUE_HUE, PHTHALO_BLUE,
CERULEAN_BLUE_HUE, VIRIDIAN_HUEJ
difference(setl, subset): [ROSE_MADDER, CRIMSON, VIOLET, MAGENTA, BRILLIANT_REOl
differencelset2, subsetl, lRAW_UMBER, SAP_GREEN, YELLOW_OCHRE, BURNT SIENNA, BURNT_UMBERJ
complement(setl, set2): [SAP_GREEN, ROSE_MADDER, YELLOW_OCHRE, BURNT_UMBER, VIOLET, BRI-
LLIANT_RED, RAW_UMBER, BURNT_SIENNA, CRIMSON, MAGENTAJ
* jjj,-
Analizando la salida. puede ver el resultado de cada una de las operaciones.
El sigui enre ejemplo utili za Sets.difference( ) para mostrar las diferencias de mtodos entre diversas clases Collection y
Map de java.util :
jI: net/mindview/util/ContainerMethodDifferences.java
package net.mindview . util
import java.lang.reflect. * ;
import java .util.;
public class ContainerMechodDifferences
statie Set<String> methodSet(Class<?> type)
Set<Scring> result = new TreeSec<String>();
for(Method m : type.getMechods(
resulc.add(m.getName( ) i
return resul t;
statie void interfaces{Class<?> typel
System. out. print (It Interfaces in " +
type.getSimpleName() ... ": ");
List<String> result = new ArrayList<String>();
for(Class<?> e type.getlnterfaces(
result.add(c.getSimpleName( i
System.out.println(result) ;
statie Set<String> object = methodSet(Object.class);
statie { object.add{lIclone
U
}; }
static void
difference(Class<?> supersec, Class<?> subset)
System. out.print(superset .getSimpleName() +
ti extends " + subset . getSimpleName () + ", adds: " ) ;
Set<String> comp = Sets.difference(
methodSet(superset), methodSet(subset;
comp.removeAll(object ) ; II No mostrar mtodos tObject
t
System.out.println(comp) ;
interfaces (superset ) ;
public static void main(String[] args)
System. out. println ( "Collection: " +
methodSet(Collection.class i
interfaces(Collection.class ) ;
difference(Set.class, Collection.class);
difference(HashSet.class, Set.class);
412 Piensa en Java
difference(LinkedHashSet.class, HashSet.class)
difference(TreeSet.class, Set.class}
difference(List.class, Collection.class};
difference(ArrayList.class, List.class)
difference(LinkedList.class, List.class) i
difference(Queue.class, Collection.class);
difference(PriorityQueue . class, Queue.class)
System.out.println("Map: 11 + methodSet(Map.class)};
difference(HashMap.class, Map.class } i
difference(LinkedHashMap.class, HashMap.class) ;
difference (SortedMap.class, Map.class);
difference(TreeMap.class, Map.class )
La salida de este programa fue utili zada en la seccin "Resumen" del Captulo 11 , Almacenamiento de objetos.
Ejercicio 17: (4) Analice la documentacin del JDK correspondiente a EnumSet. Ver que hay definido un mtodo
clone(), clonar. Sin embargo, no podemos efectuar una clonacin a partir de la referencia a la interfaz Set
que se pasa en Sets.java. Podra modificar Sets.java para tratar tanto el caso general de una interfaz Set,
tal como se muestra, como el caso especial de un objeto EnumSet, utilizando c1one( ) en lugar de crear
un nuevo objeto HashSet?
Clases internas annimas
Los genricos tambin pueden utilizarse con las clases internas y con las clases internas annimas. He aqu un ejemplo que
implementa la interfaz Generator uti li zando clases internas annimas:
JJ: genericsJBankTeller.java
JI Una simulacin muy simple de un cajero automtico.
import java.util.*;
import net.mindview.util.*
class Customer {
private static long counter = 1;
private final long id = counter++
private Customer() {}
public String toString () { return "Customer
/1 A methad to produce Generator objects:
public static Generator<Customer> generator()
return new Generator<Customer>(} (
+ id; }
public Customer next () { return new Customer () ;
} ;
class Teller {
private static long counter = 1;
private final long id = counter++i
pri vate Teller () {}
public String toString () { return "Teller 11 + id }
JI Un nico objeto Generator:
public static Generator<Teller> generator
new Generator<Teller> () {
public Teller next () { return new Teller ()
} ;
public class BankTeller {
public static void serve(Teller t, Customer el
System.out.println(t + " serves 11 + el i
public static void main(String[] args)
Random rand = new Random(47)
line = new LinkedList<Customer>();
Generators.fill(line, Cuscomer.generator(), 15);
List<Teller> tellers = new ArrayList<Teller>();
Generators.fill(tellers, Teller.generator, 4) ;
for{Customer e
,
line)
serve {tellers .get (rand.nextlnt (tellers .size ()}) ,
/ '
Output :
Teller 3 serves Customer 1
Teller 2 serves Customer 2
Teller 3 serves Customer 3
Teller 1 serves Customer 4
Teller 1 serves Customer 5
Teller 3 serves Customer 6
Teller 1 serves Customer 7
Tel ler 2 serves Customer 8
Teller 3 serves Customer 9
Teller 3 serves Customer 10
Teller 2 serves Customer 11
Teller 4 serves Customer 12
Teller 2 serves Customer 13
Teller 1 serves Customer 14
Teller 1 serves Customer 15
,///,-
15 Genricos 413
el i
Tanto Customer como Teller ti enen constructores privados, lo que nos obli ga a utili zar objetos Generator. Custorner tiene
un metodo generator( ) que genera un nuevo mtodo Generator<Customer> cada vez que lo invocamos. Puede que no
necesitemos mltiples objetos Generator, y TeIler crea un nico objeto generator pblico. Si analiza main( ) ver que
ambas tcnicas se uti li zan en los mtodos fill( ).
Puesto que tanto el mtodo generator( ) de Customer como el objeto Generator de Teller son estticos, no pueden for-
mar pal1e de una interfaz, as que no hay fonDa de hacer genrica esta fu ncin concreta. A pesar de ello funciona razona-
blemente bien con el mtodo fill ( ).
Examinaremos otras versiones de este problema de versiones de colas en el Captulo 21, Concurrencia.
Ejercicio 18: (3) Siguiendo la fOffila de BankTeller.java, cree un ejemplo donde el pez grande (Bi gFish) se coma al
chico (LittleFish) en el ocano (Ocean).
Construccin de modelos complejos
Una ventaja importante de los genericos es la capacidad de crear modelos compl ejos de forma simple y segura. Por ejem-
pl o. podemos crear fci lmente una li sta de tupl as:
11: generics/TupleList.java
11 Combinacin de tipos genricos para crear tipos genricos complejos.
import java.util.*
import net.mindview.util.*
public class TupleList<A,B,C,D>
extends ArrayList<FourTuple<A,B,C,D
public static void main(String[] args)
TupleList<Vehicle, Amphibian, String, Integer> tI =
new TupleList<Vehicle, Amphibian, String, Integer>()
tl.addITupleTest.h()) ;
414 Piensa en Java
tl.add ITupleTest.h l)) ;
for {FourTuple<Vehicle,Amphibian,String,Integer> i: tI )
System. o ut.printl n{ i ) ;
1* Output: (75% match)
(Vehicle@11b86e7, Amphibian@35ce36, hi, 47 )
(Vehicle@757aef, Amphibian@d9f9c3, hi, 47 )
* /// ,-
Aunque hace falta bastante texto (especialmente en la creacin del iterador). obtenemos una estnlctura de datos bastante
potente sin necesidad de utilizar demasiado cdigo.
He aqu otro ejemplo donde se muestra lo fcil que es crear modelos complejos utilizando tipos genricos. Cada clase se
crea como un bloque componente y la solucin total tiene mltiples partes. En este caso, el modelo corresponde a un comer-
cio de venta al por menor con pasillos, estanteras y productos:
/ 1 : generics/Store.java
JI Construccin de un modelo complejo utilizando contenedores genricos.
import java . util.*;
import net.mindview.util.*
class Product {
private final int id;
private String description
private double price;
public Product{int IDnumber, String descr, double price) (
id = IDnumber
description = descr i
this.price = price;
System.out.println(toString()) ;
public String toString{)
return id + ti: " + description + ", price: $" + price
public void priceChange(double change)
price += change
public static Generator<Product> generator
new Generator<Product> () {
private Random rand = new Random(47);
public Product next () (
}
};
return new Product(rand.nextlnt{lOOO), "Test",
Math.round(rand.nextDouble{) * 1000.0) + 0.99);
class Shelf extends ArrayList<Product> {
public Shelf (int nProducts) {
Generators.fill(this, Product.generator, nProducts);
class Aisle extends ArrayList<Shelf> {
public Aisle(int nShelves, int nProducts)
for(int i = O; i < nShelves; i++)
add(new Shelf(nProducts));
class CheckoutStand {}
class Off ice {}
public class Store extends ArrayList<Ais!e> {
private ArrayList<CheckoutStand> checkouts
new ArrayList<CheckoutStand>();
private Off ice of f ice = new Office();
public Store(int nAisles, i n t nShelves, int nProductsl
far(int i = O; i < nAis!es i++ )
add(new Aisle (nShelves, nProducts)) i
publ ic Stri ng toStri ng()
StringBuilder result new StringBui l der() ;
for (Aisl e a : this)
t or {She l f s : al
f or IProduc t p , s i {
r e s ul t.append (p ) i
r esul t .append ( "\ n" ) ;
return result.to String {) i
publ i c s t at i c void main (String [] args ) {
System.out.println (new Store (14, 5, 10)) i
/ *
Output:
25 8, Test, price: $400.99
861 , Test, price: $160 . 99
868 , Test, price: $417.99
207 , Test, pri ce: $268.99
55 1, Test, price: $114.99
278, Test, price: $804 . 99
520 , Test, priee: $554.99
140 , Test, priee: $530 . 99
* /// ,-
15 Genri cos 415
Como puede ver en Store.toString(), el resultado son muchos niveles de contenedores que, a pesar de la complejidad, resul-
tan manejables y son seguros en lo que al tratamiento de tipos se refiere. Lo ms impresionante es que no resulta demasia-
do complej o, desde el punto de vista intelectual, construir dicho tipo de modelos.
Ejercicio 19: (2) Siguiendo la fonna de Store.java, construya un modelo de un buque de carga donde se utilicen conte-
nedores metlicos para las mercancas.
El misterio del borrado
A medida que nos sumergimos ms profundamente en los genricos, aparecen una seri e de aspectos que parecen no
tener sentido a primera vista. Por ejemplo, aunque podemos escribir ArrayList.class, no podemos escribir
ArrayList<lnteger>.class. Considere tambin lo siguiente:
// : generics / ErasedTypeEquivalenee.java
import java.util. *
public elass ErasedTypeEquivalenee
publie statie void main ( String[] args )
Class el = new ArrayList<String> ( ) . getClass () ;
Class e2 = new ArrayList<Integer> () .getClass () ;
System.out.println (el == e2 )
/ * Output:
t rue
* /1/ ,-
416 Piensa en Java
ArrayList<String> y ArrayList<lnteger> son claramente de tipos di stintos. Los diferentes tipos se comportan de fonna
di stinta, y si tratamos de almacenar un objeto Integer en un contenedor ArrayList<String>, obtendremos un comporta-
miento diferente (la operacin falla) que si tratamos de almacenar ese obj ero Intcger en un contenedor ArrayList<lnteger>
(la operacin s est permitida). Sin embargo, el programa anterior sugiere que ambos tipos son iguales.
He aqu atTo ejemplo que aumenta todava ms la confusin:
// : generics/Lostlnformation.java
import java.util.*;
class Frob {}
cIass Fnorkle {}
cIass Quark<Q> {}
cIass ParticIe<POSITION,MOMENTUM> {}
pubIic cIass Lostlnformation {
public static void main(String[] args) {
List<Frob> Iist = new ArrayList<Frob> ();
Map<Frob,Fnorkle> map = new HashMap<Frob,Fnorkle>{);
Quark<Fnorkle> quark = new Quark<Fnorkle>{);
ParticIe<Long,Double> p = new Particle<Long,Double>{);
System.out.println(Arrays.toString(
list.getClass() .getTypeParameters{)));
System. out . println(Arrays.toString(
map. getClass () . getTypeParameters () ) ) ;
System.out.println(Arrays.toString(
quark. getClass () . getTypeParameters () ) } ;
System.out.println(Arrays . toString(
p.getClass() .getTypeParameters( );
} / * Output,
[E]
[K, V]
[O]
[POSITION, MOMENTUMI
* /// ,-
De acuerdo con la documentacin del JDK, Class.getTypeParameters( ) "devuelve una matri z de objetos TypeVariable
que representan las vari ables de tipo definidas en la declaracin genri ca .. . " Esto parece sugerir que podramos ser capa-
ces de averiguar cules son los tipos de parmetro. Sin embargo, como podemos ver analizando la salida, lo nico que pode-
mos averiguar son los contenedores utilizados como variables para los parmetros, lo cual no constiulye una informacin
muy interesante.
La cruda realidad es que:
No hay informacin disponible acerca de los tipos de parmetros genricos dentro del cdigo genrico.
Por tanto, podemos llegar a detemlinar cosas como el identificador del parmetro de tipo y los lmites del tipo genrico, pero
no podemos ll egar a detenninar los parmetros de tipo reales utilizados para crear una instancia concreta. Este hecho, que
resulta especialmente frustrante para los que tienen experiencia previa con e++, constituye el problema fundamental al que
hay que enfrentarse cuando se trabaja con genricos de Java.
Los genricos de Java se implementan utili zando el mecanismo de borrado. Esto significa que toda la illfomlacin espec-
fica de tipos se borra cuando se utili za un genrico. Dentro de un genrico, la nica cosa que sabemos es que estamos usan-
do un objeto. Por tanto, List<String> y List<Integer> SOIl , de hecho, el mi smo tipo en tiempo de ejecucin. Ambas fonnas
se "borran" sustiulyndolas por su tipo de origen List. Entender este mecani smo de borrado y cmo hay que tratar con l
const ituye uno de los principales problemas a la hora de aprender el concepto de genricos en Java, ste es precisamente el
probl ema que anali zaremos a lo largo de esta seccin.
15 Gen ri cos 417
La tcnica usada en e++
He aqu un ejemplo e++ que utiliza plamillas. Observar que la sintaxi s para los tipos parametri zados es bastante similar,
ya que Java se ha inspirado preci samente en e++:
ji : generics / Templates . cpp
#include <iostream>
using namespace std;
template<class T> class Manipulator {
T obj ;
public:
) ;
Manipulat or (T x l ( obj = x; )
void manipulate (1 { obj . f (1 ; )
class HasF
public :
void f () caut tlHasF;: f ( ) " endl; }
) ;
int main () {
HasF h f ;
Manipulator<HasF> manipulator (hf);
manipul a tor.manipulate{ ) ;
1* Output:
HasF, ,f ()
((( , -
La clase Ma nipulator almacena un objeto de tipo T. Lo interesante es el mtodo manipulate( ) que invoca un mtodo f()
sobre obj . Cmo puede saber que el mtodo f( ) existe para el parmetro de tipo T? El compilador C++ efecta la com-
probacin cuando instanciamos la plantilla, por lo que el punto de instantacin de Manipulator<HasF> comprueba que
HasF ti ene un mtodo f( ). Si no fuera as, se obtendra un error en ti empo de compil acin, preservndose por tanto la segu-
ridad referente a los tipos.
Escribir este tipo de cdigo en C++ resulta sencillo, porque cuando se instancia una plantilla, el cdigo de la pl anti ll a cono-
ce el tipo de sus parmetros de plantilla. Los genri cos de Java son di stintos. He aqu la traduccin de HasF:
JJ : generics J HasF.java
public class HasF {
public void f {) { System.out.println ( "HasF.f( ) " } }
) ((( ,-
Si tomamos el resto del ejempl o y lo traducimos a Java no se podr compi lar:
ji : generics / Manipulation . java
/ / {Compi leTimeError} (Won' t compile )
cl ass Manipulator<T> {
private T obj
public Manipulator(T x ) { obj = Xi }
/ / Error : no se puede encontrar el smbolo: mtodo f () :
public void manipulate (1 ( obj. f ( ) ; )
public class Manipulation {
public static void main(String[) args }
HasF hf = new HasF()
Manipulator<HasF> manipulator
new Manipulator<HasF> (hf ) i
418 Piensa en Java
manipulator.manipulate() ;
)
///,-
Debido al mecani smo de borrado. el compi lador de Java no puede relacionar el requi sito de que manipulate( ) debe Ser
capaz de invocar f( l sobre obj con el hecho de que HasF' tiene un mtodo f( l. Para poder invocar f( l. debemos ayudar a
la clase genrica, proporcionndola un l"te que indique al compilador que slo debe aceptar los tipos que se confonnen
con dicho lmite. Para esto se utiliza la palabra clave extends. Una vez que se incluye el lmite. s que se puede realizar la
compi lacin:
1/: generics/Manipulator2.java
class Manipulator2<T extends HasF>
pri vate T obj i
public Manipulator2(T x )
public void manipulate()
///,-
obj = x; )
obj . f (); )
El limite <T eXlends HasF'> dice que T debe ser de tipo HasF' o algo derivado de HasF' . Si es asi, entonces resulta seguro
invocar f( l sobre obj .
Deci mos. a este respecto, que el parmetro de tipo genrico se borra de acuerdo con su primer lmite (es posible tener ml
tipl es limites, como veremos posteriomlente). Tambin hablamos en relacin con esto, del borrado del parmetro de lipo.
El compi lador, en la prctica, sustituye al parmetro de tipo por lo que el lmite indique, de modo que en el caso anterior T
se borra y se sustituye por HasF, lo cual es lo mi smo que sustituir T por HasF en el cuerpo de la clase.
El lector podra pensar, correctamente, que en Manipulation2.java, los genricos no proporcionan ninguna ventaja.
Podramos perfectamente realizar el borrado de tipos nosotros y crear una clase sin genricos:
11: generics/Manipulator3.java
class Manipulator3 {
private HasF obj
public Manipulator3 (HasF x) { obj = X
public void manipulate () { obj. f ()
///,-
Esto nos plantea una cuestin importante: los genricos slo son tiles cuando deseamos utili zar parmetros de tipo que sean
ms "genricos" que un tipo especfico (y todos sus subtipos); en otras palabras, cuando queramos escribir cdigo que fun
cione con mltiples clases. Como resultado, los parmetros de tipo y su apl icacin dentro de un fragmento til de cdigo
genri co sern nonnalmente ms complejos que una simple sustitucin de clases. Sin embargo, no debemos concluir por
ello que cualquier cosa de la fom13 <T extends UasF> no tiene ningn sentido. Por ejemplo, si una clase ti ene un mtodo
que devuelve T. entonces los genricos son tiles, porque pennitirn devolver el tipo exacto:
11: generics/ReturnGenericType.java
class ReturnGenericType<T extends HasF>
priva te T obj;
public ReturnGenericType (T x l { obj = x;
public T get () ( return obj; )
/// ,-
Es preci so examinar todo el cdigo y detenninar si es lo suficientemente complejo como para merecer el uso de genricos.
Examinaremos el tema de los lmites con ms detalle ms adelante en el captulo.
Ejercicio 20: (1) Cree una interfaz con dos mtodos y una clase que implemente dicha interfaz y aada un tercer mto-
do. En otra clase, cree un mtodo genrico con un tipo de argumento que est limitado por la interfaz y
demuestre que los mtodos de la interfaz son invocables dentro de este mtodo genrico. En main( l , pase
una instancia de la clase implementadora al mtodo genrico.
15 Genricos 419
Compatibilidad de la migracin
Para eliminar cualquier potencial confusin acerca del mecanismo de borrado de tipos. es necesario entender claramente que
no se trala de una caracterstica del lenguaje. Se trata de un compromi so en la implementac in de los genricos de Java,
compromiso que es necesario porque los genricos no han fonnado parte del lenguaje desde el principio. Este compromi so
puede crearnos algunos quebraderos de cabeza, por lo que es necesario acostumbrarse a l lo antes posible y emender a qu
se debe.
Si los genricos hubieran fonnado parte de Java 1.0, esta funcionalidad no se habra implementado utili zando el mecanis-
mo de borrado de tipos, si no que se habra empl eado el mecanismo de la reificacin para retener los parmetros de tipo como
entidades de primera clase, de modo que seramos capaces de realizar, con los parmetros de tipo, operac iones de refl exin
y operaciones del lenguaje basadas en tipos. Veremos posterionnente en el captulo que el mecani smo de borrado reduce el
aspecto "genrico" de los genricos. Los genricos siguen siendo til es en Java, pero lo que pasa es que no son tan tiles
como podran ser, y la razn de ello es precisamente el mecanismo de borrado de tipos.
En una implementacin basada en dicho mecanismo, los tipos genricos se tratan como tipos de segunda clase que no pue-
den utilizarse en algunos contextos importantes. Los tipos genricos slo estn presentes durante la comprobacin esttica
de tipos, despus de lo cua l todo tipo genrico del programa se borra, sustituyndolo por un tipo lmite no genrico. Por
ejemplo, las anotaciones de tipos como List<T> se borran sustituyndolas por List, y las variables de tipos nonuales se
borran sustituyndolas por Object a menos que se especifique un lmite.
La principal moti vacin para el mecanismo de borrado de tipos es que permite utilizar clientes de cdigo genrico con
bibliotecas no genricas, y viceversa. Esto se denomina a menudo compatibilidad de la migracin. En un mundo ideal , exis-
tiria un punto de partida en el que lodo hubiera sido hecho genrico a la vez. En la realidad, incluso aunque los programa-
dores slo estn escribiendo cdigo genrico, se vern forzados a tratar con bibliotecas no genricas que hayan sido escritas
antes de la aparicin de Java SE5. Los autores de esas bibliotecas puede que no ll eguen nunca a tener ningn motivo para
hacer su cdigo ms genrico, o puede si mplemente que tarden algn tiempo en ponerse manos a la obra.
Por ello, los genricos de Java no slo deben soportar la compatibilidad descendente (el cdigo y los archivos de clase exis-
tentes siguen siendo legales y continan significando lo que antes significaban) sino que tambin tienen que soportar la com-
patibilidad de migracin, de modo que las bibliotecas puedan llegar a ser genricas a su propio ritmo y de modo tambin
que, cuando una biblioteca se reescriba en forma genrica, no haga que dejen de funcionar el cdigo y las aplicaciones que
dependen de ella. Despus de decidir que el objeto era ste, los di seliadores de Java y di versos grupos que estaban trabajan-
do en el problema, decidi eron que el borrado de tipos era la nica solucin factible. El mecani smo de borrado de tipos per-
mite esta migracin hacia el cdigo genrico, al conseguir que el cdigo no genrico pueda coexistir con el que s lo es.
Por ejemplo, suponga que una aplicacin utiliza dos bibliotecas, X e Y, y que Y utiliza la biblioteca Z. Con la aparicin de
Java SE5, los creadores de esta apli cacin y de estas bibliotecas probablemente terminen por efectuar una migracin hacia
cdigo genrico. Sin embargo, cada uno de esos diseadores tendr diferentes motivaciones y diferent es restricciones en lo
que respecta a dicha migracin. Para conseguir la compatibilidad de migracin, cada biblioteca y aplicacin tiene que ser
independiente de todas las dems en lo que respecta a la utilizacin de genricos. Por tanto, no deben ser capaces de detec-
tar si las otras bibliotecas estn utilizando genricos o no. En consecuencia, la evidencia de que una biblioteca concreta est
usando genricos debe ser "borrada".
Sin algn tipo de ruta de migracin, todas las bibliotecas que hubieran sido di seIiadas a lo largo del tiempo correrian el ries-
go de no poder ser utilizadas por los desarrolladores que decidieran comenzar a utilizar los genricos de Java. Pero como
las bibliotecas son la parte del lenguaje de programacin que mayor impacto tiene sobre la productividad, este coste no
resultaba aceptable. Si el mecanismo de borrado de tipos era la mejor ruta de migracin posible o la nica existente, es algo
que slo el tiempo nos dir.
El problema del borrado de tipos
Por tanto, la justificacin principal para el borrado de tipos es el proceso de transicin de cdigo no genrico a cdigo gen-
rico, y la incorporacin de genricos dentro del lenguaje sin hacer que dejen de funcionar las bibliotecas existentes. El borra-
do de tipos permite que el cdigo de cliente existente, no genrico, contine pudiendo ser usado sin modificacin, hasta que
los clientes estn listos para reescribir el cdigo de cara a utili zar genricos. Se trata de una mot ivacin muy noble, porque
no hace que de repente deje de funcionar todo el cdigo existente.
420 Piensa en Java
El coste del borrado de tipos es signifi cativo. Los tipos genricos no pueden utili zarse en operaciones que hagan referencia
explcita a tipos de tiempo de ejecucin; como ejemplo de estas operaciones podemos citar las proyecciones de tipos. las
operaciones instanceof y las expresiones ne"'. Como toda la infonnacin de tipos acerca de los parmetros se pierde, cada
vez que escribamos cdigo genrico debemos estar perpetuamente acordndonos de que la idea de que di sponemos de i n o r ~
macin de tipos es solo aparente. Por tanto, cuando escribimos un fragmento de cdigo como ste:
class Foo<T>
T var;
podra parecer que al crear una instancia de Foo:
Foo<Cat> f = new Foo<Cat>();
el cdi go de la clase Foo debera saber que ahora est trabajando con un objeto Cato La sintaxi s sugiere de manera directa
que el tipo T est siendo sustituido a lo largo de toda la clase. Pero en realidad no es as y debemos siempre tener presente,
cuando estemos escribiendo el cdigo para la clase, que se trata simplemente de un objeto de tipo Object.
Adems, el borrado de tipos y la compatibilidad de migracin significan que el uso de genricos no se impone en aquellas
ocasiones en que sera bueno que se impusiera:
11: generics/ErasureAndlnhertance.java
class GenericBase<T> {
prvate T element;
public void set (T arg) { arg = element;
publ ic T get () { return element; }
class Derivedl<T> extends GenericBase<T> {}
class Derived2 extends GenericBase {} 11 Ninguna advertencia
11 class Derived3 extends GenericBase<?> {}
//
//
//
Extrao error:
unexpected type found : ?
required: class or interface without bounds
public class ErasureAndlnheritance
@SuppressWarnings ("unchecked" )
public static void main(String[] args)
Derived2 d2 = new Derived2{);
Object obj " d2.get();
d2.set(obj); 11 Advertencia aqui!
}
///,-
Derived2 hereda de GenericBase si n ningn parmetro genrico y el compilador no genera ninguna advertencia. La adver-
tencia no se genera hasta que se invoca set().
Para qe no aparezca la advertencia, Java proporciona una anotacin, que es la que podemos ver en el li stado (esta anota-
cin no estaba soportada en las versiones anteriores a Java SE5):
@SuppressWarni ngs (lunchecked")
Observe que esta anotacin se coloca en el mtodo que genera la advertencia, en lugar de en la clase completa. Es mejor
"enfocar" 10 mximo posible a la hora de desacti var una advertencia, para no ocultar accidentalmente un problema real al
desact ivar las advertencias en un contexto demasiado amplio.
Presumiblemente, el error producido por Derived3 indica que el compilador espera una clase base pura.
Aadamos a esto el esfuerzo adicional de gestionar los lmites cuando queramos tratar el parmetro de tipo como algo ms
que simplemente un objeto de tipo Object y la conclusin de todo ello es que hace falta un esfuerzo mucho mayor con unas
15 Genricos 421
ventajas mucho menores que cuando se utili zan tipos parametrizados en lenguajes tales como C++, Ada o Eiffel. Esto no
quiere decir que dichos lenguajes tengan en general ms ventajas que Java a la hora de abordar la mayora de los problemas
de programacin, sino simplemente que sus mecanismos de tipos parametrizados son ms flexibles y potentes que los de
Java.
El efecto de los lmites
Debido al mecanismo de borrado de tipos, el aspecto ms confuso de los genricos es el hecho de que podemos representar
cosas que no tienen ningn significado. Por ejemplo:
1/ : generics/ArrayMaker.java
import java.lang.reflect.*
import java.util.*;
public class ArrayMaker<T>
private Class<T> kind
public ArrayMaker (Class<T> kind) { this. kind
@SuppressWarnings ("unchecked")
T [] create (int size) {
return (T[] )Array.newlnstance(kind, size)
public static void main(String[] argsl
ArrayMaker<String> stringMaker :
new ArrayMaker<String> (String .class ) i
kind; }
String(] stringArray = stringMaker.create(9)
System.out.println(Arrays.toString(stringArray) i
1* Output:
[null, null, null, null, null, null, nul1, null, null]
. /// ,-
Aunque kind se almacena corno Class<T>, el mecani smo de borrado de tipos signifi ca que en realidad se est almacenan-
do simplemente como un objeto Class si n ningn parmetro. Por tanto, cuando hacemos algo con ese objeto, como por
ejemplo crear una matri z, Array.ncwInstance( ) no di spone en la prcti ca de la in[oflnacin de tipos que est implcita en
kind; como consecuencia, no puede producir el resultado especfi co, lo que obli ga a real izar una proyeccin de tipo que
genera una advertencia que 110 se puede corregir.
Observe que la util izacin de Array.newlnstance() es la tcnica recomendada para la creacin de matrices dentro de gen-
neos.
Si creamos un contenedor en lugar de una matriz, las cosas son di stintas:
11 : generics/ListMaker.java
import java.util.*
public class ListMaker<T>
List<T> crea te () { return new ArrayList<T> (); }
public static void main (String [] args) (
ListMaker<String> stringMaker= new ListMaker<String>();
List<String> stringList = stringMaker.create{);
El compi lador 110 genera ninguna advert encia, an cuando sabemos perfectamente (debido al mecanismo de borrado de
tipos) que la <T> en ncw ArrayList<T>() dentro de create() se elimina: en tiempo de ejecucin no hay ninguna <T> den-
tro de la clase, por lo que parece que no tiene ningn signifi cado. Pero si hacemos caso de esta idea y cambiamos la expre-
sin a ne\\' ArrayList( ), el compilador generar una advertencia.
Carece realmente de signifi cado en este caso? Qu pasara si pusiramos algunos obj etos en la li sta antes de devolverla,
Como en el siguiente ejemplo?:
422 Piensa en Java
11 : generics/FilledListMaker.java
import java.util. *;
public class FilledListMaker<T>
List<T> create (T t, int n ) {
List<T> result = new ArrayList<T> () i
for (int i = o; i < n; i++ )
result.add (t) ;
return result;
public static void main (String(] args )
FilledListMaker<String> stringMaker =
new FilledListMaker<String>{);
List<String> list = stringMaker.create {"Hello", 4) i
System.out.println(list) ;
1* Output:
[HelIo, HelIo, HelIo, HelIo]
* ///,-
Aunque el compilador es incapaz de tener ninguna informacin acerca de T en create(), sigue pudiendo garantizar (en tiem-
po de compilacin) que lo que pongamos dentro de result es de tipo T, de modo que concuerde con ArrayList<T>. Por
tanto, an cuando el mecanismo de borrado de tipos elimine la informacin acerca del tipo real dentro de un mtodo o de
una clase, el compilador sigue pudiendo garanti zar la coherencia interna en lo que respecta a la forma en que se utili za el
tipo dentro del mtodo o de la clase.
Puesto que el mecanismo de borrado de tipos elimina la informacin de tipos en el cuerpo de un mtodo, lo que importa en
tiempo de ejecucin son los limites: los puntos en los que los objetos entran y salen de un mtodo. Estos son los puntos
en los que el compilador realiza las comprobaci ones de tipos en tiempo de compilacin e inserta cdigo de proyeccin de
tipos. Considere el siguiente ejemplo no genrico:
11: generics/simpleHolder.java
public class SimpleHolder {
private Object obj
public void set(Object obj) { this.obj
public Obj ect get () { return obj; }
public static void main(String[] args)
obj; }
SimpleHolder holder = new SimpleHolder();
holder.set("Item" ) ;
String s = (String)holder.get();
Si descompilamos el resultado con javap -c SimpleHolder, obtenemos (despus de editar la salida):
public void set{java.lang.Objectl i
o: aload O
1: aload 1
2, putfield #2; l/Campo obj ,Object;
5: return
public java .lang. Object get () i
O: aload_O
public
0 ,
3,
4,
getfield #2; l/Campo obj ,Object;
areturn
static void main{java.lang.String[]);
new #3; l/Clase SimpleHolder
dup
invokespecial #4 II Mtodo "<init>": () V
15 Genricos 423
7: astare 1
8: aload 1
9, ldc #5; I/ String Item
11: invokevirtual #6; JI Mtodo set: {Object;)V
14: aload 1
15: invokevirtual #7; // Mtodo get: () Object
18: checkcast #8; j jclass java/ langjString
21: astore_2
22: return
Los mtodos sel( ) y get( ) si mpl emente almacenan y producen el valor, y la preci sin de tipos se comprueba en el lugar
donde se produce la ll amada a gel( ).
Ahora vamos a incorporar genri cos al cdigo anterior:
JI : generics / GenericHolder.java
public class GenericHolder<T> {
private T obj;
public void set (T obj) { this. obj = obj;
public T get () { return obj; }
public static void main (String [] args) {
GenericHolder<String> holder =
new GenericHolder<String>() i
holder. set (" Item") i
String s = holder.get ( );
La necesidad de efectuar una proyeccin de tipos para get( ) ha desaparecido, pero tambin sabemos que el valor pasado a
set() sufre una comprobacin de tipos en tiempo de compilacin. He aqu el cdigo intermedio relevante:
public veid set (java.lang.Object l i
O; alead O
1; alead 1
2, putfield #2; l/ Campo obj,Objeet;
5; return
public java.lang.Object get () ;
O: alead O
public
0,
3,
4,
7,
B,
9,
11,
14,
15,
lB,
2l,
22,
getfield #2; l/ Campo obj ,Object;
areturn
static veid main(java.lang.String[]) i
new #3; l/Clase GenericHolder
dup
invokespecial #4; l/Mtodo "cinit>": ()V
astore 1
aload 1
lde #5; I/ String Item
invokevirtual #6; // Mtodo set: (Object; ) V
aload 1
invokevirtual #7; // Mtodo get: ()Object;
checkcast #8; //class java/lang/String
astore_ 2
return
El cdigo resultante es idntico. El trabajo adicional de comprobar el tipo entrante en set( ) es nulo, ya que es el compila-
dor quien se encarga de reali zarlo. Y la proyeccin de tipos para el valor saliente de get() sigue estando ah, pero ahora no
tenemos que bacerlo nosotros explcitamente; el compilador se encarga de insertar esa proyeccin automticamente, de
modo que el cdigo que escribamos (y que tengamos que leer) estar mucho ms libre de "ruido".
424 Piensa en Java
Puesto que get() y set() generan el mi smo cdi go intennedi o, toda la accin referida a los genricos ti ene lugar en los lmi -
tes: en concreto, se trata de la comprobacin adicional en ti empo de compil acin para los val ores entrantes y de la proyec-
cin de tipos que se inserta para los valores sali entes. Para contrarrestar la confusin en lo que respecta al tema del
mecani smo de borrado de tipos, recuerde siempre que "los lmites son los lugares en los que la accin se produce".
Compensacin del borrado de tipos
Como hemos visto, el borrado de tipos hace que perdamos la posibilidad de realj zar ciertas operaciones en el cdi go gen-
ri co. En concreto, no funci onar ninguna cosa que requi era conocer el tipo exacto en tiempo de ejecuci n:
11 : generics / Erased.java
II {CompileTimeError} (no se compilar )
public class Erased<T> {
private final int SIZE 100;
public stat i c void f (Object arg )
if (arg instanceof T) {}
T var = new T () ;
T(] array new T (SIZE) i
T () array = (T) new Obj ect [SIZE) i
}
111 ,-
II Error
/1 Error
I I Error
1/ Advertencia de no comprobacin
Ocasionalmente, podemos sol ventar mediante programa estos problemas, pero en ocasiones nos vemos forzados a compen-
sar el mecani smo de borrado de tipos introduciendo lo que se denomina un marcador de tipos. Esto quiere decir que pasa-
mos explcitamente el objeto Class correspondi ente a nuestro tipo para poder utilizarlo en expresiones donde los tipos entran
en Juego.
Por ejemplo, el intento de utilizar instanceof en el programa anterior falla porque la informacin de tipos ha sido borrada.
Si introducimos un marcador de tipos, podemos utili zar en su lugar un mtodo islnstance() dinmi co:
11 : generics / classTypeCapture . java
class Building {}
class House extends Building {}
public class Cl assTypeCapture<T>
Class<T> kind
publ ic ClassTypeCapture (Class<T> kind) {
this . kind = kind
public boolean f (Objec t a r g )
r eturn kind. islnst ance (arg ) ;
public static void main {String(] args)
ClassTypeCapture<Building> ctt1 =
new ClassTypeCapture<Bu ilding> (Building.class ) i
System.out . println (ctt1.f (new Building ())) i
System.out . println (ctt1.f {new House () );
ClassTypeCapture<House> ctt2 =
new ClassTypeCapture<House>(House.class )
System.out . println (ctt2.f (new Building ( ))
System.out . println (ctt2 . f (new House ( )) ;
1* Output :
true
true
false
true
*111 , -
15 Genricos 425
El compil ador garanti za que el marcador de tipos se corresponda con el argumento genri co.
Ejerc icio 21 : (4) Modifique ClassTypeCapture.java aadiendo un contenedor Map<String,Class<?, un mtodo
addType(String typename, Class<?> kind) y un mtodo createNew(String typename). createNew( )
generar una nueva instancia de la clase asociada con la cadena de caracteres que se le proporcione como
argumento, o producir un mensaj e de error.
Creacin de instancias de tipos
El intento de crear un objeto con new T( ) en Erased.j ava no fu nciona, en parte debido al mecanismo de borrado de tipos
y en parte porque el compilador no puede verificar que T tenga un constructor predeterminado (sin argumentos). Pero en
e H esta operaci n es natural, sencill a y segura (se compmeba en tiempo de compilacin):
ji : generics!InstantiaceGenericType.cpp
// C++, no Java!
template<c l ass T> class Foo
T X i JI Crear un campo de tipo T
T* y / / Puntero a T
public :
// Inicializar e l puntero :
Foo l) ( y = new TI) ; J
J ;
class Bar (J;
int main ()
Foo<Bar> tb
Foo<int> fi // . . . y funciona con primi t ivas
// / >
La solucin en Java consiste en pasar un objeto factora y utili zarlo para crear la nueva instancia. Un objeto factora muy
adecuado es el propio objet Class, por lo que si uti lizamos un marcador de tipos, podemos emplear newInstance( ) para
crear un nuevo objeto de dicho tipo:
// : generics / InstantiateGenericType . java
import s t atic net.mindvi ew. util.Print .*
c lass ClassAsFactory<T> {
T x
public ClassAsFactory (Class<T> kindl {
try (
x = kind.newInstance () ;
cat ch (Except i on e l {
throw new RuntimeException (e ) i
class Employee (J
public class InstantiateGenericType
publ ic static void main (String[] args )
ClassAs Factory<Employee> fe =
new ClassAsFactory<Employee> (Employee . class ) ;
pr int ( "ClassAsFactory<Employee> succeeded" )
try (
ClassAsFactory<Integer> ti =
new ClassAsFactory<Integer> (Integer.class )
426 Piensa en Java
catch ( Exception e) (
print(UClassAsFactory<Integer> failed" ) ;
J* Output:
ClassAsFactory<Employee> succeeded
ClassAsFactory<Integer> failed
* /// ,-
Este ejemplo se puede compilar, pero falla si utilizamos ClassAsFactory<lnteger> porque Integer no dispone de ningn
constmctor predetemlinado. Como el error no se detecta en tiempo de compilacin, la gente de Sun desaconseja utilizar esta
tcnica. Lo que sugieren, en su lugar, es que se utilice una factora explicita y que se restrinja el tipo de modo que slo admi-
ta una clase que implemente dicha factora:
JJ : generics/FactoryConstraint.java
interface FactoryI<T>
T create ()
class Foo2<T> {
private T x
public <F extends FactoryI<T Foo2(F factory ) {
x = factory.create() i
}
/ / ...
class IntegerFactory implements FactoryI<Integer> {
public Integer create () {
return new Integer {O) i
class Widget {
public static class Factory implements FactoryI<Widget> {
public Widget create ()
return new Widget( )
public class FactoryConstraint {
public static void main (String[] args )
new Foo2<Integer> (new IntegerFactory ())
new Foo2<Widget> (new Widget.Factory ())
}
/// ,-
Observe que esto no es ms que una variacin del hecho de pasar C1ass<T>. Ambas tcnicas pasan objetos factora; pero
Class<f> resulta ser el objeto factora predefinido, mientras que en el ejemplo anterior creamos un objeto factora explci-
to. Lo importante es que conseguimos que se realice una comprobacin en tiempo de compilacin.
Otra tcnica consiste en utilizar el patrn de diseo basado en plantillas. En el siguiente ejemplo, get( ) es el mtodo plan-
tilla y create( ) se define en la subclase para generar un objeto de dicho tipo:
JJ: generics/CreatorGeneric.java
abstract class GenericWithCreate<T>
final T element
GenericWi thCreate () { element = create () ;
abstract T create() i
class X {}
class Creator extends GenericWithCreate<X>
X create() { return new XI); }
void f () {
System.out.println(element.getClass() .getSimpleName ());
public class CreatorGeneric {
public static void main(String[] args)
Creator e = new Creator() i
c. f () ;
/ * Output:
15 Genricos 427
Ejercicio 22: (6) Utilice un marcador de tipos junto con el mecalli smo de reflexin para crear un mtodo que emplee la
versin con argumentos de newlnstance() con el fin de crear un objeto de una clase con un constructor
que tenga argumentos.
Ejercicio 23: (1) Modifique FactoryConstraint.java para que create() admita un argumento.
Ejercicio 24: (3) Modifique el Ejercicio 21 para que los objetos factora se almacenen en el mapa en lugar de en
Class<?>.
Matrices de genricos
Como hemos visto en Erased.java, no se pueden crear matrices de genricos. La solucin consiste en utili zar un contene-
dor ArrayList en lodos aquellos lugares donde necesitemos crear una matriz de genricos:
// : generics/ListOfGenerics.java
import java.util.*;
public class ListOfGenerics<T>
private List<T> array = new ArrayList<T>( )
public void add (T item) { array . add(item); }
public T get (int indexl { return array.get(index);
/1/ , -
Aqu obtenernos el comportamiento de una matriz, sin renunciar por ello a las comprobaciones de tipos en tiempo de com-
pilacin que los genricos penniten.
En ocasiones, seguiremos necesitando crear una matriz de tipos genricos (ArrayList, por ejemplo, utiliza matrices inter-
namente). Resulta interesante saber que podemos definir una referencia de una fonna tal que haga que el compi lador no se
queje. Por ejemplo:
// : generics/ArrayOfGenericReference.java
class Generic<T> {}
public class ArrayOfGenericReference
static Genericclnteger>[] gia
} /1/ ,-
El compilador acepta esta sintaxis sin generar ninguna advertenc ia. Pero no podemos crear nunca una matriz de ese tipo
exacto (incluyendo los parmetros de tipo), por lo que la cuestin resulta algo confusa. Puesto que todas las matrices tienen
428 Piensa en Java
la misma estrucnlra (tamao de cada posicin de la matriz y di sposicin de la matri z) independientemente del tipo que alma-
cene, parece que deberamos poder crear una matriz de objetos Object y proyectarla sobre el tipo de matriz deseado. De
hecho, esta solucin podr compilarse, pero no se podr ejecutar, ya que se generar una excepcin ClassCastException:
JI: generics/ArrayOfGeneric.java
public class ArrayOfGeneric
static final int SIZE = 100;
static Generic<Integer>(] gia
@SuppressWarnings("unchecked
ll
)
public static void main(String(] args)
1/ Se compila; genera ClassCastException:
JI! gia : (Gene ric<Integer>[] )new Object[SIZE] i
/1 El tipo en tiempo de ejecucin es el raw (despus del borrado):
gia = (Generic<Integer>[] )new Generic(SIZE]
System.out.println(gia.getClass() .getSimpleName {));
gia[oJ :::: new Generic<Integer>();
II! gia[lJ = new Object() II Error de tiempo de compilacin
II Descubre la discordancia de tipos en tiempo de compilacin:
II! gia [2] = new Generic<Double> ();
1* Output:
Generic []
'j j j>
El problema es que las matrices controlan su tipo real y ese tipo se establece en el momento de creacin de la matriz. Por
tanto, aunque gia haya si do proyectado sobre Generic<lnteger>[], dicha infonnaci n slo existe en tiempo de compilacin
(y sin la anotacin @SuppressWarning., obtendremos Ulla advertencia debido a dicha proyeccin). En tiempo de ejecu-
cin, sigue siendo Ulla matriz de tipo Object, yeso hace que se produzcan problemas. La nica forma de crear adecuada-
mente una matriz de un tipo genrico es crear una nueva matriz del tipo resultante del borrado de tipos y efectuar una
proyeccin de tipos con dicha matriz.
Veamos un ejemplo algo ms sofisticado. Considere un envoltorio genrico simple para una matri z:
11: generics/GenericArray.java
public class GenericArray<T> {
private T[] arraYi
@SuppressWarnings ( "unchecked")
public GenericArray (int SZ) {
array = (T []) new Object [sz) ;
public void put(int index, T item)
array[index] = item;
public T get{int index) { return array[index] i }
II Mtodo que expone la representacin subyacente:
public T [1 rep () { return array;
public static void main{String(] args)
GenericArray<Integer> gai =
new GenericArray<Integer> (lO ) ;
JI Esto provoca una excepcin ClassCastException:
II! Integer [1 ia :::: gai. rep () i
II Esto es correcto:
Objectl] ca = gai.rep();
}
jjj ,-
Como antes, no podemos decir TII array new TI'zJ, por lo que creamos una matri z de objetos y la proyectamos.
15 Genricos 429
El mtodo rep() devuelve una matriz TII , que en main( ) debe ser una matriz (ntegerll para gai, pero si llamamos a ese
mtodo y tratamos de capturar el resultado como una referencia a Integerll . obtenemos una excepcin
ClassCastException, de nuevo debido a que el tipo real en tiempo de ejecucin es Objectll .
Si compilamos GenericArray.java despus de desactivar mediante comentarios la anotacin @SuppressWarnings, el
compi lador genera una advertencia:
Note: GenericArray.java uses unchecked or unsafe operat ions .
Note: Recompile with -Xlint:unchecked for details.
En este caso, hemos obtenido una nica advertencia y parece que se refiere a la operacin de proyeccin de tipos. Pero si
queremos aseguramos, debemos realizar la compilacin con -Xlint:unchecked:
Gener i cArray.java : 7 : warning : [unchecked) unchecked cast
found : java . lang . Object(]
required : T[J
array (T [] ) new Object (sz] ;
1 warning
Ciertamente, el compilador se est quejando sobre la proyeccin de tipos. Puesto que las advertencias introducen ruido den-
tro del proceso de diseo, lo mejor que podemos hacer, una vez que verifiquemos que se produce una advertencia, es de-
sactivarla utilizando @SuppressWarnings. De esa forma, cuando aparezca alguna otra advertencia podremos investigarla
adecuadamente.
Debido al mecanismo de borrado de tipos, el tipo en tiempo de ejecucin de la matriz slo puede ser Objectll . Si lo pro-
yectamos inmediatamente sobre TI J, entonces el tipo real de la matriz se perder en tiempo de compilacin y el compil ador
podra no aplicar algunas comprobaciones de potenciales errores. Debido a esto, es mejor utilizar una matriz Object ll den-
tro de la coleccin y aadir una proyeccin a T cuando la usemos como elemento de la matriz. Veamos cmo se apl icara
esta solucin con el ejemplo GenericArray.java:
11 : generics/GenericArray2.java
public class GenericAr ray2<T> {
private Object[] arraYi
public GenericArray2(int sz)
array = new Object[sz] i
public void put (int index, T item) {
array[index] = item;
@SuppressWarnings ( "unchecked")
pUblic T get(int index) { return (T)array[index] i }
@SuppressWarnings ("unchecked 11)
public T [1 rep [) (
return (T[])array; II Advertencia: proyeccin no comprobada
public static void main{String[] args)
GenericArray2<Integer> gai =
new GenericArray2<Integer> (10 ) ;
fer (int i = O; i < 10 i i ++)
gai.putU, i ) i
fer (int i :; O; i < 10; i ++ )
System.out .print(gai . get(i) + " " ) i
System.out.println() i
try (
Integer [] ia = gai . rep () ;
} catch(Exceptien e) { System.out.println(e);
1* Output : (Sample )
0 12 3 4 5 678 9
430 Piensa en Java
java.lang.ClassCastException: (Ljava.lang.Object cannot be cast to [Ljava.lang.lnteger
* ///,-
Inicialmente, parece que las cosas no son muy distintas, salvo por el hecho de que hemos desplazado de lugar la proyeccin
de tipos. Sin las anotaciones @SuppressWarnings, seguimos obteniendo advertencias que nos dicen que faltan comproba-
ciones. Sin embargo, la representacin interna es ahora Objectll en lugar de TII . Cuando invocamos get(), se efecta la
proyeccin del objeto sobre T, que es de hecho el tipo correcto, por lo que la operacin es segura. Sin embargo, si invoca-
mos rep(), el mtodo vuelve a intentar proyectar Objectll sobre TI!' lo que sigue siendo incorrecto y genera una adverten-
cia en tiempo de compilacin y una excepcin en tiempo de ejecucin. Por tanto, no bay ninguna manera de cambiar el tipo
de la matriz subyacente, que slo puede ser Objectll . La ventaja de tratar array internamente como Objectll en lugar de
como T[J es que resulta mellas probable que nos olvi demos del tipo de la matriz en tiempo de ejecucin e introduzcamos
accidentalmente un error (aunque la mayora de esos errores. y quiz todos, se detectaran rpidamente en tiempo de ejecu-
cin).
Para el nuevo cdigo que desarrollemos, lo que debemos hacer es pasar un testigo de tipos. En dicho caso, GenericArray
tendra el aspecto siguiente:
1/ : generics/GenericArrayWithTypeToken.java
import java.lang.reflect.*
public class GenericArrayWithTypeToken<T>
private T(] arraYi
@SuppressWarnings (lunchecked")
public GenericArrayWithTypeToken (Class<T> type, int sz) {
array = (T[] ) Array.newlnstance (type, sz);
public void put(int index, T item)
array[index] = item;
public T get (int index) { return array [index] ;
II Exponer la representacin subyacente:
public T [] rep () { return array i }
public static void main(String[] args)
GenericArrayWithTypeToken<Integer> gai
new GenericArrayWithTypeToken<Integer>(
Integer.class, la)
II Esto ahora funciona:
Integer [] ia = gai. rep () i
El testigo de tipos Class<T> se pasa al constructor para compensar el mecanismo de borrado de tipos, con el fin de poder
crear el tipo real de matriz que necesitemos, aunque el mensaje de advertencia referido a la proyeccin de tipos deber ser
suprimido mediante @SuppressWarnings. Una vez que obtengamos el tipo real, podemos devolverlo y obtener los resul-
tados deseados como podemos ver en main(). El tipo de la matriz en tiempo de ejecucin es el tipo exacto TII.
Lamentablemente, si examinamos el cdigo fuente en las bibliotecas estndar de Java SES, podremos ver que existen por
todas partes proyecciones de matrices de tipo Object a tipos parametrizados. Por ejemplo, he aqu el constructor Arra)' List
que utiliza como argumento un contenedor Collection, despus de simplificar y limpiar el cdigo un poco:
public ArrayList (Collection e) {
size = c.size ()
elementData = (E[] )new Object(size];
c .toArray(elementData)
Si examina el cdigo de ArrayLisLjava, podr encontrar multitud de estas proyecciones. Y qu es lo que sucede cuando
compilamos este cdigo?
Note: ArrayList.java uses unchecked or unsafe operations .
Note: Recompile with -Xlint:unchecked for details.
15 Genricos 431
Como puede ver, las bibliotecas estndar generan una multitud de advertencias. Si el lector ha trabajado antes con e, yespe-
cialmente con el e anterior al estndar ANSI, recordar un efecto muy concreto de las advertencias: en el momento en que
descubrimos que las podemos ignorar, las ignoramos completamente. Por esa razn, lo mejor es que intentemos que el com-
pilador no genere ningn tipo de mensaje, a menos que el programador deba hacer algo con ese mensaje.
En su bitcora web,3 Neal Gafter (uno de los principales desarrolladores de Java SES) apunta que le daba bastante pereza
reescribir las bibliotecas Java, y que los buenos programadores no deben imitar lo que l hizo. Neal tambin seala que le
hubiera resultado imposible corregir parte del cdigo de la biblioteca Java sin modificar la interfaz existente. Por tanto, aun-
que en los archivos de cdigo fuente de la biblioteca Java aparezcan ciertas tcnicas, eso no quiere decir que esa sea la fonna
correcta de bacer las cosas. Cuando examine el cdigo de biblioteca no de por sentado que se trate de un ejemplo que haya
de seguir en su propio cdigo.
Lmites
Ya hemos presentado brevemente los lmites anterionnente en el captulo. Los lmites nos pernliten imponer restricciones a
los ti pos de parmetros que pueden utilizarse con los genricos. Aunque esto nos pennite imponer reglas acerca de los tipos
a los que pueden aplicarse los genricos, un efecto quiz ms importante es que podemos invocar mtodos pertenecientes a
los tipos definidos como lmite.
Puesto que el mecanismo de borrado de tipos elimina la infonnacin de tipos, los nicos mtodos que podemos invocar para
un parmetro genrico al que no se le hayan impuesto lmit es son aquellos di sponibl es para Object. Sin embargo, si somos
capaces de restringir el parmetro para que se corresponda con un subconj unto de tipos, entonces podemos invocar todos
los mtodos de dicho subconjunto. Para implementar esta restriccin, el mecanismo de genricos en Java utili za la palabra
clave extends. Es importante comprender que extends tiene un signifi cado bastante distinto al normal dentro del contexto
de los lmites de genricos. El siguient e ejemplo ilustra los fundamentos bsicos de los mecani smos de lmites:
11 : generics/BasicBounds.java
interface HasColor { java. awt. Color getColor (); }
class Colored<T extends HasColor> {
T item;
Colored(T item) { this.item -::o item;
T getltem() { return item; }
II El lmite nos permite invocar un mtodo:
java . awt. Color color () { return i tem. getColor () ;
class Dimension { public int x, y, Z; }
II Esto no funcionar -- primero hay que definir la clase
II y luego las interfaces:
II class ColoredDimension<T extends HasColor & Dimension>
JI Mltiples lmites:
class ColoredDimension<T extends Dimension & HasColor>
T item;
ColoredDimension(T item) { this.item = item; }
T getltem () { return i tem; }
java.awt.Color color() { return item.getColor();
int getX() { return item.x; }
int gety() { return item.y; }
int getZ () { return i tem. Z i }
3 hllp://gafter.hlogspol.com/!004/09/pll==/ing-lhrough-erasure-answer.hlml
432 Piensa en Java
interface Weight ( int weight();
JI Al igual que con la herencia, slo se puede tener una
JI clase concreta, pero puede haber mltiples interfaces:
cIass Solid<T extends Dimension & HasColor & Weight> (
T item;
Solid(T item) { this.item = item; }
T getltem () { return itero; }
java. awt . Color color () { return item. getColor (l ;
int getX () { return item.x; }
int gety() { return item.y; }
int getZ() { return item.z; }
int weight () ( return itero. weight () ;
cIass Bounded
extends Dimension implements HasColor, Weight (
public java. awt. Color getColor () { return null i
public int weight () { return O; }
public cIass BasicBounds {
public statie void main(String[] args)
Solid<Bounded> solid =
new Solid<Bounded>(new Bounded());
salid.color () ;
salid.gety{) ;
salid. weight () ;
}
/// , -
Habr observado que BasicBounds.java parece contener redundancias que podran eliminarse recurriendo al mecanismo de
herencia. En el siguiente ejemplo, podemos ver cmo cada ni vel de herencia aade tambin restricciones de lmite:
11: generics/lnheritBounds.java
class Holdltem<T> {
T item;
Holdltem(T item) ( this.item
T getltem() ( return item; )
item; }
class Colared2<T extends HasColor> extends Holdltem<T>
Colored2 (T i tem) { super (i tem) ;
java . awt . Color color () { return item. getColor (); }
class ColoredDimension2<T extends Dimension & HasColor>
extends Colored2<T> (
ColoredDimension2(T item) { super (item) ;
int getX () return i tem. x; }
int getY() return item.y; }
int getZ() return item. Z; }
class Solid2<T extends Dimension & HasColor & Weight>
extends ColoredDimension2<T> {
Solid2 (T item) { super{item);
int weight () { return i tem. weight () ;
public class InheritBounds {
public static voi d ma i n{Stri ng(] args) {
Solid2<Bounded> solid2 =
new Solid2<Bounded>{new Bounded()) i
solid2 . color () ;
solid2. gety () ;
solid2 . weight() i
)
111>
15 Genricos 433
Holdltem simpl emente almacena un objeto, por lo que este comportamiento es heredado dentro de Colored2, que requie-
re que tambin su parmetro se corresponda con HasColoT. ColoredDimension2 y Solid2 exti enden todava ms la jerar-
qua y aaden lmites en cada ni vel. Ahora, los mtodos son heredados y no tienen por qu repetirse en cada clase.
He aqu un ejemplo con ms niveles:
jI: generics/EpicBattle . java
// Ejemplo de lmites para genricos de Java .
import java . util .* ;
interface SuperPower {}
interface XRayVision extends SuperPower {
void seeThr oughWalls{);
interface SuperHearing extends SuperPower {
voi d hearSubtleNoises();
interface SuperSmell extends SuperPower {
void trackBySmell();
class SuperHero<POWER extends SuperPower> {
POWER power
SuperHero(POWER power) { this.power = power
POWER getPower() { return power; }
class SuperSleuth<POWER extends XRayVision>
extends Super Hero<POWER> {
SuperSleuth(POWER power) { super(power)
void see () { power . seeThroughWalls () }
class CanineHero<POWER extends SuperHearing & SuperSmell>
extends SuperHero<POWER> {
Cani neHero(POWER power) { super (power)
void hear () { power. hearSubtleNoises () ;
void srnell () ( power . trackBySrnell () ; )
class SuperHearSmell implements SuperHearing, SuperSmell {
public void hearSubtleNoises () {}
public void tra ckBySrnell () ()
class DogBay extends CanineHero<SuperHearSmell>
DogBoy () ( super (new SuperHearSrnell () ); )
public class EpicBattle {
434 Piensa en Java
II Limites en mtodos genricos:
static <POWER extends SuperHearing>
void useSuperHearing (SuperHero<POWER> hero) {
hero.getPower() .hearSubtleNoises();
static <POWER extends SuperHearing & SuperSmell>
void superFind(SuperHero<POWER> hero) {
hero.getPower{) .hearSubtleNoises{);
hero.getPower() . trackBySmell{);
public static void main(String[] args)
DogBoy dogBoy = new DogBoy();
useSuperHearing(dogBoy) ;
superFind(dogBoyl;
II Podemos hacer esto:
List<? extends SuperHearing> audioBoys;
II Pero no podemos hacer esto :
II List<? extends SuperHearing & SuperSmell> dogBoys;
Observe que los comodines (de los que hablaremos a continuacin) estn limitados a un nico lmite.
Ejercicio 25: (2) Cree dos interfaces y una clase que implemente ambas. Cree dos mtodos genricos, uno cuyo argu-
mento de parmetro est limitado por la primera interfaz y otro cuyo argumento de parmetro est limita-
do por la segunda interfaz. Cree una instancia de la clase que implementa ambas interfaces y demuestre
que se puede utilizar con ambos mtodos genricos.
Comodines
Ya hemos visto algunos usos si mpl es de los comodines (smbolos de interrogacin dentro de las expresiones de argumentos
genricos) en el Captulo 11 , Almacenamiento de objetos y en el Captulo 14. Informacin de tipos. En esta seccin vamos
a explorar esta cuestin con ms detall e.
Empezaremos con un ejemplo que demuestra un comportami ento concreto de las matrices. Podemos asignar una matri z de
un tipo derivado a una referencia de matri z del tipo base:
11 : generics/CovariantArrays . java
class Frui t {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}
public class CovariantArrays {
public static void main(String[] args) {
Fruit(] fruit = new Apple[lO];
fruit[O] = new Apple(); // OK
fruit[l] = new Jonathan(); //OK
II El tipo en tiempo de ejecucin es Apple (], no Fruit {l ni Orange {] :
try {
II El compilador permite aadir Fruit:
fruit(O] = new Fruit(); II ArrayStoreException
} catch(Exception el { System.out . println(e); }
try {
II El compilador permite aadir Oranges:
fruit [O] = new Orange (); II ArrayStoreException
catch(Exception el { System.out.println(e) i }
} 1* Output,
java.lang.ArrayStoreException: Frui t
j ava.lang . ArrayStoreException: Orange
*111 ,-
15 Genricos 435
La primera lnea de main() crea una matriz de objetos Apple y la asigna una referencia a una matri z de objetos Fruit. Esto
ti ene bastante sentido, ya que Apple es un tipo de Fruit, por lo que una matriz de Apple tiene que ser una matriz de Fruit.
Sin embargo, si el tipo real de la matriz es Apple(J, slo deberamos poder insertar en la matriz un objeto Apple o un sub-
tipo de Apple. lo que de hecho funciona tanto en tiempo de compil acin como en tiempo de ejecucin. Pero observe que el
compilador nos pemlite insertar un objeto Fruit dentro de la matri z. Esto tiene sentido para el compilador, porque dispone
de una referencia a Fruitll ; como dispone de esa referencia, por qu no debera pennitir colocar en la matriz un obj eto
Fruit. o cualquier cosa que descienda de Fruit, como por ejemplo Orange? Por tanto. la operacin se permite en tiempo
de compi lacin. Sin embargo, el mecanismo de tiempo de ejecuci n para las matri ces sabe que est tratando con una matriz
Applell y genera una excepcin cuando se inserta un tipo incorrecto de la matriz.
El tm1ino "generalizacin" resulta confuso dentro de este contexto. Lo que estamos realmente haciendo es asignar una
matriz a otra. El comportamiento de las matrices es tal que pennite almacenar otros objetos, pero como podemos reali zar
una generalizacin, resulta claro que los objetos matri z pueden preservar las reglas acerca del tipo de objetos que conti enen.
Es como si las matri ces fueran conscientes de qu es lo que estn almacenando, por lo que entre las comprobaciones reali-
zadas en tiempo de compilacin y las reali zadas en tiempo de ejecucin, no podemos tratar de abusar del mecanismo de
matrices.
Esta fonna de comportarse de las matri ces no resulta tan terribl e, porque al final s que detectamos en tiempo de ejecucin
que hemos insertado un tipo incorrecto. Pero uno de los objeLivos principales de los genricos era precisamente mover esos
mecanismos de deteccin de errores a tiempo de compilacin. Por tanto, qu sucede si tratamos de utilizar contenedores
genricos en lugar de matrices?
11 : generics / NonCovariantGenerics.java
II {CompileTimeError} (Won I t compile )
i mport java.util.*
public class NonCovariantGenerics
II Error de compilacin: tipos incompatibles :
Li st<Frui t> flis t = new ArrayList<Apple >{) ;
111 >
Aunque pudi ramos sentimos tentados de concluir, a la vista de este ejemplo, que no se puede asignar a un contenedor de
objetos Apple a un contenedor de objetos Fruit, recuerde que los genricos no se refieren slo a los contenedores. Lo que
este ejemplo nos dice realmente es que no se puede asignar un genrico relacionado con objetos Apple a un genrico rela-
cionado con objetos Fruit. Si el compil ador, como sucede en el caso de las matrices, supi era lo suficiente acerca del cdi -
go como para detenninar que hay contenedores impli cados, quiz podra ser algo ms pennisivo. Pero el compil ador no sabe
que es as, por lo que rehusar pemlit ir la 'generalizacin". De todos modos, tampoco se trata de una "generalizacin rea''':
una lista de objetos Apple no es una li sta de objetos Fruit. Una lista de objetos Apple penllitir almacenar objetos Apple
y subtipos de Apple, mientras que una li sta de objetos Fruit permitir almacenar cualquier clase de objetos F'ruit. Es cier-
to que esto incluye a los objetos Apple, pero eso no la hace una li sta de objetos Apple; seguir siendo una li sta de objetos
Fruit. Una li sta de objetos Apple no es equivalente en lo que respecta a tipos a una lista de objetos Fruit, an cuando un
objeto Apple sea un tipo de objeto Fruit.
La cuestin real es que de lo que estamos hablando es del tipo de contenedor, no del tipo de los objetos que el contenedor
almacena. A diferencia de las matrices, los genricos no tienen mecanismo de covarianza integrado. Esto se debe a que las
matrices estn definidas completamente en el lenguaje y pueden incorporar, por tanto, comprobaciones tanto en tiempo de
compil acin como en tiempo de ejecucin; sin embargo, con los genri cos, el compi lador y el sistema de ejecucin no pue-
den saber lo que queremos hacer con los tipos y cules son las reglas que deberan apl icarse.
En ocasiones, sin embargo, puede que queramos establecer algn tipo de relacin de generalizacin entre los dos, y esto es,
precisamente, lo que los comodines penni ten.
11 : generics / Generi c sAndCovariance.java
import java.util.*
436 Piensa en Java
public class GenericsAndCovariance
public static void main(String[] args)
II Los comodines permiten la covarianza:
List<? extends Fruit> flist = new ArrayList<Apple>() i
II Error de compilacin: no se puede aadir cualquier tipo de objeto:
II flist.add(new Apple());
II flist.add(new Fruit());
II flist.add(new Object()) ;
flist . add{null); II Legal pero poco interesante
II Sabemos que devuelve al menos Fruit:
Fruit f = flist . get (O) ;
)
111 , -
El tipo de flist es ahora List<? extends Fruit>, lo cual puede leerse como "una lista de cualquier tipo que herede de Fruit".
Sin embargo, esto no significa que la li sta pueda almacenar cualquier tipo de objeto Fruit. El comodn hace referencia a un
tipo concreto, por lo que signifi ca "algn tipo espec fi co que la referencia flist no especifique". Por tanto, la li sta que se
asigne ti ene que estar almacenando algn tipo especificado como Fruit o Apple, aunque para poder realizar la generali za-
cin a rust, ese tipo se especifi ca como "no importa cul sea".
Si la ni ca restriccin es que la li sta almacene un tipo o subtipo de Fruit especfi co, pero no nos importa en realidad cul
sea ste, entonces qu es lo que podemos hacer con di cha li sta? Si no sabemos el tipo de obj eto que la li sta est almace-
nando, cmo podemos aadir con seguridad un objeto? Al igual que sucede con la "' generalizacin" de la matri z
CovariantArrays.java, no podemos aadir cualqui er obj eto que queramos, lo nico que sucede es que, en este caso, el com-
pilador prohbe las operaciones no pennitidas antes, en lugar de que sea el si stema de ejecucin el que se encargue de pro-
hibirlas. En otras palabras, esto nos pennite descubrir el problema bastante antes.
Podramos pensar que las cosas se han ido un poco de las manos, porque ahora ni siqui era podemos aadir un objeto Applc
a una li sta que habamos dicho que s poda almacenar objetos Apple. En efecto, as es, pero es que el compilador no tiene
ningn conocimiento de eso. Un contenedor List<? extends Fruit> puede apuntar a una li sta List<Orange>. Una vez que
hacemos este tipo de "generalizacin", perdemos la posibilidad de pasar ningn objeto, ni siqui era de tipo Object.
Por otro lado, si invocamos un mtodo que devuel va Fruit, esa operacin s es segura porque sabemos que cualquier cosa
que haya en la li sta deber ser al menos de tipo Fruit, por lo que el compilador pernutir la operacin.
Ejercicio 26: (2) Ilustre la covarianza de matri ces utili zando obj etos de tipo Number e Inleger.
Ejercicio 2'7: (2) Demuestre que la covari anza no funciona con las li stas utilizando objetos de tipo Number e Inleger.
y luego introduzca comodines.
Hasta qu punto es inteligente el compilador?
Ahora, podramos pensar que no podemos invocar ningn mtodo que admita argumentos, pero vamos a anali zar este ejem-
pl o:
11: generics/compilerlntelligence . java
import java.util. * ;
publ i c class Compilerlntelligence
public static void main{String[] args)
List<? extends Fruit> flist =
Arrays . asList(new Apple());
Apple a = (Apple) f list.get{O) i 1I Ninguna advertencia
flist.contains(new Apple{)} i II El argumento es ' Object'
flist . indexOf (new Apple()) i 1I El argumento es 'Object
'
)
111,-
Podemos ver ll amadas a conlains( ) e indexOf( ) que toman obj etos Apple como argumentos, y esas llamadas son perfec-
tamente vlidas. Signifi ca esto que el compilador si que examina el cdi go para ver si un mtodo concreto modifica su
obj eto?
15 Genricos 437
Examinando la documentacin de ArrayList, podemos comprobar que el compilador no es tan inteli gente. Mientras que
add( ) lOma un argumento del tipo de parmetro genrico, contains( ) e indexOf( ) toman argumentos de tipo Object. Por
tanto. cuando especificamos un contenedor ArrayList<? extcnds Fruit>. el argumento de add() se convierte en '? extends
Frui!' . A partir de dicha descripcin, el compilador no puede saber qu tipo especifico de Fruit se requiere, por lo que no
aceptar ningn tipo de Fruit. No importa si primero generalizamos el objeto Apple a Fruit: el compilador se negar a invo-
car un mtodo (como add( si hay un comodn en la li sta de argumentos.
Con contains( ) e indexOf( ), los argumentos son de tipo Object, por lo que no hay ningn comodn implicado y el com-
pilador s que pemlite la llamada. Esto significa que es responsabilidad del diseador de clases genricas definir qu clases
son "seguras" y utili zar el tipo Object para sus argumentos. Para prohibir una llamada cuando el tipo se utilice con como-
dines, utilice el parmerro de tipo dentro de la li sta de argumentos.
Podemos ilustrar esto con una clase Holder muy simple:
11 : generics/Holder.java
publc class Holder<T>
prvate T value
public Holder() ()
public Holder{T val) { value ::: val; }
public void set (T val) { value "" val;
public T get () { return value )
public boolean equals (Object obj) {
return val ue.equals(obj)
public static void main(String[] args) {
Holder<Apple> Apple = new Holder<Apple>{new Apple ());
Apple d = Apple.get();
Apple.set(d) ;
II Holder<Fruit> Fruit = Apple II No se puede generalizar
Holder<? extends Fruic> fruit = Apple 11 OK
Fruit p = fruit.get();
d = (Apple)fruit.get(); II Devuelve ' Object'
try {
Orange c = (Orangelfruit.get{) 11 Ninguna advertencia
} catch(Exception el { System.out.println(el }
11 fruit.set(new Apple() 11 No se puede invocar set()
11 fruit.set(new Fruit ()) 11 No se puede invocar set{)
System.out.println( fruit.equals(d)); II OK
/* Output: (Sample)
java.lang.ClassCastExCeption: Apple cannot be cast to Orange
true
*111,-
Holder tiene un mtodo set() que toma un argumento T, un mtodo get() que devuelve T, y un mlOdo equals() que toma
un argumento de tipo Object. Como ya hemos visto, si creamos un contenedor Holder<Apple>, no podemos generalizar-
lo a Holder<Fruit>, pero s que podemos generalizarlo a Holder<? extends Frui!>. Si invocamos get(), slo devuelve un
objeto Fruit, que es lo nico que el compilador sabe, teniendo en cuenta que el lmite que hemos hecho es "cualquier cosa
que extienda Fruif'. Si el programador tiene ms infonnacin acerca de los objetos implicados, puede efectuar una predic-
cin sobre un tipo espec fico de Fruit y no se generar ninguna advertencia, aunque correremos el riesgo de que se genere
una excepcin ClassCastException. El mtodo set( ) no funcionar ni con un objeto Apple ni con un objeto Fruit, porque
el argumento de set( ) es tambin "? Extends Fruit", lo que significa que puede ser cualquier cosa y el compi lador no puede
verificar que "cualquier cosa" sea segura en lo que a tipos respecta.
Sin embargo, el mtodo equals( ) funciona correctamente, porque toma un objeto Object en lugar de T como argumemo.
Por tanto, el compilador slo presta atencin a los tipos de objetos que se pasan a los mtodos y que se devuelven desde
stos. El compil ador no analiza el cdigo para ver si efectuamos ninguna escritura o lectura real.
438 Piensa en Java
Contravarianza
Tambin es posi ble proceder en sentido inverso y uti lizar comodines de superripo. Con esto, lo que expresamos es que el
comodn est limitado por cualquier clase base de una clase concreta, especifi cando <? super MyClass> o incluso utilizan-
do un parmetro de tipo: <? super T> (aunque no se puede dar un lmite de supertipo a un parmetro genrico, es decir, no
podemos escribir <T super MyClass. Esto nos pennite pasar con seguridad un objeto de un cierto a un tipo genrico. De
este modo, con los comodines de supertipo podemos escribir dentro de un contenedor de tipo Collection:
11 : generics/SuperTypeWildcards.java
import java.util.*
public class SuperTypeWildcards
static void writeTo(List<? super Apple:> apples) {
apples.add {new Apple {;
apples.add(new Jonathan()) i
II apples . add{new Fruit {; II Error
}
111 ,-
El argumento apples es una lista de algn tipo que es el tipo base de Apple; por tanto. sabemos que resulta seguro aadir
un objeto Apple o un subtipo de Apple. Sin embargo, puesto que el limite inferior es Apple, no sabemos si resulta seguro
aadir un objeto Fruit a dicha li sta, porque eso pennirira que la lista se abriera a la adicin de tipos disti ntos de Apple, lo
que violara la seguridad esttica de tipos.
Por tanto, podemos pensar en los lmites de subtipo y de supertipo en trnlinos de cmo se puede "escribir" (pasar a un mto-
do) en un tipo genrico y cmo se puede "leer" (devolver desde un mtodo) de un tipo genrico.
Los lmites de supertipo relajan las restricciones de 10 que podemos pasar a un mtodo:
JI : genericsJGenericWriting.java
import java . util .*
public class GenericWriting
static <T> void writeExact (List<T:> list, T item) {
list.add (it em) ;
static List<Apple:> apples = new ArrayList<Apple:> () i
static List<Fruit:> fruit = new ArrayList<Fruit>( ) i
static void fl () (
writeExact(apples, new Apple() i
JI writeExact (fruit, new Apple {) ; II Error:
IJ Tipos incompatibles: se encontr Fruit, se requiere Apple
static <T:> void
writeWithWildcard(List<? super T:> list, T item) {
list.add(item)
static void f2 () (
writeWithWildcard(apples, new Apple () i
writeWithWildcard(fruit, new Apple { ;
public static void main(String[] args) { fl () f2( ) }
111 ,-
El mtodo writeExacl() utiliza un tipo de parmetro exacto (comodines). En fl() podemos ver que esto funciona correc-
tamente, siempre y cuando nos limitemos a insertar un objeto Apple en un contenedor List<Apple>. Sin embargo.
writeExact() no permite insertar un objeto Apple dentro de un contenedor List<Fruit>, an cuando nosotros sepamos que
eso deberia ser posible.
En wrileWilhWildcard(), el argumento es ahora Lisl<? super T>, por lo que la lista almacena un tipo especifico deriva-
do de T; por tanto. resulta seguro pasar T o cualquier cosa que se derive T como argumento a los mtodos de la lista.
15 Genricos 439
podemos ver esto en f2( ), donde sigue siendo posible insertar un obj eto Apple en una li sta List<Apple>, como antes, pero
ahora resulta posible insertar un objeto Apple en una li sta List<Fruit>, tal como cabra esperar.
podramos reali zar este mismo tipo de anlisis como revisin del tema de la covari anza y de los comodines:
jI : generics/GenericReading . java
import java . util. * ;
public class GenericReading
static <T > T readExact (List<T> list) {
return l i st . get(Q);
static List<Apple> app les = Arrays . as List(new Apple());
static List<Fruit> fruit = Arrays.asList(new Fruit();
JI Un mtodo esttico se adapta a cada llaroma da:
static void fl () {
Apple a = readExact(apples);
Frui t f = readExact(fruit);
f = readExact(apples) i
1/ Sin embargo, s i tenemos una clase , su t ipo se
11 establece en el moment o de i nstanciarla :
static class ReadereT> {
T readExact(ListcT> list) ( return list . get(O);
sta tic void f2 () {
ReadercFruit> fruitReader = new ReadercFruit>();
Fruit f = fruitReader . readExact(fruit);
11 Fruit a = frui tReade r . readExact (apples) ; 11 Error :
11 readExact( ListcFruit no pue de
11 apl i carse a (ListcApple .
static class Cova r iantReadereT>
T readCovariant (Liste? extends T> list) {
return list.get(O)
static void f3 () {
CovariantReadercFruit> fruitReade r =
new CovariantReadercFruit>() i
Fruit f fruitReader.readCovariant(fruit);
Fruit a = fruitReader . readCovariant(apples) i
public static void main (String [] args) {
nll; f211; 0 11 ;
Como antes, el primer mtodo readExact( ) utili za el tipo concreto. Por tanto, si utilizamos el tipo concreto sin ningn
comodn, podemos escribir y leer de dicho tipo concreto en una lista. Adems, para el valor de retomo, el mtodo genrico
esttico readExact( ) "se adapta" efecti vamente a cada llamada a mt odo y devuelve un objeto Apple de una li sta
List<Apple> y un objeto Fruit de una lista List<Fruit>, como puede verse en fJ() . Por tanto, si podemos resol ver el pro-
blema con un mtodo genri co esttico, no necesitamos recurrir a la covari anza si nicamente nos estamos limitando a leer.
Sin embargo, si tenemos una cl ase genri ca, el parmetro se establece para la clase en el momento de crear una instancia de
dicha clase. Como podemos ver en f2(), la instanci a fruitReader puede leer un objeto Fruit de una li sta List<Fruit>, pues-
to que ese es su tipo concreto. Pero una lista List<Apple> tambin debera producir obj etos Fruit y el objeto fruitReader
no permite esto.
Para corregir el problema, el mtodo CovariantReader.readCovariant( ) lOma una li sta List<? extends T>, de modo
que resulta seguro leer un obj eto T de di cha li sta (sabemos que todo lo que hay en esa lista es cuando menos de tipo T,
440 Piensa en Java
y posiblemente algo derivado de T). En f3( ) podemos ver que ahora si es posible leer un objeto Fruit de una lista
List<Apple>.
Ejercicio 28: (4) Cree una clase genrica Genericl<T> con un nico mtodo que tome un argumento de tipo T. Cree
una segunda clase genrica Generic2<T> con un nico mtodo que devuelva un argumento de tipo T.
Escriba un mtodo genrico con un argumento contra variante de la primera clase genrica que invoque al
mtodo de dicha clase. Escriba un segundo mtodo con un argumento covanante de la segunda clase gen-
rica que invoque al mtodo de dicha clase. Pruebe el diseo utilizando la biblioteca typeinfo.pets.
Comodines no limitados
El comodn no limitado <?> parece que quiere significar "cualquier cosa", por lo que utilizar un comodin no limitado pare-
ce equi valente a emplear un tipo normal. Ciertamente, el compilador parece aceptar, a primera vista, esta interpretacin:
11 : generics/unboundedWildcardsl.java
import java.util.*
public class UnboundedWildcardsl
static List listl
static List<?> list2
static List <? extends Object> list3
static void assignl{List list) {
listl = list
list2 = list
II list3 = list II Advertencia: conversin no comprobada
II Found: List, Required: List <? extends Object>
static void assign2(List<?> list) {
listl list
list2
list3
list
list
static void assign3(List<? extends Object> list)
listl
list2
list3
list
list
list
public static void main(String[] args) {
assignl(new ArrayList()}
assign2(new ArrayList()}
II assign3(new ArrayList{ II Adevertencia:
II conversin no comporbada. No encontrado: ArrayList
II Requerido: List<? extends Object>
assignl(new ArrayList<String>(
assign2(new ArrayList<String>(
assign3(new ArrayList<String>(
II Ambas formas son aceptables como
List<?> wildList = new ArrayList {)
wildList = new ArrayList<String>();
assignl(wildList) ;
assign2(wildList) i
assign3(wildList)
Hay muchos casos como los de este ejemplo en que al compi lador no le importa si utilizamos un tipo normal o <?>. En
dichos casos, <?> parece completamente superfl uo. Sin embargo, sigue teniendo sentido utilizar este comodn, porque lo
que dicho comodn dice en la prctica es "he escrito este cdigo teniendo presente los genricos de Java y lo que estoy uti-
lizando aqu no es un tipo 110n11al, aunque en este caso el parmetro genrico puede referirse a cualquier tipo".
15 Genricos 441
Un segundo ejemplo muestra un uso importante de los comodines no limitados. Cuando estemos tratando con mltipl es
parmetros genricos, a menudo es importante pennitir que un parmetro sea de cualquier tipo al mi smo tiempo que se asig-
na un tipo concreto al otro parmetro:
1/ : generics/UnboundedWildcards2 . java
i mport java.util.*;
public class UnboundedWildcards2
static Map mapl i
static Map<?,?> map2;
static Map<String,?> map3;
static void assignl (Map map ) { mapl c: mapi }
s tatic void assign2 (Map<?,?> map ) { map2 = map;
static void assign3 (Map<String,?> map ) { map3 = mapi
public static void main (String (] args) {
}
assignl (new HashMap ()) ;
assign2 (new HashMap ( i
/1 assign3 {new HashMap ( i /1 Advertencia:
11 Conversin no comprobada. Encontrado: HashMap
11 Requerido : Map<String,?>
assignl (new HashMap<String,Integer> ( ;
assign2 {new HashMap<String,Integer> ( ;
assign3 (new HashMap<String,Integer> ( ;
/// >
Pero de nuevo, cuando lo que tenemos son todos comodines no limitados, como por ejemplo en Map<?,?>, el compilador
parece no efectuar di stinciones con respecto a un mapa nonnal y corriente. Adems, UnboundedWildcardsl.java muestra
que el compilador trata List<?> y List<? extends Object> de manera diferente.
Lo que resulta ms confuso es que el compilador no siempre diferencia entre, por ejemplo, List y List<?>, por lo que ambas
expresiones podran parecer equivalentes. De hecho, puesto que los argumentos genricos se ven sometidos al mecanismo
de borrado de tipos, List<?> podra parecer equivalente a List<Object>, y List es de hecho equi valente a List<Object>.
Sin embargo, estas afinnaciones no son completamente ciertas. List quiere decir en la prctica "una lista simple que alma-
cena cualquier tipo de objeto Object", mientras que List<?> significa "una lista no simple de algn lipo especfico, aunque
no sabemos qu tipo es ese".
En qu casos se preocupa el compilarlor de las diferencias entre los tipos nonnales y los tipos que incluyen comodines no
limitados? El sigui ente ejemplo utiliza la clase Holder<T> que hemos definido anterionnente. Conti ene mtodos que toman
Holder como argumentos, pero de distintas maneras: como tipo nonna!, con un parmetro de un tipo especfico y con un
parmetro con un comodn no limirado:
11 : generics / wildcards.java
/1 Exploracin del significado de l os comodines.
public class Wildcards {
/1 Argumento normal:
static void rawArgs (Holder holder, Objec t arg ) {
// holder.set {arg); / 1 Advertencia:
/1 Llamada no comprobada a (T) como miembro
1/ del tipo normal Holder
// holder.set{new Wildcards {) ; I1 Misma advertencia
/1 No se puede hacer esto; no se dispone de ninguna 'T':
// T t holde r.get () ;
II OK, pero la informacin de tipos se ha perdido :
Object obj holder.get () ;
/1 Similar a rawArgs {) , pero con errores en lugar de advertencias:
442 Piensa en Java
static void unboundedArg(Holder<?> holder, Object arg) {
II holder.set(arg); II Error,
II set(capture of ?) en Holder<capture of ?>
1 I no puede aplicarse a (Object)
II holder.set{new Wildcards(); II Mismo eTror
I1 No se puede hacer esto; no se dispone de ninguna lT':
II T t holder.get();
II OK, pero la informacin de tipos se ha perdido:
Object obj = holder.get{);
static <T> T exactl (Holder<T> holder) {
T t = holder.get();
return t;
static <T> T exact2(Holder<T> holder, T arg) {
holder.set(arg) ;
T t = holder.get();
return t;
static <T>
T wildSubtype{Holder<? extends T> holder, T arg) {
1/ holder.set(arg); II Error,
II set(capture of ? extends T) en
II Holder<capture of ? extends T>
1I no se puede aplicar a (T)
T t = holder.get();
return ti
static <T>
void wildSupertype(Holder<? super T> holder, T arg) {
holder.set(arg) ;
liT t = holder. get (); II Error,
I1 Tipos incompatibles: encontrado Object, requerido T
/1 OK, pero la informacin de tipos se ha perdido:
Object obj = holder.get();
public static void main(String[] args)
Holder raw = new Holder<Long>{);
1/ O bien,
raw = new Holder();
Holder<Long> qualified = new Holder<Long>()
Holder<?> unbounded = new Holder<Long>();
Holder<? extends Long> bounded = new Holder<Long>{);
Long lng = lL
rawArgs{raw, lng};
rawArgs{qualified, lng);
rawArgs{unbounded, lng);
rawArgs(bounded, lng);
unboundedArg(raw, lng);
unboundedArg{qualified, lng);
unboundedArg{unbounded, lng);
unboundedArg(bounded, ln9);
II Object rl = exactl(raw) II Advertencias:
// Conversin no comprobada de Holder a Holder<T>
// Invocacin de mtodo no comprobado: exactl(Holder<T
11 aplicada a (Hol der )
Long r2 : exactl(qualifiedl j
Obj ect r3 = exactl (unhoundedl; /1 Debe devolver Obj ect
Long r4 exactl (bounded) ;
// Long r5 = exact2 (raw, ln9); /1 Advertencias:
/1 Conve rsin no comprobada de Holder a Holder<Long>
// Invocacin de mtodo no comprobado: exact2 {Holder<T>,T)
/ / aplicada a (Holder, Long )
Long r6 = exact2{qualified, ln9);
JI Long r 7 = exact2 (unbounded, 1n9); JI Error:
JI exact2 (Ho lder<T>,T) no puede aplicarse a
11
11
11
11
(Holder<capture of ?>,Long)
Long rB = exact2 (bounded, ln9); JI Error:
exact2 {Holder<T>,T) no puede aplicarse a
to (Holder<capture of ? extends Long>, Long )
// Long r9 = wildSubtype (raw, lng) i // Advertencias:
// Conversin no comprobada de Holder
// a Ho lder<? extends Long>
// Invocacin de mtodo no comprobado:
// wildSubtype{Holder<? extends T>,T)
// aplicada a {Holder, Long)
Long rlO = wildSubtype{qualified, ln9 ) ;
// OK, pero slo puede devolver Object:
Object rll = wildSubtype {unbounded, ln9 ) ;
Long r12 = wildSubtype (bounded, lng) ;
// wildSupertype (raw, lng); // Advertencias:
11
11
Conversin no comprobada de Holder
a Holder<? super Long>
// Invocacin de mtodo no comprobado:
// wildSupertype (Holder<? super T>,T)
// aplicada a (Holder, Long)
wildSupertype (qualified, lng) i
// wildSupertype {unbounded, 1ng) ; // Error:
11
11
11
11
11
}
111 ,-
wildSupertype(Holder<? super T>,T) no puede
aplicarse a (Holder<capture of ?>,Long)
wildSupertype(bounded, 1ng) i // Error:
wildSupertype (Holder<? super T>,T) no puede
aplicarse a (Holder<capture of ? extends Long>, Long)
15 Genricos 443
En rawArgs( l , el compilador sabe que Holder es un tipo genrico, por lo que an cuando se lo exprese aqu como un tipo
nonnal, el compilador sabe que pasar un objeto de tipo Object a sct( l no resulta seguro. Puesto que se trata de un tipo nor-
mal, podemos pasar un objeto de cualquier tipo a set( l y dicho objeto se generaliza a Object. Por tanto, siempre que ten-
gamos un tipo nonnal, estaremos sacrificando posibilidades de comprobacin en tiempo de compilacin. La llamada a get( l
muestra el mismo problema: no hay ningn T, por lo que el resultado slo puede ser de tipo Object.
Resulta tentador pensar que el tipo Holder y Holder<?> son aproximadamente iguales. Pero unboundedArg( l demuestra
que son di sti ntos: este mtodo hace aflorar el mismo tipo de problemas, pero infonna de ellos como de errores en lugar de
como advertencias, porque el tipo Holder pennitir almacenar una combinacin de objetos de cualquier tipo, mientras que
Holder<?> almacena una coleccin homognea de algn (ipo especfico, y no podemos limitarnos a pasar un objeto de tipo
Object.
En exactl( l y exact2( l , podemos ver los parmetros genricos exactos utilizados si n comodines. Como vemos, exact2( l
tiene limitaciones distintas que exactl( l, debido al argumento adicional.
444 Piensa en Java
En wildSubtype( ), las restricciones en el tipo de Holder se relajan para incluir un contenedor Holder de cualqui er COSa
que extienda T. De nuevo, esto significa que T podra ser Fruit, mientras que holder podra ser perfectamente el contene-
dor Holder<Apple>. Para impedir que se inserte un objeto Orange en un contenedor Holder<Apple>, la ll amada a set( )
(o cualqui er mtodo que tome un argumento referido al parmetro de tipo) no est permitida. Si n embargo, seguimos sabien-
do que cualquier cosa que extraigamos de un contenedor Holder<? extends Fruit> ser al menos de tipo Fruit, por lo que
s est pem1itido invocar get() (o cualquier mtodo que genere un valor de retomo que se corresponda con el parmetro de
tipo).
Los comodines de supertipo se muestran en wildSupertype(), que tiene el comportamiento opuesto a wildSubtype( ): hol-
der puede ser un contenedor que almacene cualquier tipo que sea una clase base de T. Por tanto, set( ) puede aceptar un
objeto T, puesto que cualquier cosa que fu ncione con un tipo base tambin funcionar, polimrficamente. con un tipo deri-
vado (y por tanto con T). Sin embargo. no resulta til tratar de invocar get( ). porque el tipo almacenado por holder puede
ser cualquier supertipo, de modo que el nico tipo seguro es Object.
Este ejemplo tambin muestra las limitaciones relativas a lo que se puede y no se puede hacer respecto a un parmetro no
limitado. El metodo que ilustra esta situacin es unbounded() : no se puede invocar get() o set( ) con T porque no dispo-
nemos de ningn T.
En main( ), podemos ver cules de estos mtodos pueden aceptar cada uno de estos mtodos sin errores ni advertencias.
Para garanti zar la compati bilidad de mi gracin, rawArgs() admite todas las diferentes variac iones de Holder sin generar
advertencias. El mtodo unboundedArg() tambin acepta todos los tipos, aunque, como hemos indicado anteriormente, los
gestiona de manera distinta dentro del cuerpo del mtodo.
Si pasamos una referencia Holder normal a un mtodo que tome un tipo genrico "exacto" (comodi nes) obtendremos una
advertencia, porque el argumento exacto est esperando infonnacin que no existe en el tipo nonnaJ. Y si pasamos una refe-
rencia no limitada a exactt ( ), no existe la sufi ciente informacin de tipos como para establecer el tipo de retorno.
Podemos ver que exact2( ) tiene el mayor nmero de restricciones, ya que desea di sponer exactamente de un Holder<T>
y de un argumento de tipo T, y debido a ello genera errores o advertencias a menos que le ent reguemos los argumentos exac-
tos. En ocasiones, esto es perfectamente admisible, pero se trata de una restriccin excesiva; en ese caso, podemos utilizar
comodines, dependiendo de si queremos obtener valores de retorno de un tipo detenninado a partir de nuestro argumento
genrico (como puede verse en wildSubtype() o de si queremos pasar argumentos con un tipo detenminado a nuestro argu-
mento genrico (como puede verse en wildSupertype( .
Por tanto, la ventaja de uti lizar tipos exactos en lugar de tipos con comodn es que podemos hacer ms cosas con los par-
metros genri cos. Sin embargo, utilizar comodines nos permite aceptar como argumentos un rango ms ampli o de tipos
parametrizados. El programador deber decidir cul es el compromjso ms adecuado examinando caso por caso.
Conversin de captura
Hay una situacin concreta que exige el uso de <?> en lugar de un tipo normal. Si se pasa un tipo normal a un mtodo que
utilice <?>, al compilador le resulta posible inferir el parmetro de tipo real , de modo que el mtodo puede a su vez invo-
car otro mtodo que uti li ce el tipo exacto. El siguiente ejemplo muestra esta tcnica que se denomina conversin de caplll-
ra porque el tipo no especificado con comodn se captura y se convierte en un tipo exacto. Aqu, los comentarios acerca de
las advertencias slo se aplican si se el imina la anotacin @SuppressWarnings :
// : generics /CaptureConversion.java
public class CaptureConversion
static <T> void fl {Holder<T> holder )
T t = holder.get () i
System. out. println (t. getClass () . getSimpleName () ) i
static void f2 (Holder<?> holder ) {
f1 (holder ) i // Llamada con tipo capturado
@SuppressWarnings ( tlunchecked ti )
public static void main(String[] args )
Holder raw = new Holder<Integer > (1) i
1/ fl(raw) i JI Genera advertencias
f2 (raw) i tI Sin advertencias
Holder rawBasic = new Holder () ;
rawBasic.set (new Object ( ) ) ; 1/ Advertencia
f2 (rawBasic ) ; / / Sin advertencias
// Generalizacin a Holder<?>, sigue sabiendo cmo manejarla :
Holder<?> wildcarded = new Holder<Oouble> ( l.O ) i
f2 (wildcarded ) ;
/ * Output:
Int eger
Obj ect
Double
, /// ,-
15 Genricos 445
Los parmetros de tipo f1 () son todos exactos, sin comodines ni lmites. En f2(), el parmetro Holder es un comodin no
limitado, por lo que podra parecer que es desconocido en la prctica. Sin embargo, dentro de n ( ) , se invoca f1 () Y f1 ()
requiere un parmetro conocido. Lo que est sucediendo es que el tipo de parmetro se captllra en el proceso de invocacin
de n(), de modo que puede utilizarse en la llamada a f1 ().
El lector podra preguntarse si esta tcnica podra util izarse para escribi r, pero eso requerira que pasramos un tipo espec-
fico junto con Holder <?>. La conversin de captura slo funciona en aquell as situac iones donde necesitamos, dentro del
mtodo, trabajar con el tipo exacto. Observe que no se puede devolver T desde n ( ), porque T es desconocido para n ( ).
La conversin de captura resulta interesante, pero bastante limitada.
Ejerci cio 29: (5) Cree un mtodo genrico que tome como argumento un contenedor Holder<List<?. Detennine qu
mtodos puede o no invocar para el contenedor Holder y para la li sta List. Repita el ejercicio para un argu-
mento de tipo List<Holder <?.
Problemas
Esta seccin analiza di versos tipos de problemas que surgen cuando se intentan utilizar los genricos de Java.
No pueden usarse primitivas como parmetros de tipo
Como se ha mencionado anterionnente en este captulo, una de las limitaciones existentes en los genricos de Java es que
no pueden uti lizarse primitivas como parmetros de tipo. No podemos, por ejemplo, crear un contenedor ArrayList<int>.
La solucin consiste en utili zar las clases envoltorio de las primitivas en conjuncin con el mecanismo de conversin auto-
mtica de Java SE5. Si creamos un contenedor ArrayList<l nteger> y utilizamos enteros primiti vos con este contenedor,
descubriremos que el mecani smo de conversin automtica entra en accin y se encarga de convertir entre enteros y obje-
tos Integer automticamente, por tanto, es como si di spusiramos de un contenedor ArrayList<int>:
11 : generics / ListOflnt.java
II El mecanismo de conversin automtica compensa la incapacidad
II de utilizar primitivas en los genricos.
import java.util. * ;
public class ListOf lnt
public static void main {String [] args ) {
Lisc<Integer> li new ArrayList<Integer> () ;
for ( int i :: O; i < 5; i++ )
1i .add l i ) ;
for ( int i : li)
Syscem.out.print (i + " 11 ) ;
1* Output:
O 1 2 3 4
' /// ,-
446 Piensa en Java
Observe que el mecanismo de conversin automtica admite incluso la utili zacin de la sintaxi sforeach para generar valo-
res int.
En general , esta sol ucin funciona adecuadamente, ya que podemos almacenar y extraer apropiadamente valores primitivos
enteros. Se producen ciertas conversiones, pero stas se llevan a cabo de fonna transparente. Sin embargo, si las cuestiones
de rendimiento son un problema, podemos uti li zar una versin especiali zada de los contenedores adaptada para tipos pri-
mitivos; un ejemplo de versin de cdigo fuente abierto para este tipo de contenedores especializados es org.apache.
cornmons.collections.primitives.
He aqu otro enfoque, que crea un conjunto de bytes:
J/: generics/ByteSet.java
import java.util.*;
public class ByteSet {
Byte[] possib1es = { 1,2,3,4,5,6,7,8,9 j;
Set<Byte> mySet =
new HashSet<Byte>{Arrays.asList{possibles))
11 Pero no se puede hacer esto:
11 Set<Byte> mySet2 = new HashSet<Byte> {
/1 Arrays . <Byte>asList{1,2,3,4,S,6,7,e,9));
111,-
Observe que el mecanismo de conversin automtica resuelve algunos problemas, pero no todos ellos. El sigui ente ejemplo
muestra una interfaz Generator genrica que especifica un mtodo next() que devuelve un objeto con el tipo especi fi cado
del parmetro. La clase FArray contiene un mtodo genrico que utili za un generador para rellenar una matri z con objetos
(hacer la clase genrica no funcionara en este caso porque el mtodo es esttico). Las implementaciones de Generator
provienen del Capitulo 16, Matrices, y en maine ) podemos ver cmo se utili za FArray.fill( ) para rell enar matrices con
objetos:
/1: generics/PrimitiveGenericTest.java
import net.mindview.util.*;
1I Rellenar una matriz utilizando un generador:
class FArray {
public static <T> T [] fill (T [] a, Generator<T> gen ) {
for{int i = O; i < a.length i++)
a[iJ = gen.next{);
return a;
public class PrimitiveGenericTest {
public static void main{String[] args)
String {] strings = FArray. fill (
new String[7] , new RandornGenerator . String{lO));
for{String s : strings)
System.out.println(s) ;
Integer [] integers = FArray. fill (
new Integer(7] , new RandomGenerator.Integer{));
for{int i: integers)
System. out.println(i) ;
II El mecanismo de conversin automtica no funciona en este caso.
11 Lo siguiente no podr compilarse :
11 int[] b =
II FArray.fill (new int[7], new RandIntGenerator {)) ;
1* Output:
YNzbrnyGcF
OWZnTcQrGs
eGZMmJMROE
suEcUOneOE
dLsmwHLGEa
hKcxrEqUCB
bklnaMesht
7052
6665
2654
3909
5202
2209
5458
*/// ,-
15 Genricos 447
Pueslo que RandomGenerator.lnteger implementa Generator<Jnteger>, cabria esperar que el mecanismo de conversin
automtica se encargara de convertir el valor de ncxt( ) de Intcgcr a int. Sin embargo, el mecanismo de conversin auto-
mtica no se apli ca a las matrices, as que esta soluci n no funciona.
Ejercici o 30: (2) Cree un contenedor Holder para cada uno de los tipos envoltorio de las primit ivas y demuestre que el
mecanismo de conversin automtica funciona para los mtodos sct() y get() de cada instancia.
Implementacin de interfaces parametrizadas
Una clase no puede impl ementar dos variantes de la mi sma interfaz genrica. Debido al mecani smo de borrado de tipos,
ambas seran la mi sma interfaz. He aqu una situacin donde se produce este tipo de coli sin:
// : generics/MultiplelnterfaceVariants.java
/ / {CompileTimeError} (No se compilar)
interface {}
class Employee implements {}
c lass Hourly extends Employee
implements Payable<Hourly> {} /// ,-
Hourly no se compilar debido a que el mecanismo de borrado de tipos reduce Payable<Employee> y Payabl e<Hourly>
a la misma clase, Payable, y el cdigo anterior significara que estaramos tratando de implementar la mi sma interfaz dos
veces. Lo que resulta interesante es que, si eliminamos los parmetros genri cos en ambos usos de Payable (como hace el
compilador durante el borrado de tipos), el cdigo si que puede compilarse.
Esta cuesti n puede resultar bastante frustrante cuando estemos trabajando con alguna de las interfaces ms fundamentales
de Java, como Comparable<T>, como podremos ver ms adelante en esta seccin.
Ejercicio 31: ( 1) Elimine todos los genricos de MultiplelnterfaeeVariants.java y modifique el ejemplo para que el
cdigo pueda compilarse.
Proyecciones de tipos y advertencias
Ut ilizar una proyeccin de tipos o instanceof con un parmetro de tipo genrico no tiene ningn efecto. El siguiente conte-
nedor almacena los valores internamente como objetos de tipo Objeet y los proyecta de nuevo sobre T en el momento de
extraerlos:
// : generics /GenericCast.java
class {
private int index : o;
private Object[] storage;
public FixedSizeStack(int size)
storage : new Object[size];
448 Piensa en Java
public void push{T item) { storage [index++] = item;
@SuppressWarnings{ "unchecked" )
public T pop() { return (T) storage [ - -index) ; }
public class GenericCast {
public static final int SIZE = 10;
public static void main(String(] args)
FixedSizeStack<String> strings =
new FixedSizeStack<String> (SIZE ) ;
for lString s : "A BCD E F G H 1 JII.Split(l' 1') )
strings.push(s) ;
for{int i = O; i < SIZE; i++)
String s = strings.pop();
System.out .print{s + I! " ) i
1* Output:
J 1 H G F E D e B A
* /// ,-
Sin la anotacin @SuppressWarnings, el compilador generara una advertencia de "proyeccin de tipos no comprobada"
para pop(). Debido al mecanismo de borrado de tipos, no puede saber si la proyeccin de tipos es segura, y el mtodo pop()
no realiza en la prctica ninguna proyeccin. T se borra para sustituirla por su primer lmite que es Object de manera pre
detenninada, por lo que pop() est, de hecho, proyectando un objeto de tipo Objecl sobre Objecl.
Hay veces en que los genri cos no eliminan la necesi dad de efectuar la proyeccin, y esto genera una advertencia por parte
del compilador, que es inapropiada. Por ejemplo:
11: generics/NeedCasting.java
import java.io.*;
import java.util. *;
public class NeedCasting
@SuppressWarnings ( "unchecked")
public void f(String[] args) throws Exception {
ObjectlnputStream in = new ObjectlnputStream(
new FilelnputStream(args(O] ));
List<Widget> shapes = (List<Widgetin.readObject () ;
Como veremos en el siguiente capitulo, readObjecl( ) no puede saber lo que est leyendo, por lo que devuelve un objeto
que habr que proyectar. Pero cuando desactivamos mediante comentarios la anotacin @SuppressWarnings y compil a
mos el programa, se obtiene una advertencia:
Note: NeedCasting.java uses unchecked or unsafe operations.
Note: Recompile with - Xlint:unchecked for details.
y si seguimos las instrucciones y recompilamos con -Xlint:unchecked:
NeedCasting . java:12: warning: [unchecked] unchecked cast
found : java.lang.Object
required: java.util.List<Widget>
List<Shape> shapes = (List<Widgetin.readObject () i
Estamos obli gados a realizar la proyeccin y a pesar de ello se nos dice que no podemos hacerlo. Para resolver el proble-
ma, debemos utilizar una nueva forma de proyeccin de tipos introducida en Java SES, que es la proyeccin de tipos a tra
vs de una clase genrica:
11 : generics/classCasting.java
import java.io.*;
import java.util.*;
public class ClassCasting {
@SuppressWarnings ("unchecked")
public void f(String[] args) throws Exception (
ObjectlnputStream in = new ObjectlnputStream(
new FilelnputStream(args[O])) i
// No se compilar:
/1 List<Widget> lwl =
11 List<Widget>.class.cast (in.readObject () ) i
List<Widget> lw2 = List.class.cast(in.readObject());
}
/// ,-
15 Genricos 449
Sin embargo, no podemos efectuar una proyeccin sobre el tipo real (List<Widget. En otras palabras, no podemos
escribir:
List<Widget> .class.cast (in .readObject ()}
e incluso si aiiadimos otra proyeccin como la siguiente:
(List<WidgetList,class.cast(in.readObject())
seguiremos obteniendo una advertencia.
Ejercici o 32: (1) Verifique que FixedSizeStack en GenericCast.java genera excepciones si intentamos salimos de los
lmites fijados. Quiere esto decir que no se necesita el cdigo de comprobacin de lmites?
Ejercici o 33: (3) Corrija GenericCast.java utilizando un contenedor ArrayList.
Sobrecarga
El siguiente ejemplo no se podr compilar, aunque parece que se trata de algo bastante razonable:
jj : genericsjUseList.java
ji {compileTimeError} (no se compilar)
import java.util.*
public class UseList<W,T>
void f (List<T> v) {}
void f (List<W, vi {)
/1 /,-
Al sobrecargar el mtodo se produce, debido al mecanismo de borrado de tipos, un conflicto debido a la existencia de sig-
naturas idnticas.
En lugar de ello, debemos proporcionar nombres de mtodos distintos en aquellos casos en que el borrado de tipos en los
argumentos no genere li stas de argumentos diferenciadas:
jj : genericsjUseList2.java
import java.util.*
public class UseList2<W,T>
void fl (List<T, vi {)
void f2 (List<W> v) {}
/1 / ,-
Afortunadamente, el compi lador detecta este tipo de problemas.
Secuestro de una interfaz por parte de la clase base
Suponga que disponemos de una clase Pet que es Comparable con otros objetos Pet:
jj : genericsjComparablePet.java
public class ComparablePet
450 Piensa en Java
i mplements
publi c int compareTo (ComparablePet arg ) { return Oi }
} /1/ , -
Resulta razonable tratar de enfocar mejor el tipo con el que pueda compararse una subclase de ComparablePet. Por ejem-
plo, un objeto Cat slo debera ser Comparable con otros objetos Cat:
11 : generics / Hijackedlnterface.java
II {Compi leTimeErro r } (no se compilar)
c lass Cat extends ComparablePet implements Comparable<Cat >{
II Error: Comparable no puede heredarse con
II diferentes argumentos: <Cat> y <Pet>
public int compareTo (Cat arg) { return O; }
/1 / ,-
Lamentablemente, esta solucin no funciona. Una vez que se establece el argumento Compar ablePet para Comparable,
no puede ya compararse ninguna otra clase implementadora con ninguna cosa, salvo con ComparablePet:
/1 : generics / RestrictedComparablePets . java
class Hamster e x tends Compar ablePet
implements Comparable<ComparablePet>
public int compareTo (ComparablePet arg ) { return O; }
/1 O simplemente :
class Gecko extends ComparablePet {
public int compareTo (ComparablePet arg ) { return O; }
} /1 /,-
Harnster demuestra que es posible reimplementar la misma interfaz que podemos encontrar en ComparablePet, siempre y
cuando sea exactamente la mi sma, incluyendo los tipos de parmetro. Sin embargo, esto es lo mi smo que limitarse a susti-
tuir los mtodos en la clase base, como puede verse en Gecko.
Tipos autolimitados
Existe una sintaxi s que provoca bastante confusin y que aparece con bastante asiduidad en el caso de los genricos de Java.
He aqu el aspecto:
c lass SelfBounded<T extends SelfBounded<T { II ...
Esto es algo comparable a tener dos espejos enfrentados, lo que genera una especia de refl exin infinita. La clase
Selffiounded toma un argumento genri co T, por su parte, T est restringido por lIn lmite, y dicho lmite es Selffiounded,
con T como argumento.
Este tipo de sintaxis resulta dificil de entender cuando se ve por primera vez, y nos pennite enfatizar el hecho de que la pal a-
bra clave extends, cuando se utiliza con los lmites de los genricos, es completamente di stinta que cuando se la usa para
crear subclases.
Genricos curiosamente recurrentes
Para comprender lo que significa un tipo autolimitado, comencemos con una versin ms simple de esa sintaxi s, sin el auto-
lmite.
No podernos heredar directamente de un parmetro genrico. Sin embargo, si que podemos heredar de una clase que utili-
ce dicho parmetro genrico en su propia definicin. En otras palabras. podemos escribir:
11 : generics/CuriouslyRecurringGene ric.java
class GenericType<T> {}
15 Genricos 451
public class CuriouslyRecurringGeneric
extends GenericType<CuriouslyRecurringGeneric> {} /1/:-
Este tipo de estructura podra denominarse genrico curiosamente recurrente siguiendo el ttulo del artculo Curiously
Recurring Template Pattern de Jim Coplien aplicado a C++. La parte "curiosamente recurrente" hace referencia al hecho de
que nuestra clase, lo cual resulta muy curioso, en su propia clase base.
Para comprender 10 que esto significa, tratemos de enunciarlo en voz alta: "Estamos creando una nueva clase que hereda de
un tipo genrico que toma el nombre de nuestra clase como parmetro", Qu es lo que puede hacer el tipo base genrico
cuando se le da el nombre de la clase derivada? A este respecto, tenemos que tener en cuenta que los genricos en Java estn
relacionados con los argumentos y los tipos de retomo, as que se puede definir una clase base que utilice el tipo derivado
en sus argumentos y en sus tipos de retomo. Tambin puede utilizar el tipo derivado para definir el tipo de los campos, an
cuando esos tipos sern borrados y sustituidos por Object . He aqu una clase genrica que nos permite expresar esto:
// : generics/BasicHolder.java
public class BasicHolder<T>
T element
void set(T arg) { element
T get () { return element;
void f () (
arg; )
System. out. println (element. getClass () . getSimpleName () ) ;
Se trata de un tipo genri co normal con mtodos que aceptan y generan objetos del tipo especificado en el parmetro, junto
con un mtodo que opera sobre el campo almacenado (aunque ni camente reali za operaciones de tipo Object con ese
campo).
Podemos utili zar BasicHolder en un gentico curiosamente recurrente:
// : generics/CRGWithBasicHolder.java
class Subtype extends BasicHolder<Subtype> {}
publ ic class CRGWithBasicHolder {
public static void main{String[] args) {
Subtype stl = new Subtype(), st2 = new Subtype() i
stl.set (st2) i
Subtype st3
stl.f() ;
/ * Output:
Subtype
,///,-
stl.get () ;
Observe en este ejemplo algo muy importante: la nueva clase Subtype toma argumentos y valores de retomo de tipo
Subtype, no simplemente de la clase base BasicHolder. sta es la esencia de los genricos curiosamente recurrentes: la
clase derivada sustituye a la clase base en sus parmetros. Esto significa que la clase base genrica se convierte en una cier-
ta clase de plantilla para describir la funcionalidad comn de todas sus clases deri vadas, pero esta funcionalidad utili zar el
tipo derivado en todos los argumentos y tipos de retomo. En otras palabras, se utilizar el tipo exacto en lugar del tipo base
en la clase resultante. Por tanto, en Subtype, tanto el argumento de sel() como el tipo de retomo de gel( ) son exactamen-
te Subl)' pe.
Autolimitacin
El contenedor BasicHolder puede utilizar cualquier tipo como su parmetro genrico, como puede verse aqu :
//: generics/Unconstrained.java
class Other ()
452 Piensa en Java
class BasicOther extends BasicHolder<Other> {}
public class Unconstrained {
public static void main(String(] args)
BasicOther b = new BasicOther (), b2 = new BasicOther()
b.set(new Other()) i
Other other = b.get () ;
b. f 11 ;
1* Output :
Other
* jjj,-
La autolimitacin reali za el paso adicional de obligar a que el genenco se utilice como su propio argumento lmi te.
Examinemos cmo puede utilizarse y cmo no puede utili zarse la clase resultante:
11: generics/SelfBounding.java
class SelfBounded<T extends SelfBounded<T
T element
SelfBounded<T> set(T arg)
element = arg
return this
T get () { return e lemen t }
class A extends SelfBounded<A> {}
class B extends SelfBounded<A> {} II Tambin OK
class C extends SelfBounded<C>
C setAndGet IC arg) ( set larg); return get () ; }
class O {}
II No se puede hacer esto:
II class E extends SelfBounded<D> {}
II Error de compilacin: el parmetro de tipo D no est dentro de su lmite
II Sin embargo, podemos hacer esto, as que no se puede forzar la sintaxis :
class F extends SelfBounded {}
public class SelfBounding {
public static void rnain (String [] argsl {
A a = new A() ;
a.set (new A() 1
a a. set (new AII) .get 11;
a a. get 11 ;
C e = new C(l i
c c.setAndGet(new C{))
Lo que la autolimitacin hace es requerir el uso de la clase en una relacin de herencia como sta:
class A extends SelfBounded<A> {}
Esto nos fuerza a pasar la clase que estemos definiendo como parmetro a la clase base.
Cul es el valor aadido que obtenemos al autol imitar el parmetro? El parmetro de tipo debe ser el mi smo que la clase
que se est definiendo. Corno podemos ver en la definicin de la clase B, tambin podemos heredar de una clase
15 Genricos 453
SelfBounded que utilice un parmetro SelfBounded, aunque el uso predominante parece ser el que podernos ver en la clase
A. El intento de definir E demuestra que no se puede utilizar un parmetro de tipo que no sea SelfBounded.
Lamentablemente, F se compila sin ninguna advertencia, por lo que la sintaxis de autolimitacin no se puede imponer. Si
fuera realmente importante, se necesitara una herramienta externa para garanti zar que los tipos nomlales no se utilizan en
lugar de los tipos parametrizados.
Observe que se puede eliminar la restricc in y todas las clases se seguirn compilando, pero entonces E tambin se compi-
lara:
JI : generics/NotSelfBounded . java
public class NotSelfBounded<T>
T element
NotSelfBounded<T> set(T arg)
element = arg i
return this;
T get () { return element; }
class A2 extends NotSelfBounded<A2> {}
class B2 extends NotSelfBounded<A2> {}
class C2 extends NotSelfBounded<C2>
C2 setAndGet IC2 arg) ( set larg); return get 1) ; }
elass D2 ()
// Ahora esto es OK:
class E2 extends NotSelfBounded<D2> {} ///:-
As que, obviamente, la restriccin de autolimitacin slo sirve para imponer la relacin de herencia. Si se utiliza la autoli-
mitacin, sabemos que el parmetro de tipo utilizado por la clase ser el mismo tipo bsico que la clase que est utilizando
dicho parmetro. Esto obliga a cualquiera que utilice dicha clase a ajustarse a ese fornlato.
Tambin resulta posible utilizar la autolimitacin en los mtodos genricos:
// : generics/SelfBoundingMethods.java
public class SelfBoundingMethods
static <T extends SelfBounded<T T f(T arg) {
return arg.set (arg) .get () i
public static void main (String [] args) {
A a = f lnew Al));
}
/// ,-
Esto evita que el mtodo se aplique a ningn tipo, excepto un argumento autolimitado de la forma mostrada.
Covarianza de argumentos
El valor de los tipos auto limitados es que producen tipos de argumentos ca variantes, es decir, tipos de argumentos de los
mtodos que varan con el fin de ajustarse a las subclases.
Aunque los tipos autolimitados tambin producen tipos de retomo que coinciden con el tipo de la subclase, esto no es tan
importante, porque Java SES ya introduce la funcionaljdad de lipos de retorno covariantes:
// : generics/CovariantReturnTypes.java
class Base {}
454 Piensa en Java
class Derived extends Base {}
interface OrdinaryGetter
Base get ();
interface DerivedGetter extends OrdinaryGetter {
jj El tipo de retorno del mtodo sustituido puede variar:
Derived get()
public class CovariantReturnTypes
void test (DerivedGetter d) (
Derived d2 = d.get{);
El mtodo get() en DerivedGetter sustituye a get() en OrdinaryGetter y devuelve un tipo que se deriva del tipo devuel
to por OrdinaryGetter.get() . Aunque se trata de algo perfectamente lgico (un mtodo de un tipo derivado debera poder
devolver el tipo ms especfico que el mtodo del tipo base que est sustituyendo). no poda hacerse en las versiones ante-
riores de Java.
Un genrico aut olimitado produce, de hecho, el tipo exacto derivado como valor de retomo, como podemos ver aqu con el
mtodo get( ):
jj: genericsjGenericsAndReturnTypes.java
interface GenericGetter<T extends GenericGetter<T
T get () ;
interface Getter extends GenericGetter<Getter> {}
public class GenericsAndReturnTypes
void test (Getter g) {
Getter result = g.get()
GenericGetter gg = g.get(); jj Tambin el tipo base
Observe que este cdigo no podria haberse compilado de no haberse incluido los tipos de retomo covariantes en Java SE5.
Sin embargo, en el cdigo no genrico, los tipos de argumento no pueden variar con los subtipos:
jj: genericsjOrdinaryArguments.java
class OrdinarySetter {
void set (Base base) {
System. out . println ("OrdinarySetter. set (Base) ")
class DerivedSetter extends OrdinarySetter {
void set (Derived derived) {
System.out.println ("DerivedSetter. set (Derived) ti)
public class OrdinaryArguments (
public static void main{String(] args)
Base base = new Base ();
Derived derived = new Derived()
DerivedSetter ds = new DerivedSetter();
dS.set(derived} ;
ds.set(base); /1 Se compila : sobrecargado, no sustituido
/ * Output:
DerivedSetter.set(Derived)
OrdinarySetter.set {Base )
'111,-
15 Genricos 455
Tanto set(derived) como set(base) son legales, por lo que DerivedSetter.set() no est sust ituyendo OrdinarySetter.set(),
sino que est sobrecargando dicho mtodo. Analizando la salida, puede ver que hay dos mtodos en DerivedSetter, por lo
que la versin de la clase base sigue estando disponible, lo que confim1a que se ha producido una sobrecarga del mtodo.
Sin embargo, con los tipos autolimitados, slo hay un nico mtodo en la clase derivada y dicho mtodo loma el tipo deri-
vado como argumento, no el tipo base:
1/ : generics/SelfBoundingAndCovariantArguments .java
interface SelfBoundSetter<T extends SelfBoundSetter<T
void set (T arg);
interface Setter extends SelfBoundSetter<Setter> {}
public class SelfBoundingAndCovariantArguments {
void testA(Setter 51, Setter 52, SelfBoundSetter sbs) {
sl.5et{s2) ;
II sl.set{sbs}; II Error,
// set(Setter) en SelfBoundSetter<Setter>
// no puede aplicarse a (SelfBoundSetter )
El compilador no reconoce el intento de pasar el tipo de base como argumento a set(), porque no existe ningn mtodo con
dicha signatura. El argumento ha si do, en la prctica, sustituido.
Sin el mecanismo de autolimitacin, entra en accin el mecani smo nonnal de herencia y lo que obtenemos es una sobrecar-
ga, al igual que sucede en el caso no genrico:
//: generics/PlainGenericlnheritance.java
class GenericSetter<T> { // Sin autolimitacin
void set (T arg) {
System. out. println ("GenericSetter. set (Base) ,, ) ;
class DerivedGS extends GenericSetter<Base> {
void set(Derived derived) {
System.out.pr intln ( "DerivedGS.set (Derived ) 11 ) i
public class PlainGenericlnheritance {
public static void main(String[] args)
Base base = new Base();
Derived derived = new Derived () i
DerivedGS dgs = new DerivedGS();
dgs.set(derived) i
dgs . set(basel i // Se compila: sobrecargado, no sustituido
/ * Output :
456 Piensa en Java
DerivedGS.set {Derivedl
GenericSetter.set (Base )
' 111 ,-
Este cdigo se asemeja a OrdinaryArguments.java; en dicho ejemplo, DerivedSetter hereda de OrdinarySetter que con-
tiene un conjunto set(Base). Aqu, DerivcdGS hereda de GenericSetter<Base> que tambin contiene un conjunto
set(Base), creado por el genrico. Y al igual que en OrdinaryArguments.java, podemos ver analizando la sali da que
DerivedGS contiene dos versiones sobrecargadas de set(). Sin el mecanismo de la autolimitacin, lo que se hace es sobre-
cargar los tipos de argumentos. Si se utiliza la 3ulolimitacin, se tennina disponiendo de una nica versin de un mtodo,
que admite el tipo exaclO de argumento.
Ejercicio 34: (4) Cree un tipo genrico autolimitado que contenga un mtodo abstracto que admita un argumento con el
tipo del parmetro genrico y genere un valor de retomo con el tipo del parmetro genrico. En un mto-
do no abstracto de la clase, invoque dicho mtodo abstracto y devuelva su resultado. Defina otra clase que
herede del tipo autolimitado y compruebe el funcionamiento de la clase resultante.
Seguridad dinmica de los tipos
Dado que podemos pasar contenedores genricos a los programas anteriores a Java SES, sigue existiendo la posibilidad de
que cdigo escrito con el esti lo antiguo corrompa nuestros contenedores. Java SES dispone de un conjunto de utilidades en
java.utU.Collections para resolver el problema de comprobacin de tipos en esta situacin: los mtodos estticos
checkedCollection( ), checkedList( ), checkedMap( ), checkedSct( ), checkedSortcdMap( ) y checkedSortcdSet( ).
Cada uno de estos mtodos toma como primer argumento el contenedor que queramos comprobar dinmicamente y como
segundo argumento el tipo que queramos imponer que se utilice.
Un contenedor comprobado generar una excepcin ClassCastException en cualquier lugar en el que tratemos de insertar
un objew inapropiado, a diferencia de los conrenedores anteriores a la introduccin del mecanismo de genricos (conrenedo-
res nonnales y corrientes), que lo que haran sera inforruamos de que hay un problema en el momento de tratar de extraer el
objeto. En este ltimo caso, sabremos que existe un problema, pero no podremos detenninar quin es el culpable; por el con-
trario, con los contenedores comprobados, s que podemos averiguar quin es el que ha tratado de insertar el objeto errneo.
Examinemos este problema de "insercin de un gato en una li sta de perros" utili zando un contenedor comprobado. Aqu,
oldStyleMethod( ) representa un cierto cdigo heredado, porque admite un obejto List nomlal, siendo necesaria la anota-
cin @SuppressWarnings("uDchecked") para suprimir la advertencia resultante:
11 : generics / CheckedList.java
II Using Collection.checkedList {) .
import typeinfo . pets.*;
import java.util.*;
public class CheckedList
@SuppressWarnings ( "unchecked" )
static void oldStyleMethod (List probablyDogs } {
probablyDogs . add (new Cat( )} ;
public static void main (String(] args ) {
List <Dog> dogsl = new ArrayList<Dog> () ;
oldStyleMethod(dogslJ; II Acepta sin rechistar un objeto Cat
List<Dog> dogs2 = Collections.checkedList(
new ArrayList<Dog>(), Dog.class);
try {
oldStyleMethod (dogs2 ) ; II Genera una excepcin
catch (Exception e ) {
System.out.println (e ) ;
I I Los tipos derivados funcionan correctamente:
List<Pet> pets = Collections.checkedList(
new ArrayList<Pet>(), Pet.class);
pets.add (new Dog {) ) ;
15 Genricos 457
pets . add{new Cat()) i
/ * Output:
java.lan g . ClassCa stException: Attempt to ins e rt class t ypeinf o . pets . Cat eleme nt i nta
col l e ction with e l e ment type class type info .pets . Dog
*/// , -
Cuando ejecutamos el programa, vemos que la insercin de un objeto Cat no provoca ninguna queja por parte dogsl , pero
dogs2 genera inmediatamente una excepci n al tratar de insertar un tipo incorrecto. Tambin podemos ver que resulta per-
fectamente posible introducir objetos de un tipo derivado dentro de un contenedor comprobado donde se est haciendo la
comprobacin segn el tipo base.
Ejerci cio 35: (1) Modifique CheckedList.java para que utilice las clases Coffee definidas en este capitul o.
Excepciones
Debido al mecanismo de borrado de tipos, la utili zacin de genricos con excepciones es extremadamente limitada. Una
clusula catch no puede tratar una excepcin de un tipo genrico, porque es necesario conocer el tipo exacto de la excep-
cin tanto en tiempo de compilacin como en tiempo de ejecucin. Asimismo, una clase genrica no puede heredar directa
ni indirectamente de Throwable (esto impide que tratemos de definir excepciones genricas que no puedan ser atrapadas).
Sin embargo. los parmetros de tipo s que pueden usarse en la clusula throws de la declaracin de un mtodo. Esto nos
pennite escribir cdigo genrico que vare segn el tipo de una excepcin comprobada:
JJ: g e nerics/ThrowGenericExcept i on . java
impert java . util .* ;
interface Processor<T,E extends Exception> {
void p r ocess{List<T> resultCellector) throws E
class ProcessRunner<T,E e x t e nds Exception>
extends ArrayLis t <Processor<T, E {
List<T> processAII{) throws E {
List<T> r e sultCollector = new ArrayList<T>();
for{Processor<T,E> proce ssor : this)
p r ocessor . process(resultCollector) ;
return resul t Collector;
class Failure1 extends Exception {}
class Processor1 implements Processor<String,Failure1>
static int count = 3;
public void
process(List<String> resultCollector) throws Failurel {
if(count-- > 1)
resultCollector. add ( " Hep! " ) ;
else
resultCollector . add ("Ha! U) i
if(count < O)
throw new Failure1();
class Failur e2 exte nds Exception {}
class Processor2 impleme nts Processar<Integer ,Failure2>
458 Piensa en Java
static int count = 2;
public void
process(List<Integer> resultCollector) throws Failure2 {
if(count-- == O)
resultCollector.add(47) ;
else (
resulcCollector.add(ll) ;
if (count < O)
throw new Failure2();
public class ThrowGenericException
public static void main(String[] args)
ProcessRunner<String,Failurel> runner =
new ProcessRunner<String, Failurel> () ;
for(nt i = Oi i < 3; i++)
runner.add(new Processorl{;
try (
System.out.println(runner.processAll{ ;
catch(Failurel e) {
System.out.println(e) ;
ProcessRunner<Integer,Failure2> runner2 =
new ProcessRunner<Integer,Failure2>();
for(int i = O; i < 3; i++)
runner2.add(new Processor2 ());
try (
System.out.println{runner2.processAll{) ;
catch(Failure2 el {
System.out.println(e) ;
Un objeto Processor ejecuta un mtodo process( ) y puede generar una excepcin de tipo E. El resultado de process( )
se almacena en el contenedor List<T> resultCollector (esto se denomina parmetro de recoleccin). Un obje-
to ProcessRunner tiene un mtodo processAll( ) que ejecuta todo objeto Process que almacene y devuelve el objeto
resultCoUector.
Si no pudiramos parametri zar las excepciones generadas, no podramos escribir este cdigo en forma genrica. debido a la
existencia de las excepciones comprobadas.
Ejercicio 36: (2) Ailada una segunda excepcin parametrizada a la clase Processor y demuestre que las excecpiones
pueden vari ar independientemente.
Mixins
El tnnino mixin parece haber adquirido distintos significados a lo largo del tiempo, pero el concepto fundamental se refi e
re a la mezcla (mixing) de capacidades de mltiples clases con el fin de producir una clase resultante que represente a todos
los tipos del mixin. Este tipo de labor suele realizarse en el ltimo minuto, lo que hace que resulte bastante conveniente para
ensamblar fcilmente unas clases con otras.
Una de las ventajas de los mixins es que permiten apli car coherentemente una serie de caractersticas y comportamientos a
mltiples clase. Como ventaj a aadida, si queremos cambiar algo en una clase mixin, dichos cambios se aplicarn a todas
las clases a las que se les haya aplicado el mixin. Debido a esto, los mixins parecen orientados a lo que se denomina progra-
macin orientada a aspectos (AOP, aspect-oriented programming), y diversos autores sugieren preci samente que se utilice
el concepto de aspectos para resolver el problema de los mixins.
15 Genricos 459
Mixins en C++
Uno de los argumentos ms slidos en favor de la utilizacin de los mecanismos de herencia mltiples en e++ es, precisa-
mente, el poder utilizar mixins. Sin embargo, un enfoque ms interesante y ms elegame a la hora de tratar los mixins es el
que se basa en la utilizacin de tipos parametrizados; segn este enfoque. un mixin es una clase que hereda de su parme-
tro de tipo. En C++, podemos crear mixins fci lmente debido a que e++ recuerda el tipo de SlI S parmetros de plantilla.
He aqu un ejemplo de e++ con dos tipos de mixin: uno que pennite aadir la propiedad de di sponer de una marca tempo-
ral y otro que aade un nmero de sen e para cada instancia de objeto:
JI: generics/Mixins.cpp
#include <string>
#inc!ude <ctime>
#include <iostream>
using namespace std
template<class T> class TimeStamped public T {
long timeStamp
public:
) ;
TimeStampedO { timeStamp = time(O) }
long getStamp () { return timeStamp }
template<class T> class SerialNumbered
long serialNumber
static long counter
public:
public T {
SerialNumbered () { serialNumber = counter++ }
long getSerialNumber () { return serialNumber; }
};
11 Definir e inicializar el almacenamiento es t tico:
template<class T> long SerialNumbered<T>: : counter = 1
class Basic {
string value
public:
};
vaid set (string val) { value
string get () { return value
int main () {
val; )
TimeStamped<SerialNumbered<Basic> > mixinl, mixin2
mixinl.set("test string 1");
mixin2 . set ( " test string 2 t' ) ;
caut mixin1. get () " " mixin1 . getStamp ()
" " mixin1. getSerialNumber () endl;
caut mixin2. get () " " mixin2. getStamp ()
" " mixin2. getSerialNumber () endl;
1* Output: (Sample)
test string 1 1129840250 1
test string 2 1129840250 2
* /// ,-
En maine ), el tipo resultame de mixin I y mixin2 tiene todos los mtodos de los tipos que se han usado en la mezcla.
Podemos considerar un mixin como una especie de func in que establece una correspondencia entre clases existentes y una
serie de nuevas subclases. Observe lo fcil que resulta crear un mixin utilizando esta tcnica; bsicamente, nos limitamos a
decir lo que queremos y el compilador se encarga del resto:
TimeStamped<SerialNumbered<Basic> > mixin1, mixin2;
460 Piensa en Java
Lamentablemente, los genricos de Java no penniten esto. El mecanismo de borrado de tipos hace que se pierda la infonna-
cin acerca del tipo de la clase base, por lo que una clase genrica no puede heredar directamente de un parmetro genrico.
Mezclado de clases utilizando interfaces
Una solucin comnmente sugerida consiste en utilizar interfaces para generar el efecto de los mixins, de la f0n11a siguiente:
11: generics/Mixins.java
import java.util.*;
interface TimeStamped { long getStamp (); }
class TimeStampedlmp implements TimeStamped
private final long timeStamp;
public TimeStampedlmp () {
timeStamp = new Date() .getTime{);
public long getStamp() { return timeStamp
interface SerialNumhered { long getSeriaINumber(); }
class SerialNumberedlmp implements SerialNumbered
private static long counter = 1
private final long serialNumber = counter++
public long getSerialNumber () { return serialNumber;
interface Basic {
public void set(String val);
public String get()
class Basiclmp implements Basic
private String value;
public void set (String val) { value
public String get() { return value
class Mixin extends Basiclmp
implements TimeStamped, SerialNumbered
val; }
private TimeStamped timeStamp = new TimeStampedlmp();
private SerialNumbered serialNumber =
new SeriaINumberedlmp()
public long getStamp() { return timeStamp.getStamp();
public long getSerialNumber () {
return seriaINumber.getSerialNumber();
public class Mixins {
public static void main(String(] args)
Mixin mixinl = new Mixin(), mixin2 new Mixin();
mixinl. set ( " test string 1
11
);
mixin2.set("test string 211);
System. out. println (mi xinl. get () + " 11 +
mixinl. getStamp () + 11 11 + mixinl. getSerialNumber () ) i
System. out. println (mixin2 . get () + " " +
mixin2. getStamp () + 11 11 + mixin2. getSerialNumber () )
/* Output: {Sample}
test string 1 1132437151359 1
cest string 2 1132437151359 2
*///0-
15 Genricos 461
La clase M i ~ i est utilizando, bsicamente, el mecanismo de delegacin, por lo que cada uno de los tipos mezclados
requiere un campo en Mixin, y es necesario escribir todos los mtodos que se precisan en Mixin para redirigir las llamadas
al objeto apropiado. Este ejemplo utiliza clases triviales, pero en un mixin ms complejo el cdigo puede llegar a crecer de
tamao de fonn3 bastante rpida.
4
Ejercici o 37: (2) Aada una nueva clase mixin Colored a Mixins.java, mzclela en Mixio y demuestre que funciona.
Utilizacin del patrn Decorador
Cuando examinamos la forma en que se utiliza, el concepto de mixin parece estar estrechamente relacionado con el patrn
de diseo Decorador.
5
Los decoradores se utilizan a menudo en aquellas ocasiones donde, para poder satisfacer cada una
de las combinaciones posibles, el mecanismo simple de creacin de subclases produce tantas clases que llega a resultar poco
prctico.
El patrn Decorador utiliza objetos en di stintos niveles para aadir responsabilidades, de fomla dinmica y transparente, a
distintos objetos indi viduales. El Decorador especifica que todos los objetos que envuelven al objeto inicial de partida tie-
nen la misma interfaz bsica. En cierto modo, lo que hacemos es definir que un cierto objeto es "decorable" y luego agre-
garle funcionalidad por el mtodo de envolver alrededor de ese objeto otra serie de clases. Esto hace que el uso de los
decoradores sea transparente: existe un conjunto de mensajes comunes que podemos enviar a un objeto, independi entemen-
te de si ste ha sido decorado o no. La clase decoradora tambin tener mtodos pero, como veremos, esta posibilidad tiene
sus limitaciones.
Los decoradores se implementan utilizando los mecani smos de composicin juma con estructuras fonnales (la jerarqua de
objetos decorables/decoradores), mientras que los mixins estn basados en el mecanismo de herencia. Por tanto, podriamos
decir que los mixins basados en tipos parametrizados son una especie de mecanismo genrico decorador que no requiere que
se utilice la estructura de herencias definida en el patrn de diseo Decorador.
El ejemplo anterior puede rehacerse con el patrn de diseo Decorador:
/1: generics/decorator/Decoration.java
package generics.decorator
import java.util.*
class Basic {
private String value
public void set (String val ) { value
public String get () { return value
class Decorator extends Basic {
protected Basic basic
val; }
public Decorator (Basic basic) { this .basic = basic
public void set{String val) { basic.set{val) }
public String get() { return basic.get{) }
class TimeStamped extends Decorator
private final long timeStamp
public TimeStamped (Basic basic) {
4 Observe que algunos entornos de programacin, como Eclipse e lntelliJ Idea, generan automticamente el cdigo de delegacin.
5 Los patrones se tratan en Thinking in Pal1ems (with Java), que puede encontrar en www.MindView.ner. Consulte tambin Design Partems, de Erieh
Gamma er al. (Addison- Wesley, 1995).
462 Piensa en Java
super{basic) ;
timeStamp = new Date{) .getTime ();
public long getStamp () { return timeStamp;
class SerialNumbered extends Decorator {
private static long counter = 1;
private final long serialNumber = counter++ ;
public SerialNumhered (Basic basic) { super (basic) ;
public long getSerialNumber () { return serialNumber;
public class Decoration {
public static void main (String [1 args) {
TimeStamped t = new TimeStamped(new Basic());
TimeStamped t2 = new TimeStamped(
new SerialNumbered(new Bas i c()));
II! t2.getSerialNumber () ; II No disponible
SerialNumbered s = new SerialNumbered{new Basic ()) ;
SerialNumbered s2 = new SerialNumbered(
new TimeStamped (new Basic ())) ;
II! s2.getStamp(); II No disponible
}
111 ,-
La clase resultante de un mixin contiene todos los mtodos de inters, pero el tipo de objeto que resulta de la utilizacin de
decoradores es el tipo con que el objelO haya sido decorado. En otras palabras. aunque es posible aadir ms de un nivel, el
tipo real ser el ltimo de esos niveles, de modo que slo sern visibles los mtodos de ese nivel fina l: por el contrario,
el tipo de un mixin es lodos los tipos que se hayan mezclado. En consecuencia, una desventaja significativa del patrn de
diseo Decorador es que slo trabaja, en la prctica. con uno de los ni veles de decoracin (el nivel final) mientras que la
tcnica basada en mixin resulta bastante ms natura l. Por tamo, el patrn de diseilo Decorador slo constituye una solucin
limitada para el problema que los mixins abordan.
Ejercicio 38: (4) Cree un sistema Decorador simpl e comenzando con una clase que represeme un caf normal y luego
proporcionando una serie de decoradores que representen la leche, la espuma, el chocolate. el caramelo y
la crema batida.
Mixins con proxies dinmicos
Resulta posibl e utili zar un pro.\)' dinmico para un mecanismo que pennita modelar los mixins de forma ms precisa que lo
que se puede conseguir utilizando el patrn de diseiio Decorador (consulte el Captulo 14, Informacin de tipos, para ver
una explicacin acerca de cmo funcionan los proxies dinmicos en Java). Con un prox)' dinmico, el tipo dinmico de la
clase resultante es igual a los tipos combinados que hayamos mezclado.
Debido a las restriCCIOnes de los proxies dinmicos, cada una de las clases que intervengan en la mezcla deber ser la imple-
mentacin de una interfaz:
11 : generics/DynamicProxyMixin.java
import java . lang .reflect.*;
import java.util .*;
import net.mindview.util.*;
import static net.mindview.util.Tuple.*;
class MixinProxy implements InvocationHandler
Map<String,Object> delegatesByMethod;
public MixinProxy(TwoTuple<Object,Class<? ... pairs)
delegatesByMethod = new HashMap<String,Object>();
for(TwoTuple<Object,Class<? pair : pairs) {
for(Method methad : pair.second.getMethods(
String methodName = method.getName();
// La primera interfaz del mapa
1/ implementa el mtodo.
if (!delegatesByMethod.containsKey(methodName)}
delegatesByMethod.put(methodName, pair.firstl;
public Object invoke(Object proxy, Methad methad,
Object[J argsJ throws Throwable {
String methodName = method . getName();
Object de l egate = delegatesByMethod.get(methodName);
return method. invoke (delegate, args) i
@SuppressWarnings (Hunchecked!l)
public static Object newlnstance{TwoTuple ... pairs)
Class [] interfaces = new Class [pairs . length) ;
for(int i = O; i <: pairs . length; i++} {
interfaces [i] = (Class) pairs (i] . second
Cl assLoader el =
pairs(O) . first . getClass() . getClassLoader()
return Proxy . newProxylnstanee(
el, interfaces, new MixinProxy(pairs))
public class DynamicProxyMixin {
public static void main(String[] args)
Ob ject mixi n = MixinProxy. newInstance(
tuple(new Basic I mp(), Basic.class),
tuple(new TimeStampedI mp(), TimeStamped.class),
tuple(new SerialNumberedImp() ,SerialNumbered . class));
Basic b = (Basic)mi x in
TimeStamped t = (TimeStamped)mixin
SerialNumber ed s = (Se rialNumbered)mixin
b . set ( "Hello")
System. out.println(b.get()
System. out . println(t . getStamp())
System.out.println(s.getSerialNumber() ;
/ * Output : (Sample)
Hello
113251 9137015
1
, /// , -
15 Genricos 463
Puesto que el nico que incluye todos los tipos mezclados es el tipo dinmico y no es tipo esttico, esta solucin sigue sin
ser tan elegante como la utilizada en C++, ya que nos vemos obligados a realizar una especializacin sobre el tipo apropia-
do antes de que podamos invocar ningn mtodo del mi smo. Sin embargo, esta solucin est significativamente ms prxi-
ma que las anteriores a lo que es el concepto de un verdadero mixi11.
Es mucho el trabajo que se ha realizado para poder soportar mixins en Java, incluyendo la creacin de una extensin del len-
guaje (el lenguaje Jam) que se ha definido especficamente con el objeto de soportar los mixins.
Ejercicio 39: (1) Aada una nueva clase mixin Colored a DynamicProxyMixin.java, mzclela en mixin, y demuestre
que funciona.
464 Piensa en Java
Tipos latentes
Al principio de este capitulo hemos introducido la idea de escribir cdigo que pueda ser aplicado de la forma ms general
posible. Para hacer esto, necesitamos fonnas de relajar las restricciones que afectan a los tipos con los que nuestro cdigo
puede trabajar. sin por ello perder los beneficios proporcionados por el mecanismo de comprobacin esttica de tipos. Por
eso, somos capaces de escribir cdigo que puede utilizarse, sin modificaciones, en el nmero mayor de siruaciones; en otras
palabras, cdigo ' "ms genrico".
Los genricos de Java parecen dar un paso adicional en esta direccin. Cuando escribimos o utilizamos genricos que sim-
plemente se emplean para almacenar objetos, el cdigo funciona con cualquier tipo de datos (salvo con las primitivas, aun-
que como hemos visto, el mecanismo de conversin automtica solventa este problema). Dicho de otra manera, los
genricos de "almacenamiento" son capaces de decir: "No me importa de qu tipo eres". El cdigo al que no le preocupa el
tipo de dato con el que funciona puede aplicarse, ciertamente, en cualquier situacin y es, por tanto, bastante "genrico".
Como tambin hemos visto, surge un problema en el momento en el que queremos realizar manjpulaciones con los tipos
genricos (distintas de la invocacin de mtodos de ObjecI), porque el mecanismo de borrado de tipos requiere que espe-
cifiquemos los lmites que pueden usarse para los tipos genricos, con el fin de poder invocar de manera segura mtodos
especficos para los objetos genricos dentro del cdigo. sta es una limitacin significativa al concepto de "genrico",
porque es necesario restringir los tipos genricos de modo que puedan heredar de clases concretas o implementar interfaces
particulares. En algunos casos, puede que tenninemos generando en lugar de genricos una clase o interfaz normales, debi-
do a que un genrico con lmites puede no diferir de la especificacin de una clase o una interfaz.
Una solucin que proporcionan algunos lenguajes de programacin se denomina tipos latentes o tipos estructurales. Un tr-
mino ms coloquiai es el de lipos palo (dllck typing); este tnnino ha llegado a ser muy popular debido a que no acarrea el
bagaje que los otros trminos s acarrean. El tnnino proviene de la frase "si camina como un pato y se compona como un
pato, podemos tratarlo como un pato".
El cdigo genrico, nonnalmente, slo invoca unos cuantos mtodos de un tipo genrico, y los lenguajes con tipos latentes
relajan la restriccin (teniendo que producir cdigo ms genrico) requiriendo que tan slo un subconjunto de los mtodos
sea implementado, no una clase o interfaz concretas. Debido a esto, los tipos latentes penniten saltar las fronteras de las
jerarquas de clase, invocando mtodos que no fonnan parte de una interfaz comn. En la prctica, un fragmento de cdigo
podra decir: "No me importa de qu tipo eres, siempre y cuando di spongas de los mtodos speak() y silO". Al no reque-
rir un tipo especfico, el cdigo puede ser ms genrico.
Los tipos latentes son un mecanismo de organizacin y reutilizacin del cdigo. Con ellos, podemos escribir fragmentos de
cdigo que pueden reutilizarse ms fcilmente que si no se empleara el mecanismo. La organizacin y reutilizacin del cdi-
go son las herramientas fundamentales de la programacin infonntica: escribir el cdigo una sola vez, utilizarlo ms de una
vez y mantener el cdigo en un nico lugar. Al no vemos obligados a especificar una interfaz exacta con la que el cdigo
pueda operar, los tipos latentes nos penniten escribir menos cdigo y aplicar dicho cdigo ms fcilmente y en ms lugares.
Dos ejemplos de lenguajes que soportan el mecanismo de tipos latentes son Pytbon (que puede descargarse gratuitamente
de \Vww.Python.org) y C++.
6
Python es un lenguaje con tipos dinmicos (prcticamente todas las comprobaciones de tipos
se realizan en tiempo de ejecucin) y C++ es un lenguaje con tipos estticos (las comprobaciones de tipos se realizan en tipo
de compilacin), por lo que los tipos latentes pueden utilizarse tanto con las comprobaciones de tipo estticas como con las
dinmicas.
Si tomamos la descripcin anterior y la expresamos en Python, tendra el aspecto siguiente:
#: generics/DogsAndRobots.py
class Dog:
def speak(self):
print It Arf ! It
def sit (se lf ) :
print nSitting
n
de f reproduce (se 1 f ) :
pass
6 o ~ lenguajes Ruby y Smalhalk tambin soportan los tipos latentes.
class Robot:
def speak (self) :
print "Click!"
def sit (self) :
print "Clank!"
def oilChange (self) :
pass
def perform(anything):
anything.speak()
anything. si t ()
a = Dog()
b = Robot (1
perform(a)
perform(b)
#,-
15 Genricos 465
python utiliza el sangrado para detenniDar el mbito (as que no hacen falta smbolos de llaves) y un smbol o de dos pun-
tos para dar comienzo a un nuevo mbito. Un smbolo '#' .ndica un comentario que se extiende hasta el final de la lnea, al
igual que '//' en Java. Los mtodos de una clase especifican explcitamente, como primer argumento, el equivalente de la
referencia this, que en este lenguaje se denomina self por convenio. Las llamadas a constructor no requieren ninguna clase
de palabra clave "new". Y Python permite la existencia de funciones nonnales (es decir, funciones que no son miembro de
una clase), como pone de manifiesto la funcin perform() .
En perform(anylhing), observe que no se indica ningn tipo para for anylhing, y que anylhing es simplemente un identi-
ficador. Esa variable debe ser capaz de reali zar las operaciones que perform( ) le pida, por lo que hay una interfaz implci-
ta. Pero nunca hace falta escribir explcitamente dicha interfaz, ya que est latente. perform( ) no se preocupa de cul sea
el ti po de su argumento, as que podemos pasarle a esa funcin cualquier objeto siempre y cuando ste soporte los mtodos
,peak() y ,il(). Si pasamos a perform( ) un objeto que no soporte estas operaciones, obtendremos una excepcin en tiem-
po de ejecucin.
Podemos producir el mi smo efecto en e++:
// : generics/DogsAndRobots.cpp
class Dog {
public:
} ;
void speak (1 {)
void sitll {}
void reproduce (1 {)
class Robot {
public:
} ;
void speak () {}
void sitll {}
void oilChange{)
template<class T> void perform{T anything) {
anything.speak{) ;
anything.sit() i
int main ()
Oog di
Robot r;
perform (d) i
perform(r) ;
///,-
466 Piensa en Java
Tanto en Python como en C++, Dog y Robot no tienen nada en comn. salvo que ambos disponen de dos mtodos con sig.
naturas idnticas. Desde el punto de vista de los tipos. se trata de tipos completamente distintos. Sin embargo. a perform()
no le preocupa el tipo especfico de su argumento y el mecanismo de tipos latentes le pennite aceptar ambos tipos de objeto.
C++ verifica que pueda enviar dichos mensajes. El compilador proporcionar un mensaje de error si tralamos de pasar el
tipo incorreclO (estos mensajes de error han sido, histricamente. bastante amenazallles y muy extensos, y son la razn prin
cipal de que las plantillas C++ tengan una reputacin tan mala). Aunque ambos lo hacen en instantes distintos (C++ en tiem
po de compi lacin y Python en tiempo de ejecucin). los dos lenguajes garantizan que no se puedan utilizar incorrectamente
los tipos, y esa es la razn por la que decimos que los dos lenguajes sonfuer,emente (ipodas.
7
Los tipos latentes no afectan
al tipado fuerte.
Como el mecanismo de genricos se aadi a Java en una etapa tarda, no hubo la oportunidad de implementar un mecanis-
mo de tipos latentes. as que Java no tiene soporte para esta funcionalidad. Como resultado, puede parecer al principio que
el mecanismo de genri cos de Java es "menos genrico" que el de otros lenguajes que s soporten los tipos latentes.
8
Por
ejemplo, si tratamos de implementar el ejemplo anterior en Java! estaremos obligados a utilizar una clase o una interfaz y a
especificarlas dentro de una expresin de lmite:
11 : generics/Performs.java
public interface Performs {
void speak () ;
void sit();
/// ;-
11 : generics/DogsAndRobots.java
II No hay tipos latentes en Java
import typeinfo.pets.*
import static net.mindview.util.Print.*
class PerformingDog extends Dog implements Performs {
public void speak () { print ("Woof! ") i }
public void sit() { print ( tlSitting" ); }
public void reproduce () {}
class Robot implements Performs {
public void speak () { print ("Click! It) i
public void si t () ( print ( "Clank! "1; )
public void oilChange (1 ()
class Communicate {
public static <T extends Performs>
void perform(T performer)
performer.speak{) ;
performer . sit()
public class DogsAndRobots {
public static void main (String [] args ) {
PerformingDog d = new PerformingDog{);
7 Dado que se pueden utilizar proyecciones de tipos, que deshabilitan en la prctica el sistema de tipos, algunos autores argumentan que e++ es un len-
guaje dbilmente tipado, pero creo que esa opinin es exagerada. Probablemente sea ms justo decir que C++ es un lenguaje "fuertemente tipado" con una
pueTla trasera."
8 La implementacin de los mecanismos de Java utilizando el borrado de tipos se denomina, en ocasiones, mecanismo de lipos genricos de segunda clas{'.
Robot r : new Robot () ;
Communicate.perform( d J ;
Communicate.perform(r ) ;
/ * Output:
Woof!
Si tting
Cl i ck !
Clank!
, /// ,-
15 Genricos 467
Sin embargo. observe que perform( ) no necesita utili zar genricos para poder funcionar. Podemos simpl emente especifi-
car que acepte un objeto Performs:
JI : generics j SimpleDogsAndRobots.java
// Eliminacin del genrico, el cdigo sigue funcionando.
c l ass CommunicateSimply {
static void perform( Performs performer ) {
performer.speak {) ;
performer . sit () i
public class SimpleDogsAndRobots {
public static void main (String [) args ) {
CommunicateSimply.perform(new PerformingDog ( ;
CornmunicateSimply.perform(new Robot ( ;
/ * Qutput:
Woof!
Sitting
Click!
Clank!
, / // o-
En este caso, los genricos eran simpl emente innecesarios, ya que las clases estaban ya obligadas a implementar la interfaz
Performs.
Compensacin de la carencia de tipos latentes
Aunque Java no soporta el mecanismo de tipos latentes, resulta que esto no significa que el cdigo genrico con lmites no
pueda aplicarse a travs de diferentes jerarquas de tipos. En otras palabras: sigue siendo posible crear cdigo verdadera-
mente genrico. aunque hace falta algo de esfuerzo adicional.
Reflexin
Una de las tcnicas que podemos utili zar es el mecanismo de reflexin. He aqu el mtodo perform() que utili za tipos laten-
tes:
11 : generics / LatentReflection.java
II Utilizacin del mecanismo de reflexin para generar tipos latentes.
import java.lang.reflect. *
import static net.mindview.util.Print.*
II No implementa Performs:
class Mime {
public void walk.AgainstTheWind () {}
public void sit () { print ("Pretending te sit
tl
) }
468 Piensa en Java
public void pushlnvisibleWalls () {}
publ le String toString () { return "Mime 11 i
// No implementa Performs:
class SmartDog {
public void speak () { print ("Woof! ") ;
public void sit () { print ("Sitting");
public void reproduce () ()
class CornmunicateReflectively {
public static void perform{Object speaker)
Class<?> spkr = speaker.getClass{);
try (
try (
}
Methad speak = spkr.getMethod("speak
U
) i
speak . invoke (speaker) ;
catch {NoSuchMethodException el {
print (speaker + 11 cannot speak");
try (
Methad sit = spkr.getMethod( " sit
lt
);
sit.invoke{speakerl i
catch(NoSuchMethodException el {
print (speaker + " cannot si t ") i
catch{Exception el
throw new RuntimeException(speaker.toString(), el;
public class LatentReflection {
public static void main{String[J argsl {
CommunicateReflectively.perform(new SmartDog()) i
CommunicateReflectively_perform(new Robot());
CommunicateReflectively.perform(new Mime());
/ * Output:
Woof!
Sitting
Click!
Clank!
Mime cannot speak
Pretending to sit
*///,-
Aqu, las clases son completamente di sjuntas y no tenemos clases base (distintas de Object) ni interfaces en comn. Gracias
al mecanismo de reflexin, Communicat cReflectively.perform( ) puede establecer dinmicamente si los mtodos desea-
dos estn disponibl es e invocarlos. Incluso es capaz de gestionar el hecho de que Mime slo tiene uno de los mtodos nece-
sari os, cumpli endo parcialmente con su obj eti vo.
Aplicacin de un mtodo a una secuencia
El mecanismo de refl exin proporciona algunas posibilidades interesantes, pero rel egan todas las comprobaciones de tipos
a tiempo de ejecucin, por lo que resulta indeseabl e en muchas situaciones. Nonnalmente, siempre es preferible conseguir
que las comprobaciones de tipos se reali cen en ti empo de compilacin. Pero, es posibl e tener una comprobaci n de tipos
en ti empo de compilacin y tipos latentes?
15 Genricos 469
Examinemos un ej emplo donde se analiza este problema. Suponga que desea crear un mtodo apply( ) que pueda aplicar
cualquier mtodo a todos los objetos de una secuencia. sta es una situacin en la que las interfaces parecen no encajar. Lo
que queremos es apli car cualquier mtodo a una coleccin de obj etos, y las int erfaces introducen demasiadas restricciones
como para poder describir el concepto de "cualquier mtodo". Cmer podemos hacer esto en Java?
Ini cialmente, podemos resolver el problema medi ante el mecani smo de reflexin, que resulta ser bastante elegante gracias
a los varargs de Java SES:
ji : generics / Apply.java
II {main , ApplyTest}
i mport java.lang.reflect.*i
import java.util.*;
i mport static net.mindview.util.Print.*
public class Apply {
public static <T, S extends Iterable<? extends T
void apply (S seq, Method f, Object .. . args l {
try {
for (T t: seql
f . invoke (t, args);
catch(Exception el {
1/ Los fallos son errores del programador
throw new RuntimeException (e ) ;
class Shape {
public void rotate() {pr int(this + 11 rotate
ll
) }
public void resize (int newSize ) {
print (this + 11 resize 11 + newSize ) ;
c lass Square extends Shape {}
c lass FilledList<T> extends ArrayList<T>
public FilledList(Class<? extends T> type, int size) {
try (
for (int i = O; i < size i++ )
11 Presupone un constructor predeterminado:
add (type.newlnstance (
catch (Exception e l {
throw new RuntimeException (e ) ;
class ApplyTest
public static void main (String[] args ) throws Exception
List<Shape> shapes = new ArrayList<Shape>( ) ;
for (int i = O; i < 10 ; i++)
shapes.add(new Shape( )) ;
Apply. apply {shape s, Shape.class . getMethod ( "rotate" ) ;
Apply. apply (shapes,
Shape.class . getMethod("resize", int.class ) , 5);
List<Square> square s = new ArrayList<Square>( ) ;
for(int i = O; i < 10; i++)
squares.add (new Square( ) );
Apply. apply (squares, Shape . class.getMethod ( lIrotate") ) i
470 Piensa en Java
Apply.apply (squares,
Shape . class . getMethod ( "resize", int. c lass ) , S} i
Apply.apply (new FilledList<Shape> (Shape.class, lO } ,
Shape.class.getMethod ( t!rotate " ) ;
Apply. apply (new FilledList <Shape> (Sguare. class, 10) ,
Shape.class.getMethod ( "rotate
"
) ;
SimpleQueue<Shape> shapeQ = new SimpleQu eue<Shape> ()
for {int i = Di i < Si i++ )
shapeQ.add l new Shape l)) ;
shapeQ.add (new Square { ;
Apply. apply (shapeQ, Shape.class.getMethod ( lIrotate" }) i
/ * (Execute to see output ) * /// :-
En Apply, tenemos sllert e. porque se da la circunstanci a de que Java incorpora una interfaz Iterable que es utili zada por la
bibli oteca de contenedores de Java. Debido a esto, el mtodo apply() puede aceptar cualqui er cosa que implemente la inter-
faz Iterable, lo que incluye todas las clases Collection, como List. Pero tambin puede aceptar cualquier otra cosa, siem-
pre y cuando hagamos esa cosa de tipo Iterable: por ej emplo. la cl ase SimpleQueue definida a continuacin y que se utili za
en el ejemplo ant erior en main():
// : generics / SimpleQueue.java
/ / Un tipo diferente de contenedor que es Iterable
import java.uti l . *
public class SimpleQueue<T> implements Iterable<T> {
private Li nkedList<T> storage = new LinkedList<T> ()
public void add (T t ) { storage . offer ( t); }
public T get 1) { return storage . poll 1); }
public Iterator<T> iterator () {
return storage.iterator () i
}
1110-
En Apply.java, las excepciones se convierten a RuntimeException porque no hay mucha posibilidad de recuperarse de las
excepciones, en este caso, representan realmente errores del programador.
Observe que hemos tenido que incluir lmites y comodines para poder utili zar Apply y FilledList en todas las situaciones
deseadas. Pruebe a experimentar quitando los lmites comodines y descubrir que Apply y FilledList no funcionan en algu-
nas situaciones.
FilIedList representa un cierto dilema. Para poder utilizar un tipo, ste debe disponer de un constructor predeterminado (si n
argumentos). Java no ti ene ninguna fonlla de descubrir eso en ti empo de compilacin, asi que el problema se pasa a tiem-
po de ej ecucin. Una sugerencia muy comn para poder garanti zar la comprobacin de tipos en tiempo de ejecucin con-
siste en definir lma interfaz faetona que disponga de un mtodo que genere objetos, entonces FilledList aceptara di cha
interfaz en lugar de la "factora normal " correspondiente al tipo especificado. El pmblema con esto es que todas las clases
que se utili cen en FilledList deben entonces impl ementar esa interfaz factora. Y el caso es que la mayora de las clases se
crean sin tener conocimiento de nuestra interfaz, por lo que no pueden implementarla. Posterionnente veremos una posibl e
solucin utilizando adaptadores.
Pero la tcnica utilizada, consistente en emplear un indicador de tipo, constituye probablemente un compromiso razonable (al
menos como primera solucin). Con esta tcnica, utilizar algo como FilledList es lo suficientemente fcil como para que el
programador se sienta tentado de utilizarlo, en lugar de ignorarlo. Por supuesto, dado que los errores se descubren en tiempo
de ejecucin, ser necesario preocuparse de que dichos errores aparezcan lo antes posible durante el proceso de desarrollo.
Observe que esta tcnica basada en un indicador de tipos es la que se recomienda en diversos libros y artculos, como por
ejemplo el artculo Generics in the Java Programming Language, de Gilad Bracha
9
. En dicho artculo, el autor seiiala que:
9 Vase la cita al fina l de este captulo.
15 Genricos 471
"Se trata de una sintaxis que se utiliza intensivamente en las nuevas API para manipulacin de anotaciones, por ejemplo".
Sin embargo. en mi opinin. no todo el mundo se siente igual de cmodo al utilizar esta tcnica; algunas personas prefieren
emplear el mtodo de la factora que fue presentado anteriormente en este capitulo.
Asimismo. aunque la solucin de Java resulta bastante elegante. debemos observar que el LISO del mecanismo de reflexin
(aunque se ha mejorado significativamente en las ltimas versiones de Java) puede hacer que el programa sea ms lento que
las implementaciones no basadas en la reflexin. ya que hay demasiadas tareas que llevar a cabo en tiempo de ejecucin.
Esto no deberia impedir que empleramos esta solucin, al menos como primera sol ucin al problema (salvo que queramos
caer en el error de la optimizacin prematura), pero representa ciertamente una diferencia entre las dos tcnicas.
Ejercicio 40: (3) Aada un mtodo speak( ) a todas las clases de typeinfo.pets. Modifique Apply.java para invocar al
mtodo speak() para una coleccin heterognea de objetos Peto
Qu pasa cuando no disponemos de la interfaz correcta?
El ejemplo anterior aprovechaba el hecho de que la interfaz Iterable ya est disponible, siendo esa interfaz precisamente lo
que necesitbamos. Pero qu sucede en el caso general, cuando no existe todava una interfaz que se ajuste a nuestras nece-
sidades?
Por ejemplo, vamos a generalizar la idea de FilledList y a crear un mtodo fill() parametrizado que admita una secuencia
y la rellene utilizando un objeto Gcnerator. Al tratar de escribir esto en Java nos encontramos con un problema, porque no
existe ninguna interfaz adecuada "Addablc" (una interfaz que permita aadir objetos), mientras que en el caso anterior s
que tenamos ulla interfaz Iterable. Por tanto, en lugar de decir "cualquier cosa para la cual podamos invocar el mtodo
add( r', nos vemos forzados a decir "un subtipo de Collection". El cdigo resultante no es particulamlente genrico, ya que
debe restringirse para funcionar con implementaciones de Collection. Si tratamos de utilizar una clase que no implemente
Collection, el cdigo genrico no funcionar. He aqu el aspecto que tendra este ejemplo:
11: generics/Fill.java
II Generalizacin de la idea de FilledList
II {main, FillTest}
import java.util.*;
lINo funciona con tlcualquier cosa que tenga un mtodo add () ti. No hay
l/una interfaz "Addable
tl
, por lo que nos vemos limitados a
// utilizar un contenedor Collection. No podemos generalizar
/1 empleando genricos en este caso.
public class Fill {
public static <T:> ',oid fill (Collection<T:> collection,
Class<? extends T:> classToken, int size) {
for(int i ::: O; i < size i++)
// Presupone un constructor predeterminado:
try {
collection.add(classToken.newlnstance()) ;
catch (Exception e) {
throw new RuntimeException{e);
class Contract {
private static long counter = O;
private final long id = counter++i
public String toString () {
return getClass{) .getName() + " 11 + id;
class TitleTransfer extends Contract {}
472 Piensa en Java
class FillTest {
public static void main(String [] args ) {
List<Contract> contracts = new ArrayList<Contract>();
Fill.fill(contracts, Contract.class, 3} i
Fill.fill{contracts, TitleTransfer.class, 2);
for(Contract e: contracts)
System.out . println(c) i
SimpleQueue<Contract> contractQueue
new SimpleQueue<Contract>();
II No funciona. fill() no es lo suficientemente genrico:
II Fill.fill{contractQueue, Contract.class, 3);
1* Output:
Contract O
Contract 1
Contract 2
TitleTransfer 3
TitleTransfer 4
*11 1,-
Es en estas situaciones donde resulta ventajoso disponer de un mecanismo parametrizado con tipos latentes, porque de esa
fonna no estaremos a merced de las deci siones de diseo que hubiera tomado en el pasado cualquier diseador concreto de
bibliotecas; gracias a eso no tendremos que reescribir nuestro cdigo cada vez que nos encontremos con una biblioteca que
no hubiera tenido en cuenta nuestra situacin concreta (as que el cdigo ser verdaderamente "genrico"). En el caso n t e ~
rior, como los diseadores de Java no vieron la necesidad (lo cual resulta bastante natural) de agregar una interfaz
"Addable", estamos obligados a movernos dentro de la jerarqua Colleetion, y SimpleQueue no funcionar, an cuando
di sponga de un mtodo addQ. Dado que ahora est restringido a trabajar con Colleetion, el cdigo no es particularmente
"genrico". Con los tipos latentes este problema no se presentara.
Simulacin de tipos latentes mediante adaptadores
De modo que los genricos de Java no disponen de tipos latentes y necesitamos algo como los tipos latentes para poder escri-
bir cdigo que pueda aplicarse traspasando las fronteras entre las clases (es decir, cdigo "genrico"). Hay alguna forma
de salvar esta limitacin?
Qu es lo que no pennitira hacer los tipos latentes? Los tipos latentes implicaran que podramos escribir cdigo que dije-
ra: "No me importa qu tipo estoy usando, siempre y cuando ese tipo disponga de estos mtodos". En la prctica, los tipos
latentes crean una in/e/faz implcita que contiene los mtodos deseados. Por tamo, si escribimos la interfaz necesaria a mano
(ya que Java no lo hace por nosotros), eso debera resolver el problema.
Escribir cdigo para obtener una interfaz que necesitamos a partir de otra interfaz de la que di sponemos constituye un ejem-
plo del patrn de di seo Adaptador. Podemos utili zar adaptadores para adaptar las clases existentes con el fin de producir la
interfaz deseada, utilizando para ello una cantidad de cdigo relativamente pequea. La solucin, que utiliza la jerarqua
Coffee anterionnente definida, ilustra las diferentes fonnas de escribir adaptadores:
11 : generics/Fil12.java
II Utilizacin de adaptadores para simular tipos latentes.
II {main, Fil12Test}
import generics.coffee.*;
import java.util.*i
import net.mindview. util.*;
import static net . mindview. util.Print.*;
interface Addable<T> { void add(T t); }
public class Fil12 {
II Versin con indicador de clase:
public static <T> void fill{Addable<T> addable,
Class<? extends T> classToken, int size) {
)
for ( int i = O; i < size i++ )
try (
addable.add (classToken.newlnstance ()) ;
catch (Exception e l {
throw new RuncimeException (e ) ;
// Versin con generador :
public static <T> void fill {Addable<T> addable,
Generator<T> generator, int size ) {
for ( int i = O; i < size; iT+ )
addable.add (generator.next () ) ;
JI Para adaptar un tipo base, es necesario utilizar composicin.
JI Definir Addable como contenedor Collection usando composicin:
cl ass AddableCollectionAdapter<T> implements Addable<T> {
private Collection<T> C
public AddableCollectionAdapter (Collection<T> e l (
this . c = C;
public void add(T item) ( c . add(item) ; )
JI Un mtodo auxiliar para capturar el tipo automticamente :
c lass Adapter (
publie static <T>
Addable<T> colleetionAdapter (Colleetion<T> e )
return new AddableColleetionAdapter<T>(c ) ;
II Para adaptar un tipo especifico, podemos usar la herencia .
II Hacer Addable un contenedor SimpleQueue utilizando la herencia:
e lass AddableSimpleQueue<T>
extends SimpleQueue<T> implements Addable<T> (
public void add (T item) ( super.add (item) ; }
e lass Fil12Test (
publie statie vo id main (String (] args ) (
II Adaptar una coleccin:
List<Coffee> earrier = new ArrayList<Coffee> () ;
Fi1l2. till (
new AddableCollectionAdapter<Coffee> (earrier ) ,
Coffee.class, 3);
II El mtodo auxiliar captura el tipo :
Fill2.fill(Adapter.collectionAdapter(earrier) ,
Latte . elass, 2);
for(Coffee c: earrier)
print ( e) ;
print("---------------------- " ) ;
II Utilizar una clase adaptada:
AddableSimpleQueue<Coffee> coffeeQueue
new AddableSimpleQueue<Coffee> {) ;
FiI12.fill(eoffeeQueue , Mocha.class, 4);
Fill2 . fill(eoffeeQueue, Latte.class, 1);
for(Coffee c: coffeeQueue)
print (e ) ;
15 Genricos 473
474 Piensa en Java
/ * Output:
Coffee O
Coffee 1
Coffee 2
Latte 3
Latte 4
Mocha 5
Mocha 6
Mocha 7
Mocha 8
Latte 9
*///,-
Fill2 no requiere un objeto Collection a diferencia de FiII. En lugar de ello, slo necesita algo que implemente Addable, y
Addable ha sido escri ta preci samente para FiII, este ejemplo es una manifestacin del tipo latente que queramos que el
compi lador construyera por nosotros.
En esta versin, tambin hemos aadido un mtodo fill( ) sobrecargado que toma un objeto Generator en lugar de un indi-
cador de tipo. El objeto Gener.tor no presenta problemas de seguridad de tipos en tiempo de compilacin: el compilador
garantiza que lo que pasemos sea un objeto Gt'nerator vlido, as que no puede generarse ninguna excepcin.
El primer adaptador, AddableCollcctionAdapler, funciona con el tipo base Colleclion, lo que significa que puede utilizar-
se cualquier implementacin de Collection. Esta versin simplemente almacena la referenc ia a Colleetion y la utiliza para
implementar add( ).
Si disponemos de un tipo especfico en lugar de disponer de la clase base de una jerarqua, podemos escribir algo menos de
cdigo a la hora de crear el adaptador empleando el mecanismo de herencia, como puede verse en AddableSimpleQueue.
En Fill2Test.main( ), podemos ver cmo funcionan los diversos tipos de adaptadores. En primer lugar, se adapta un tipo
Collcclion con AddableCollcctionAdapler. Una segunda versin de esto utili za el mtodo auxili ar genrico, y podemos
ver cmo el mtodo genrico captura el tipo. as que no es necesario escribirl o explcitamente; se trata de un truco bastan-
te conveni ente, que nos permite escribir cdigo ms elegante.
A continuacin, se utiliza la clase AddableSimpleQucue pre-adaptada. Observe que en ambos casos los adaptadores per-
miten util izar con Fil12.fIl( ) las clases que previamente no impl ementaba Add.ble.
La utilizacin de adaptadores de esta fornla parece compensar la falta de un mecanismo de tipos latentes, por lo que podra
pensarse que podemos escribir cdigo genuinamente genrico. Sin embargo, se trata de un paso de programacin adicional
y es necesario que lo comprendan tanto el creador de la biblioteca como el consumidor de la misma; y este concepto puede
no ser entendido fcilmente por los programadores menos expertos. Los verdaderos mecani smos de tipos latentes permiten,
al eliminar este paso adicional, aplicar el cdigo genrico ms fcilmente, y ah radica precisamente su valor.
Ejercicio 41: ( 1) Modifique Fi112.j.va para utilizar las clases typeinfo.pels en lugar de las clases Coffee.
Utilizando los objetos de funcin como estrategias
Este ejemplo final nos pennitir cdigo verdaderamente genrico utilizando la tcnica de adaptadores descrita en la seccin
anterior. El ejemplo comenz con un intento de crear una suma de una secuencia de elementos (de cualqui er tipo que pueda
sumarse), pero tem1in evolucionando hacia la reali zacin de operaciones generales. usando un est ilo de programacin jim-
ciona/.
Si nos fijamos exclusivamente en el proceso de sumar objetos, podemos ver que este es un caso en el que tenemos opera-
ciones comunes entre clases, pero dichas operaciones no estn representadas en ninguna clase base que podamos especifi-
car: en ocasiones se puede incluso utili zar un operador <+' y otras veces puede haber algn tipo de mtodo "suma". sta es,
general mente la situacin con la que nos encontramos cuando tratamos de escribir cdigo genrico, porque lo que quere-
mos es que el cdigo se pueda apli car a mltiples clases; especialmente, como en este caso, mltiples clases que ya existan
y que no tengamos posibi lidad de "corregir". Incluso si restringiramos este ejemplo a las subclases de Number, di cha
superclase no incluye nada acerca de la "sumabil idad".
15 Genricos 475
La solucin consiste en utilizar el patrn de diseo de EstraTegia, que pennite obtener cdigo ms elegante porque asla
completamente "las cosas que cambian" dentro de un objelo deful1cin
lO
. Un objeto de funcin es un objeto que se com-
porta. en cierta manera, como una funcin: normalmente, existe un mtodo de inters (en los lenguajes que soportan el
mecanismo de sobrecarga de operadores, podemos hacer que la llamada a este mtodo pare=ca una llamada a mtodo nOf-
mal). El valor de los objetos de funcin es que, a diferencia de un mtodo normal , los podemos pasar de un sitio a olro y
tambin pueden tener un estado que persista entre sucesivas llamadas. Por supuesto, podemos conseguir algo como esto con
cua lquier mtodo de una clase, pero (al igual que con cualquier patrn de diseo) el objeto de funcin se distingue princi-
palmente por su intencin original. Aqu , la intencin es crear algo que se comporte como un nico mtodo que podamos
pasar de un sitio a otro; por tanto, est estrictamente acoplado (yen ocasiones es indistinguible de l). con el patrn de dise-
o de Esrrateg;a.
De acuerdo con mi experiencia con distintos patrones de diseo, las fronteras son un tanto difusas en este caso: lo que vamos
a hacer es crear objetos de funcin que realicen una cierta adaptacin, yesos objetos se van a pasar a una serie de mtodos
para utilizarlos como estrategias.
Adoptando este enfoque, en este ejemplo se aaden los diversos tipos de mtodos genricos que originalmente queramos
crear, as como algunos otros. He aqu el resultado:
11: generics/Functional.java
import java.math.*;
import java.util.concurrent . atomic.*
import java.util.*;
import static net.mindview.util.Print.*
II Distintos tipos de objetos de funcin:
interface Combiner<T> { T combine(T x, T yl
interface UnaryFunction<R,T> { R function(T x l
interface Collector<T> extends UnaryFunction<T,T>
T result(); II Extraer resultado del parmetro de recopilacin
interface UnaryPredicate<T> { boolean test(T x ) j
pubIic cIass Functional {
II Invoca al objeto Combiner para cada elemento con el fin de
II combinarlo con un resultado dinmico, devolvindose
II al final el resultante:
pubIic static <T> T
reduce{Iterable<T> seq, Combiner<T> combiner) {
Iterator<T> it = seq.iterator() i
}
if (i t . hasNext ()) {
T result = it.next() i
while{it.hasNext())
resul t = combiner. combine (resul t, i t. next () ) ;
return result;
II Si seq es la lista vaca:
return null; II O generar una excepcin
II Tomar un objeto de funcin e invocarlo para cada objeto de
II la lista, ignorando el valor de retorno. El objeto de funcin
II puede actuar como un parmetro de recopilacin, as que
II se lo devuelve al final.
public static <T> Collector<T>
forEach (Iterable<T> seq, Collector<T> tune ) {
tor (T t : seq)
func.tunction(t) ;
10 En ocasiones, podni. ver que a estos objetos se los denominafimclores. En este texto, utilizaremos el trmino objeto defllllci" en lugar defimctor, ya
que el tnnino "functor" tiene un significado diferente y muy especfico en matemticas.
476 Piensa en Java
return func;
// Crea una lista de resultados invocando un objeto
// de funcin para cada objeto de la lista :
public static <R,T> List<R>
transform (Iterable<T> seq, UnaryFunction<R, T> func ) {
List<R> result = new ArrayList<R> () ;
for (T t : seg)
result.add (func.function (t )) ;
return resul t;
// Aplica un predicado unario a cada elemento de una secuencia y devuelve
/ / una lista con los elementos que han dado como resultado "true":
public static <T> List<T>
filter ( Iterable<T> seg, UnaryPredicate<T> pred) {
List<T> result = new ArrayList<T>();
fer (T t , seq)
if (pred.test(t ) )
result.add (t ) ;
return result;
// Para utilizar los anteriores mtodos genricos, necesitamos crear
// objetos de funcin para adaptarlos a nuestras necesidades concretas:
static class IntegerAdder implements COmbiner<Integer>
public Integer combine (Integer x, Integer y) {
return x + y;
static class
IntegerSubtracter implements Combiner<Integer> {
public Integer combi ne ( Integer x, Integer y ) {
return x - y;
static class
BigDecimalAdder implements COmbiner<BigDecimal> {
public BigDecimal cOmbine (BigDecimal x, BigDecimal y ) {
return x. add (y ) ;
static class
BiglntegerAdder implements Combiner<Biglnteger> {
public Biglnteger cOmbine (Biglnteger x, Biglnteger y)
return x.add(y) ;
static class
AtomicLongAdder implements Combiner<AtomicLong> {
public AtomicLong cOmbine (AtomicLong x, AtomicLong y ) {
// No est claro si esto es significativo:
return new AtomicLong (x.addAndGet (y.get( ))) ;
/ I Podemos incluso hacer una funcin unaria con un Itulp"
/ / (Units in the last place, las unidades en el ltimo lugar ) :
static class BigDecimalUlp
implements UnaryFunction<BigDecimal,BigDecimal> {
public BigDecimal function (BigDecimal xl {
return x.ulp() i
static class GreaterThan<T extends Comparable<T
implements UnaryPredicate<T> {
private T bound
public GreaterThan(T bound)
public boolean test (T xl {
this.bound
return x. compareTo (bound) > Di
static class MultiplyinglntegerCollector
implements Collector<Integer> {
private Integer val = 1
public Integer function(Integer xl {
val *= Xi
return val;
public Integer result () { return val; }
public static void main{String[] args)
bound; }
15 Genricos 477
// Genricos, varargs y conversin automtica funcionando conjuntamente:
List<Integer> li == Arrays,asList{l, 2, 3, 4, 5, 6, 7};
Integer result = reduce (li, new IntegerAdder() j
print(result) i
result = reduee(li, new IntegerSubtracter ()) i
print(result) i
print(filter(li, new GreaterThan<Integer>(4))) i
print(forEaeh(li,
new MultiplyinglntegerCollector()) .result()) i
print(forEaeh(filter(li, new GreaterThan<Integer>(4)),
new MultiplyinglntegerCollector () ) . result () ) ;
MathContext me = new MathContext(7)
List<BigDecimal> lbd = Arrays.asList{
new BigDecimal(1.1, me), new BigDeeimal{2.2, me),
new BigDecimal(3.3, me), new BigDeeimal(4.4, me));
BigDeeimal rbd = reduce (lbd, new BigDeeimalAdder());
print (rbd) i
printlfilterllbd,
new GreaterThan<BigDecimal>(new BigDeeimal(3)))}
II Utilizar la funcionalidad de generacin de primos de Biglnteger:
List<Biglnteger> lbi = new ArrayList<Biglnteger>();
Biglnteger bi = Biglnteger.valueOf{11);
for{int i = O; i < 11; i++} {
lbi. add Ibi I ;
bi = bi.nextProbablePrime(} i
print (lbi) i
Biglnteger rbi
print (rbi) i
reduce (lbi, new BiglntegerAdder{));
478 Piensa en Java
JI La suma de esta lista de primos tambin es prima:
print(rbi,isProbablePrime(S) ;
List<AtomicLong> lal = Arrays,asList (
new AtornicLong(ll), new AtomicLong(47) ,
new AtomicLong (74), new AtomicLong(133;
AtomicLong ral = reduce (lal, new AtomicLongAdder());
print (ral ) ;
print(transform(lbd, new BigDecimalUlp (})) i
/ * Output:
28
-26
(5, 6, 71
5040
210
11.000000
(3.300000, 4.4000001
[11, 13, 17, 1 9, 23, 29, 31, 37, 41, 43, 47 ]
311
true
265
(0.000001, 0 . 000001, 0 . 000001, 0 . 0000011
* ///,-
El ejemplo comienza definiendo interfaces para distintos tipos de objetos de funcin. Estas interfaces se han creado a medi-
da que eran necesarias mientras se desarrollaban los diferentes mtodos y se descubra la necesidad de cada una. La clase
Combner me fue sugerida por un contribuidor annimo a uno de los artculos que publiqu en mi sitio web. Combiner
abstrae los detalles especficos relativos a tratar de sumar dos objetos y se limita a enunciar que esos objetos estn siendo
combinados de alguna manera. Corno resultado, podemos ver que IntegerAdder y IntegerSubtraeter pueden ser tipos de
Combiner.
Una funcin unario (UnaryFunetion) toma un nico argumento y produce un resultado, el argumento y el resultado no tie-
nen por qu ser del mi smo tipo. Se utili za un elemento Collector como "parmetro de recopilacin" y podemos extraer el
resultado una vez que hayamos acabado. Un predicado UnaryPredicate produce un resultado de tipo booleano Hay otros
tipos de objetos de funcin que pueden definirse. pero estos son suficientes para entender el concepto.
La clase Functional contiene Wla serie de mtodos genricos que aplican objetos de funcin a secuencia. El mtodo
reduce() aplica la funcin contenida en un objeto Combiner a cada elemento de una secuencia con el fin de producir un
nico resultado.
forEaeh() toma un objeto Colleetor y aplica su funcin a cada elemento, ignorando el resultado de cada llamada a funcin.
Podemos invocar este mtodo simplemente debido a su efecto colateral (lo que no encajara con un estilo de programacin
'funcional" pero puede, a pesar de todo, ser til), o bien el objeto Colleetor puede mantener el estado interno para actuar
como un parmetro de recopilacin, como es el caso en este ejemplo.
transform( ) genera una lista invocando una funcin UnaryFunetion sobre cada objeto de la secuencia y capturando el
resultado.
Finalmente, filter( ) aplica un predicado UnaryPredieate a cada objeto de una secuencia y almacena los objetos que pro-
ducen true en un contenedor List. que luego se devuelve.
Podemos definir funciones genricas adicionales. La biblioteca estndar de plantillas de C++, por ejemplo, dispone de mul-
titud de ellas. El problema tambin se ha resuelto con unas bibliotecas de cdigo abierto, como por ejemplo JGA (Generic
A Igorilhms for Java).
En C++, el mecanismo de tipos latentes se encarga de establecer la correspondencia entre las operaciones cuando se invo-
can las funciones, pero en Java necesitamos escribir los objetos de funcin para adaptar los mtodos genricos a nuestras
necesidades concretas. Por tanto, la siguiente parte de la clase muestra diferentes implementaciones de los objetos de fun-
cin. Observe, por ejemplo, que IntegerAdder y BigDecimalAdder resuelven el mi smo problema, (sumar dos objetos),
15 Genricos 479
in\'ocando las operaciones apropiadas para su tipo concreto. ste es un ejemplo de combinacin de los patrones de diseo
Adaptador y Estrategia.
En main( ). podemos ver que en cada llamada a mtodo se pasa una secuencia junto con el objeto de funcin apropiado.
Asimismo. \emos que hay expresiones que pueden llegar a ser bastante complejas, como por ejemplo:
f orEach (filter {li, new GreaterThan (4 )) ,
new MultiplyinglntegerCollector ()) .result ()
Esta expresin genera una lista seleccionando todos los elementos de li que sean mayores que 4, y luego aplica el mtodo
MultiplyinglntegerCoUector() a la lista resultante y extrae el resultado con resul t(). No vamos a explicar los detalles del
resto del cdigo, aunque el lector no debera tener problemas en comprenderlo sin ms que analizarlo.
Ejercicio 42: (5) Cree dos clases separadas, que no tengan nada en comn. Cada clase debe almacenar un valor y dis-
poner al menos de mtodos que produzcan dicho valor y pennitan reali zar una modificacin del mismo.
Modifique FUDctional.java para que realice operaciones funcionales sobre colecciones de dichas dos cIa-
ses (estas operaciones no tienen que ser aritmticas como son las de Functional.java).
Resumen: realmente es tan malo el mecanismo de proyeccin?
Habiendo estado trabajando en explicar las plantillas de C++ desde que stas fueron concebidas, probablemente yo baya
estado haciendo la pregunta que da ttulo a esta seccin durante ms tiempo que la mayora de los dems autores. Pero slo
recientemente me he detenido realmente a pensar hasta qu punto esta pregunta es vlida en muchas situaciones: cuntas
veces merece la pena complicar las cosas para el problema que estamos tratando de describir?
Podramos argumentar de la fonna siguiente, Uno de los lugares ms obvios para utilizar un mecani smo de tipos genricos
es con clases contenedores tales como List, Set, Map, etc" es decir con las clases que ya hemos visto en el Captulo 11,
Almacenamiento de objetos , y de lo que hablaremos ms en detalle en el Captulo 17, Anlisis detallado de los contenedo-
res. Antes de Java SE5, cuando incluamos un objeto en un contenedor, ste se generalizaba a Obj ect , perdindose as la
infomlacin de tipos. Cuando se quera extraerlo de nuevo para hacer algo con l, era necesario volver a proyectarlo sobre
el tipo apropiado. Un ejemplo sera una lista Li st de objetos Cat (gatos). Sin la versin genrica de los contenedores intro-
ducida en Java SE5, lo que bararnos seria introducir objetos de tipo Object y extraer objetos de tipo Object , con lo cual
resulta perfectamente posible insertar un perro (Dog) en una lista de objetos Cat .
Sin embargo, lo que las versiones de Java anteriores a la aparicin de genricos no nos pennitan era mal utilizar los obje-
tos introducidos en un contenedor, Si introducimos un objeto Dog en un contenedor donde estamos almacenando objetos
Cat y luego intentamos tratar todo lo que hay en el contenedor como si fuera un objeto Cat , se obtiene una excepcin
Ru ntimeException al extraer la referencia Dog del contenedor Cat y tratar de proyectarla sobre Cal. As pues, el proble-
ma lennina por descubrirse; la nica desventaja es que se descubra el problema en tiempo de ejecucin en lugar de en tiem-
po de compi lacin,
En las ediciones anteriores del libro, yo deca que:
Esto no es slo una molestia, En ocasiones, puede dar lugar a errores de pmgramacin difici-
les de de/ec/m: Si una parte de un programa (o varias par/es de un pmgrama) inserta objetos
en un contenedor y lo nico que podemos descubrir en una parte separada del programa, por
la generacin de una e..tcepcin, es que se ha introducido un objeto incorrecto dentm del con-
tenedOl; entonces nos vemos obligados a averiguar en qu punto se ha producido la insercin
incorrecta,
Sin embargo, despus de examinar de nuevo la cuestin, comenc a pensar en ella, En primer lugar, con qu frecuencia se
produce este problema? Personalmente, no recuerdo que nunca me haya pasado algo as Y. cuando he preguntado a la gente
que asista a mis conferencias. tampoco he logrado encontrar a nadie que me dijera que a ell os le haba pasado. En otro libro
sobre el tema, se inclua un ejemplo de una lista denominada files que contena objetos String; en este ejemplo, pareca com-
pletamente natural anadir un objeto Fil e (archivo) a fil es, por lo que babria sido mejor denominar al objeto liIeNames (nom-
bres de archivo). Independientemente de lo exhaustivos que sean los mecani smos de comprobacin de tipos de Java, sigue
siendo posible escribir programas enrevesados, y el hecho de que un programa mal escrito se pueda compil ar no quiere decir
que deje de ser un programa mal escrito, Qui z la mayora de los programadores utilicen contenedores con nombres apro-
480 Piensa en Java
piados como "cats" que proporcionan una advertencia visual al programador que est intentado aadir un objeto que no sea
del tipo Cato E incluso si llegara a darse el caso de que alguien introduzca el objeto incorrecto, duranre cunto tiempo per-
manecera ocult o ese problema antes de ser descubierto? Parece lgico pensar que, tan pronto como empezramos a ejecu-
tar pruebas con datos reales, se generara rpidamente una excepcin.
Un detenninado autor ha llegado a deci r que dicho problema podra 'pennanecer oculto durante aos", pero yo no he podi-
do encontrar infom1cs que habl en de personas que tengan grandes difi cultades para encontrar crrores del tipo "perro en una
li sta de gatos ", ni tampoco he encontrado infonnes donde se diga que ese problema se produce muy a menudo. Mientras
que con las hebras de programacin, como veremos en el Captul o 2 1, Concurrencia. resulta bastante senci llo y comn que
haya errores que slo se manifiesten de fomla muy infrecuente, y que slo nos proporcionan una vaga indicacin de qu es
lo que anda mal, en este otro ejempl o de la insercin de objetos errneos, las cosas no son as. Por tanto, es el probl ema
de la insercin de objetos errneos la razn de que todas estas funcionalidades tan significati vas y complejas hayan sido aa-
didas a Java?
En mi opini n. la intencin de esa funcionalidad del lenguaje de propsito general denominada "genricos" (no necesaria-
mente de la implementacin concreta que Java hace) es la expresividad, no simplemente la creacin de contenedores que
sean seguros en lo que respecta a los tipos de datos. La posibilidad de di sponer de contenedores que sean seguros respecto
a los tipos de datos se obtiene corno efecto colateral de la capacidad de crear cdigo que tenga un propsito ms general.
Por tanto, aunque el argumento de la insercin de tipos incorrectos en UDa lista se utiliza a menudo para justificar la exis-
tencia de los genricos, dicho argumento resulta cuestionabl e. Y, como decamos al principio del captulo, no creo que el
concepfo de genricos tenga nada que ver con ese probl ema. Por el contrario, los genricos, como su propio nombre indi-
ca, constituyen una fonna de escribir cdigo ms "genrico" y que est menos restringido por los tipos de datos con los que
pueda trabajar, de manera que un mismo fragmento de cdigo pueda aplicarse a una mayor cant idad de tipos de datos. Como
hemos visto en este captulo, resulta fcil escribir clases "contenedoras" verdaderamente genricas (es decir, lo que son los
contenedores de Java), pero escribir cdigo que manipul e sus tipos genricos requiere un esfuerzo adicional tanto por parte
del creador de la clase, como por parte del consumidor de la misma, que debe comprender el concepto y la impl ementaci n
del patrn de di seo Adaptador. Dicbo esfuerzo adicional reduce la facilidad de uso de esta funcionalidad y puede, por tanto,
hacer que sea menos aplicable en diversos lugares en los que podra sin embargo representar un valor aadido.
Observe tambi n que como los genricos fueron introducidos de manera bastante tarda en Java en lugar de haber sido
incluidos en el lenguaje desde el principio, algunos de los cont enedores no pueden ser tan robustos como deberan. Por ejem-
plo, fijese en Map, y en panicular en los mtodos containsKey(Object key) y get(Object key). Si estas clases hubieran
sido diseadas con genri cos previamente existentes, estos mtodos hubieran utili zado tipos parametrizados en lugar de
Object, permitiendo as las comprobaciones en tiempo de compilacin que se supone que los genricos deben proporcio-
nar. En los mapas de e++ por ejemplo, el tipo de la clave se comprueba siempre en tiempo de compil acin.
Hay una cosa muy clara: introducir cualquier tipo de mecani smo genrico en una versin posterior del lenguaje despus de
que ese lenguaje haya ll egado a ser de uso general, conduce a situaciones extremadamente li osas, y es imposible cumplir el
objetivo sin un esfuerzo enonne. En C++, las plantill as fueron introducidas en la versin ISO ini cial del lenguaj e (aunque
incluso eso fue causa de cierta confusin, porque ya se estaba usando una versin anterior, sin pl antill as, antes de que el pri-
mer estndar de C++ apareciera), por lo que, en la prctica, las plantill as fueron siempre una pane del lenguaje. En Java, los
genri cos no se introdujeron hasta casi 10 aos despus de que el lenguaje empezara a utilizarse, por lo que los problemas
con la migracin hacia los genricos son considerables y han tenido un impacto signifi cati vo en el diseo del propio meca-
nismo de genricos. El resultado es que nosotros, los programadores, tenemos que sufrir ahora las consecuencias derivadas
de la falta de visin de los diseadores de Java que crearon la versin 1.0. Cuando Java se di se originalmente, los dise-
adores tenan conocimi ento, por supuesto, acerca de las plantillas C++. E incluso consideraron incluirlas en el lenguaje,
pero por alguna razn decidieron dejarlas fuera (lo que probablemente indica que tenan prisa por terminar el di seo). Como
resultado, tanto el lenguaje corno los programadores que lo emplean tienen que enfrentarse con una serie de problemas deri-
vados de esa omisin. Slo el tiempo nos dir cul es el impacto final sobre el lenguaje que tendr la solucin que Java ha
adoptado para el tema de los genricos.
Algunos lenguaj es, y en especial Nice (vase hllp://nice.sollrceforge.net; este lenguaje genera cdigo intennedio Java y fun
ciona con las bibliotecas Java existentes) y NextGen (vase hllp://japan.cs.rice.edu/nexlgell) han incorporado otras solucio-
nes ms limpi as y menos problemticas para el tema de los tipos parametrizados. No resulta posible imaginar que uno de
estos lenguajes llegue a ser el sucesor de Java, porque ambos han adoptado el mi smo enfoque exacto que C++ adopt con
respecto C: usar aquello que estaba di sponible y mejorarlo.
15 Genricos 481
Lecturas adicionales
El documento de carcter introductorio para los genricos es Generics in fhe Java Programming Language, de Gi lad Bracha,
que puede encontrar en http://java.slIll.com/j2sel/ .5/pdflgenerics-1lIIoria/pdf
Java Generics FAQs de Angelika Langer es un recurso muy til. Puede encontrarlo en \Vw\v./angel:camelot.deIGenericsFAQ
/JavaGenerics FA Q. hlm/.
Puede ver detall es acerca de los comodines en Adding Wildcards lO (he Java Programming Languoge, de Torgerson, Ernst,
Hansen, von der Ahe, Bracha y Gafter, que podr encontrar en \Vwwjolfm/issues/issue_2004 _1 2/arlicle5.
Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico Tlle Thinkil/g ;1/ )01'0 Anflotated SOllllioll Guide. disponible para
la venIa en 1I'11"l\:AlindV,ew.nel.
Matrices
Al fInal del Captulo 5, Jniciali::.acin y limpieza, vimos cmo definir e inicializar una matriz.
Podriamos tratar de describir de manera simple las matrices diciendo que lo que hacemos es crearlas y rellenarlas. luego
seleccionar elementos de las mismas utilizando ndices enteros y diciendo. adems, que las matrices no cambian de tama-
o. La mayora de las veces, eso es todo 10 que necesitamos saber pero en ocasiones hay que realizar operaciones ms sofis-
ticadas con las matrices. y tambin puede que tengamos que comparar la utilizacin de una matriz con la de otros
contenedores ms llexibles. En este captulo veremos cmo analizar las matrices con un mayor detalle.
Por qu las matrices son especiales
Existen diferentes maneras de almacenar objetos. as que qu es lo que hace que las matrices sean especiales?
Existen tres aspectos que distinguen a las matrices de airas tipos de contenedores: la eficiencia, el tipo y la capacidad de
almacenar primitivas. La matriz es la forma ms eficiente en Java para almacenar una secuencia de referencias a objetos y
para acceder aleatoriamente a ell as. Una matriz es una secuencia lineal simple, lo que hace que el acceso a los elementos
sea rpido. El coste que hay que pagar por esta velocidad es que el tamao de un objeto matriz es fijo y no puede cambiar-
se a Jo largo de la vida de la matriz. Podramos pensar, como alternativa, en utilizar un contenedor de tipo ArrayList (con-
sulte el Captulo 11, Almacenamiento de objetos), que se encargar de asignar automticamente ms espacio, creando un
nuevo contenedor y desplazando todas las referencias desde el contenedor antiguo hasta el nuevo. Aunque generalmente
resulta preferible emplear un contenedor ArrayList en lugar de una matriz, esta flexibilidad adicional tiene un cieno coste
asociado, de manera que un contenedor ArrayList es perceptiblemente menos eficiente que una matriz.
Tanto las matrices como los contenedores incluyen mecanismos necesarios para garantizar que no podamos abusar de ellos.
Independientemente de si estamos utilizando una matriz o un contenedor obtendremos una excepcin RuntimeException
si nos pasamos de los lmites, un hecho que indica que se ha producido un error del programador.
Antes de la adopcin de los genricos, las otras clases de contenedores trataban con los objetos como si stos no tuvieran
ningn tipo especfico. En otras palabras, trataban con ellos como si fueran de tipo Object, la clase raz de todas las clases
de Java. Las matrices son ms convenientes que los contenedores anteriores a los mecanismos de genricos, porque pode-
mos crear una matri z para almacenar un tipo especfico. Esto significa que se dispone de una comprobacin de tipos en tiem-
po de compilacin, con el fin de impedir que insertemos un objeto de tipo incorrecto o que confundamos el tipo de los
objetos que estemos extrayendo. Por supuesto, Java impedir que enviemos un mensaje inapropiado a cualquier objeto, tanto
en tiempo de compilacin como en tiempo de ejecucin, de modo que ninguno de los dos enfoques es ms arriesgado que
el otro. Simplemente resulta mucho ms cmodo que el compilador nos avise de los errores, y con las matrices existe una
menor probabilidad de que el usuario final pueda verse sorprendido por la generacin de una excepcin.
Una matriz puede almacenar primitivas mientras que un contenedor de los anteriores a la adopcin de mecanismo de gen-
ricos no puede albergar primitivas. Sin embargo, con los genricos, los contenedores tienen que especificar y comprobar el
tipo de los objetos que almacenan y, gracias a los mecanismos de conversin automtica, los contenedores pueden actuar
COmo si fueran capaces de almacenar primitivas, ya que la conversin es transparente. He aqui un ejemplo donde se com-
paran las matrices con los contenedores genricos:
ji: arrays/ContainerComparison . java
import java.util.*
import static net.mindview.util.Print.*
484 Piensa en Java
class BerylliumSphere
private static long counter
private final long id = counter++;
public String toString () { return "Sphere " + id; }
public class ContainerComparison {
public static void main(String[] argsl {
BerylliumSphere[] spheres = new BerylliumSphere(lO];
for(int i "" O; i < 5; i++)
spheres[i] = new BerylliumSphere()
print(Arrays.toString(spheres ;
print(spheres[4]) ;
List<8erylliumSphere> sphereList =
new ArrayList<BerylliumSphere>();
for(int i = O; i < 5; i++}
sphereList.add(new BerylliumSphere(;
print(sphereList) ;
print(sphereList.get(4 ;
int [] integers = ( O, 1, 2, 3, 4, 5 );
print(Arrays.toString(integersl) ;
print (integers [4] ) ;
List<Integer> intList = new ArrayList<Integer>{
Arrays . asList{O, 1, 2, 3, 4, 5;
intList.add(97) ;
print(intList) ;
print(intList.get{4 ;
/ * Output:
(Sphere O, Sphere 1, Sphere 2, Sphere 3, Sphere 4, null, null, null, null, null]
Sphere 4
(Sphere S, Sphere 6, Sphere 7, Sphere 8, Sphere 9]
Sphere 9
[O, 1, 2, 3, 4, 5]
4
[O, 1, 2, 3, 4, 5, 97]
4
* ///,-
En ambas maneras de almacenar los objetos se comprueban los tipos de los datos y la nica diferencias aparente es que las
matrices utilizan I I para acceder a los elementos, mientras que un contenedor de tipo Lis! utiliza mtodos como add( ) y
get( ). La similitud entre las matrices y el contenedor Ar rayList es intencionada, de manera que resulte conceptualmente
fcil conmutar entre ambas soluciones. Pero, como vimos en el Captulo 11 , Almacenamiento de los objetos, los contenedo-
res tienen una funcionalidad mucho ms rica que las matrices.
Con la aparicin de los mecani smos de conversin automtica, los contenedores son casi tan fciles de utili zar con primiti-
vas como las matri ces. La nica ventaja que le queda, en consecuencia, a las matrices es la eficiencia. Sin embargo, cuan-
do lo que estemos tratando de resolver sea un problema ms general, las matrices pueden ser demasiado restrictivas, y en
esos casos lo que hacemos es utilizar una clase de contenedor.
Las matrices son objetos de primera clase
Independientemente del tipo de matri z con el que estemos trabajando, el identificador de la matriz es de hecho una referen-
cia a un verdadero objeto que se crea dentro del cmulo de memoria. ste es el objeto que almacena las referencias a los
otros objetos (los que estn almacenados en la matriz) y puede crearse tanto implcitamente como parte de la sintaxis de ini-
16 Matrices 485
ciali zacin de la matriz. cuanto explcitamente mediante una expresin new. Parte del objeto matri z (de hecho, el nico
campo o mtodo al que podemos acceder) es el mi embro length (longitud) de slo lectura que nos di ce cuntos elementos
pueden almacenarse en dicho objeto matriz. La si ntaxis I J' es la ni ca otra fonna que tenemos de acceder al objeto matri z.
El siguiente ejemplo resume las diversas fom18s en que puede inicializarse una matri z, y las maneras en que las referencias
de matriz pueden asignarse a diferentes obj etos matriz. El ej emplo tambin muestra que las matrices de objetos y las matri-
ces de primitivas son casi idnticas en lo que a su uso se refiere. La ni ca diferencia es que las matrices de objetos almace-
nan referencias, mientras que las matri ces de primitivas almacenan directamente valores primitivos.
11 : arrays/ArrayOptions.java
II Inicializacin y reasignacin de matrices.
import java . util .*;
import static net.mindview.util.Print.*;
public class ArrayOptions {
public static void main (String [] args) {
II Matrices de objetos :
BerylliumSphere[] a; II Variable local no inicializada
BerylliumSphere[] b = new BerylliumSphere[S] i
II Las referencias dentro de la matriz se inicializan
II automticamente con null:
print ("b : 11 + Arrays. toString (b) ) ;
BerylliumSphere[] c = new BerylliumSphere[4];
fortint i = O; i <: c.length ; i++}
if te [i] == null) II Se puede comprobar si es una referencia nula
c[i) = new BerylliumSphere{);
II Inicializacin agregada:
BerylliumSphere[] d = { new BerylliumSphere(),
new BerylliumSphere(), new BerylliumSphere{)
};
I1 Inicializacin agregada dinmica :
a = new BerylliumSphere[] {
new BerylliumSphere(), new BerylliumSphere{),
};
I1 (La coma final
print (lIa .length
print (lIb .length
print("c.length
print (lid .length
a = di
print (" a .length
es opcional en ambos casos)
+ a .length) ;
+ b .length) ;
+ c .length) ;
+ d .length) ;
+ a .length) ;
I1 Matrices de primitivas:
int[) e; 11 Referencia nula
int[] f = new int[5];
II Las primitivas contenidas en la matriz se
I1 inicializan automticamente con cero:
print (11 f: 11 + Arrays. toString {f) ) ;
int[] 9 = new int(4) i
for{int i = O; i < g.length; i++)
g[i] = i*i;
int[J h = { 11, 47, 93 };
II Error de compilacin: variable e no inicilizada:
II!print(lIe.length = 11 + e.length) i
print ( " f .length + f .length) ;
print (lIg .length + 9 .length) ;
print ("h .length + h . length) i
e = h;
print ( "e .length + e .length) ;
486 Piensa en Java
e new int [1 { 1, 2 };
print ( "e.1ength = + e.1ength) ;
/ * Output:
b: [nu11, nu11, nu11, nu11, nu11]
a .1ength .2
b.1ength 5
e .1ength 4
d .length 3
a .1ength 3
f, [O, O, O, 0, 01
f . 1ength 5
9 .1ength 4
h .length 3
e .1ength 3
e .1ength 2
* ///,-
La matriz a es una variable local no inicializada y el compilador nos impide que hagamos nada con esta referencia hasta
que la hayamos inicializado adecuadamente. La matriz b se inicializa para que apunte a una matriz de referencias
BeryUiumSphere, pero en esa matriz nunca se llegan a almacenar objetos BeryIHumSphere directamente. De todos modos,
podemos seguir preguntando cul es el tamao de la matriz, ya que b est apuntando a un objeto legtimo. Esto nos revela
una cierta desventaja: no podemos averiguar cuntos elementos hay realmente almacenados en la matri z, ya que length nos
dice slo cuntos elementos pueden almacenarse; en otras palabras, dicho campo nos dice el tamao del objeto matriz no el
nmero de elementos que est almacenando en un momento determinado. Sin embargo, cuando se crea un objeto matriz sus
referencias se iniciali zan automticamente con el valor null, por lo que podemos ver si una posicin concreta de una matriz
tiene un objeto almacenado, comprobando si esa posicin tiene un valor null . De forma similar, las matrices de primitivas
se inicializan automticamente con cero para los tipos numricos, con (char)O para char, y con fabe para boolean.
La matriz e pennite ilustrar la creacin del objeto matriz seguida de la asignacin de objetos BerylliumSphere a todas las
posiciones de la matriz. La matriz d muestra la sintaxis de "inicializacin agregada" que hace que el objeto matri z se cree
(implcitamente con new en el cmulo de memoria, al igual que la matriz e) e inicialice con objetos BerylliumSphere, todo
ello en una nica instruccin.
La siguiente inicializacin de matriz puede considerarse como una especie de "inicializacin agregada dinmica". La ini-
cializacin agregada utilizada por d debe utilizarse en el lugar donde se define d, pero con la segunda sintaxis podemos crear
e inicializar un objeto matriz en cualquier parte. Por ejemplo, suponga que hide() es un mtodo que toma como argumen-
to una matriz de objetos Berylli umSphere. Podramos invocar ese mtodo escribiendo:
hide (d) ;
pero tambin podemos crear dinmicamente la matriz que queramos pasar como argumento:
hide(new Bery11iumSphere[] { new Bery11iumSphere (),
new BerylliumSphere (1 });
En muchas situaciones, esta sintaxis proporciona una forma mucho ms conveniente de escribir el cdigo.
La expresin:
a = d;
muestra cmo podemos tomar una referencia asociada a un objeto matriz y asignarla a otro objeto matriz, al igual que pode-
mos hacer con otro tipo de referencia a objetos. Ahora, tanto a como d estn apuntando al mismo objeto matriz situado en
el cmulo de memoria.
La segunda parte de ArrayOptions,java muestra que las matrices de primitivas funcionan igual que las matrices de obje-
tos, salvo porque las matrices de primitivas almacenan directamente los valores primitivos.
Ejercicio 1: (2) Cree un mtodo que tome como argumento una matriz de objetos BerylliumSpbere. Invoque el mto-
do creando el argumento dinmicamente. Demuestre que la inicializacin agregada normal de matrices no
funciona en este caso. Descubra las nicas situaciones en las que funciona la inicializacin agregada de
matrices y en las que la inicializacin agregada dinmica es redundante.
16 Matrices 487
Devolucin de una matriz
Suponga que estamos escribiendo un mlOdo y que no queremos devolver un nico valor, si no un conjunto de ellos. Los
lenguajes C0l11 0 e y e++ hacen que esto sea dificil, porque no se puede devolver una matriz, sino slo un puntero a una
matriz. Esto genera problemas. porque resulta compli cado controlar el tiempo de vida de la matriz, lo que a su vez condu-
ce a fugas de memoria.
En Java. basta con devolver directamente la matri z. Nunca tenemos por qu preocupamos por esa matri z: la matriz pervivi-
r mientras que sea necesari a y el depurador de memoria se encargar de borrarl a una vez que hayamos tenninado de utili-
zarl a.
Como ejempl o, vamos a ver cmo se devolvera una matri z de objetos String:
// : arrays / lceCream.java
II Devolucin de matrices desde mtodos.
import java . util.*;
public class IceCream
private static Random rand ;; new Random (47 ) ;
static final String [] FLAVORS = {
};
"Chocolate", "Strawberry", "Vanilla Fudge Swirl",
"Mint Chip", "Mocha Almond Fudge", "Rum Raisin" ,
"Praline Cream
ll
, "Mud Pie"
public static String [] flavorSet (int n)
if(n > FLAVORS . lengt h)
throw new IllegalArgumentException ( "Set too big");
String [] results new String [nI ;
boolean[] picked new boolean [FLAVORS . length] ;
for (int i = O; i < n; i++) (
int t
do
t = rand.nextlnt( FLAVORS.length);
while (picked[tj) ;
results[i] = FLAVORS[t]
picked[t] = true
return resul ts;
public static void main(String[] args) {
for (int i = O; i < 7 ; i++)
System.out.println(Arrays.toString(flavorSet(3))) ;
1* Output:
[Rum Raisin, Mint Chip, Mocha Almond Fudge]
[Chocolate, Strawberry, Mocha Almond Fudge]
[Strawberry, Mint Chip, Mocha Almond Fudgel
[Rum Raisin, Vanilla Fudge Swirl, Mud Pie)
[Vanilla Fudge Swirl , Chocolate, Mocha Almond Fudge]
[Praline Cream, Strawberry, Mocha Almond Fudge )
[Mocha Almond Fudge , St rawberry, Mi nt Chip]
* ///,-
El mtodo navorSet( ) crea una matriz de objetos String denominada results. El tamao de esta matri z es n, que est deter-
minado por el argumento que le pasemos al mtodo. A continuacin, el mtodo selecciona una serie de valores aleatoria-
mente de entre la matri z FLAVORS y los coloca en rcsults, devol viendo despus esta matriz. La devolucin de una matriz
es exactamente igual que la devolucin de cualquier otro objeto: se trata simpl emente de una referencia. No es importante
que la matri z haya sido creada dentro de navorSct( ), o en cualquier otro lugar. El depurador de memoria se encargar de
borrar la matri z cuando hayamos tenninado de usarla y esa matri z persistir durante todo el tiempo que la necesitemos.
488 Piensa en Java
Como nota adicional, observe que cuando flavorSet( ) selecciona valores al eatoriamente, se encarga de comprobar que un
valor concreto no haya si do previamente seleccionado. Esto se hace en un bucle do que contina realizando selecciones alea-
torias hasta que encuentre un valor que-no est ya en la matri z picked (por supuesto, tambi n podra haberse realizado una
comparacin String para ver si el valor aleatorio est ya en la matri z results). Si tiene xi to, aade la nueva entrada y loca-
li za la sigui ente (i se incrementa).
Puede ver analizando la salida que flavorSet() selecciona los valores en orden aleatorio cada una de las veces.
Ejercicio 2: ( 1) Escriba un mtodo que tome un argumento int y devuelva una matri z de di cho tamao rellenada con
objetos BerylliumSphere.
Matrices multidimensionales
Podemos crear fcilmente matrices multidimensionales. Para las matrices multidimensionales de primitivas, delimitamos
cada vector de la matriz mediante llaves:
JI: arrays/MultidimensionalPrimitiveArray.java
/1 Creacin de matrices multidimensionales.
import java . util .*
public class MultidimensionalPrimitiveArray
public static void main{String[] argsJ {
int [][] a = {
{ l,2,3,},
{4,5,6,},
};
System.out.println (Arrays ,deepToString (a) )
1* Output :
[[1 , 2, 31. [4, 5, 6]]
* /1/,-
Cada conjunto anidado de llaves nos desplaza al siguiente nivel de la matriz.
Este ejemplo utiliza el mtodo Arrays.deepToString() de Java SES, que transfonna matrices multidimensionales en obje-
tos String, como podemos ver a la salida,
Tambi n podemos asignar una matri z con new. He aqu una matriz tridimensional asignada en una expresin Dew:
/1 : arrays/ThreeDWithNew.java
import java,util ,*
public class ThreeDWithNew
public static void main(String[] args}
/1 Matriz 3-D con longitud fija:
int [] [ 1 [] a = new int [2] [2] [ 4 ] ;
System,out.println(Arrays.deepToString(a)) i
1* Output:
[1 [0, 0, 0, Ol. [0, 0, 0, O]l. [[0, 0, 0, O]. [0, 0, 0, 01]]
* ///,-
Podemos ver que los va lores primitivos de la matri z se iniciali zan automticamente si no proporcionamos un valor de ini-
cializacin explcito. Las matrices de objetos se inicializan con nuU,
Cada vector de las matrices que fonnan la matriz total puede tener cualquier longitud (esto se denomina matriz desigual):
11 : arrays/RaggedArray,java
import java,util, *
public class RaggedArray
publ ic static void main (String [] args) {
Random rand = new Random(47) i
/1 Matriz 3-D con vectores de longitud varaib!e:
int [) [) [) a = new int [rand.nextlnt (7)) [) [);
for(int i = O; i < a.length; i++) {
a [i] = new int [rand.nextlnt (S) 1 [] i
for(int j O; j < a[i] .length; j++)
a[i] [j] = new int[rand.nextlnt(S)];
System.out.println(Arrays.deepToString(a) ;
} / * Output,
[[), [[O), [o),
[ [O, O, O), [O),
[O), [)), [[O),
* /// ,-
[O I O I O, O]],
[O, O, O, O)),
[), [O]))
[[), [O, O),
[(O, O, O),
[O, O)),
(O, O, O),
16 Matrices 489
La primera instruccin new crea una matriz con un primer elemento de longitud aleatoria y el resto indeterminado. La
segunda instruccin new dentro del bucle for rellena los elementos, pero dejando el tercer ndice indetenninado hasta que
nos encontramos con la tercera instruccin new.
Podemos tratar matrices de objetos no primitivos de una forma similar. En el siguiente ejemplo podemos ver cmo agrupar
varias expresiones new mediante llaves:
jj: arraysjMultidimensionalObjectArrays.java
import java . util.*
public class MultidimensionalObjectArrays
public static void main(String[] args) {
Beryl liumSphere [] [] spheres = {
( new BerylliumSphere (), new BerylliumSphere () },
{ new
new
new
new
BerylliumSphere() ,
BerylliumSphere() ,
BerylliumSphere (} ,
BerylliumSphere() ,
new BerylliumSphere(),
new BerylliumSphere(),
new
new
new
new
new
new
BerylliumSphere() ,
BerylliumSphere () },
BerylliumSphere() ,
BerylliumSphere() ,
BerylliumSphere(} ,
BerylliurnSphere () }.
} ;
System.out . println{Arrays.deepToString(spheres})
j * Output:
[[Sphere O, Sphere 1], [Sphere 2, Sphere 3, Sphere 4, Sphere 5],
[Sphere 6, Sphere 7, Sphere 8, Sphere 9, Sphere la,
Sphere 11, Sphere 12, Sphere 131]
*///,-
Podemos ver que spheres es otra matriz desigual , siendo la longitud de cada li sta de objetos diferente.
El mecani smo de conversin automtica tambi n funciona con los inicializad ores de matrices:
jj: arraysjAutoboxingArrays.java
import java.util.*
public class AutoboxingArrays
public static void main (String [1 args) {
Integer [] [] a = { / j Conversin automtica:
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 },
{ 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 }.
{ 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 },
{ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 },
} ;
System.out.println(Arrays.deepToString(a)} ;
490 Piensa en Java
} 1* Output:
[[1, 2, 3, 4, 5, 6, 7, 8, 9, lO], [21, 22, 23, 24, 25, 26,
27, 28, 29, 301, [51, 52, 53, 54, 55, 56, 57, 58, 59, 601,
[7 1, 72, 73, 74, 75, 76, 77, 78, 79, 80]]
' ///0-
He aqu cmo podramos construir por partes una matriz de objetos no primitivos:
11: arrays/AssemblingMultidimensionalArrays.java
II Creacin de matrices multidimensionales.
import java.util.*;
public class AssemblingMultidimensionalArrays
public static void main(String[] args) {
Inceger [1 [1 a;
a == new Integer (3] [] ;
for {int i = O; i < a.length; i++ )
a [i] = new Integer [3] ;
for (int j O; j < a(i] .length; j++)
a [iJ [j J = i * j j II Conversin automtica
System.out.println{Arrays.deepToString(a)) ;
/ * Output:
[[O, O, O], [O, 1, 2], [O, 2, 411
'/ // 0-
La expresin i*j slo tiene por objeto asignar un valor interesante al objeto Integer.
El mtodo Arrays.deepToString( ) funciona tanto con matrices de primitivas como con matrices de objetos:
/1 : arrays/MultiDimWrapperArray.java
II Matrices multidimensionales de objetos "envoltorio".
import java.util.*;
public class MultiDimWrapperArray
public static void main(String[] args)
Integer [] [] al = { /1 Conversin automtica
{1,2,3,},
{4,5,6,).
} ;
Double [1 [1 [1 a2 { //
Conversin automtica
{ { 1.1, 2.2 }. { 3.3, 4.4 } } ,
{ { 5.5, 6.6 } , { 7.7, 8 . 8 } } ,
{ { 9.9, 1.2 } , { 2.3, 3.4 } }.
} ;
String [1 [1 a3 : {
{ IIThe
ll
, IIQuick
ll
, "Sly", "Fox" },
{ "Jumped", "Over
ll
},
{ tlThe", "Lazy", "Brown
ll
, IIDog", tland", tlfriend" },
} ;
System. out. println ( ti al:
System. out. println (ti a2 :
System.out.println("a3:
1* Output:
aL [[1, 2, 3], [4, 5, 611
+ Arrays.deepToString(al));
+ Arrays.deepToString (a2));
+ Arrays.deepToString(a3));
a20 [[[1.1, 2.2], [3.3, 4.41], [[5.5, 6.6], [7.7, 8.81],
[9.9, 1.2], [2.3, 3.4111
a3: [[The, Quick, Sly, Fox] , [Jumped, Over) , [The, Lazy,
Brown, Dog, and, friendll
' /// 0-
16 Matrices 491
De nuevo. en las matrices Integer y Double. el mecanismo de conversin automtica de Java SES se encarga de crear por
nosotros los objetos envoltorio.
Ejercici o 3:
Ejerci cio 4:
Ejercicio 5:
Ejercicio 6:
Ejercicio 7:
(4) Escriba un mtodo que cree- e inicialice una matriz bidimensional de valores double. El tamao de la
matriz estar determinado por los argumentos del mtodo y los valores de inicializacin sern un rango
detenninado por sendos valores inicial y final que tambin sern argumentos del mtodo. Cree un segun-
do mtodo que imprima la matriz generada por el primer mtodo. En main( ) compruebe los mtodos
creando e imprimiendo varias matrices de tamaos diferentes.
(2) Repita el ejercicio anterior para una matriz tridimensional.
(1) Demuestre que las matrices multidimensionales de tipos primitivos se inicilizan automticamente con
null.
(l) Escriba un mtodo que tome dos argumentos ot, indicando las dos dimensiones de una matriz 2-D. El
mtodo debe crear y rellenar una matriz 2-D de objetos Berylliul11Sphere de acuerdo con los argumentos
de dimensin.
(1) Repita el ejercicio anterior para una matriz 3-D.
Matrices y genricos
En general, las matrices y los genricos no se combinan demasiado bien. No se pueden instanciar matrices de tipos parame-
trizados:
peels = new i jj Ilegal
El mecanismo de borrado automtico de tipos elimina la infonnacin del parmetro de tipo, y las matrices deben conocer
el tipo exacto que almacenan, con el fin de poder imponer los mecanismos de seguridad de tipos.
Sin embargo, lo que s se puede es parametrizar el tipo de la propia matriz:
ji : arraysjParameterizedArrayType.java
class {
public T[] f (T [] arg) { return arg; )
class MethodParameter {
public static T(] f(T(] arg) return arg; }
public class ParameterizedArrayType
public static void main(String(] args)
Integer [] ints = { " 2, 3, 4, 5 };
Double[] doubles = { 1.1, 2.2, 3.3, 4.4, 5.5 );
Integer (1 ints2 =
new () . f (ints) i
Double(] doubles2 =
new () . f (doubles) i
ints2 = MethodParameter.f(ints) i
doubles2 = MethodParameter.f(doubles) i
)
1110-
Observe la comodidad que se deriva de utilizar un mtodo parametrizado en lugar de una clase parametrizada: no hace falta
instanciar una clase con un parmetro para cada tipo diferente al que necesitemos apli car, y adems el mtodo se puede defi -
nir como esttico. Por supuesto, no siempre podemos utilizar un mtodo pararnetrizado en lugar de una clase paramerriza-
da, aunque s hay muchas ocasiones en las que puede ser preferible.
En realidad, no es del todo correcto decir que no se pueden crear matrices de tipos genricos. El compilador no pennite, en
efecto, b1Sfandar una matriz de un tipo genrico, sin embargo, lo que s pennite es crear una referencia a dicha matriz. Por
ejemplo:
492 Piensa en Java
List<String> [ ) lSi
El compil ador admite este tipo de sintaxis sin ernir ni guna queja. V, aunque no podemos crear un obj eto matri z real que
almacene genricos, s que podemos crear una matri z del tipo no genri co y efectuar una proyeccin de tipos:
11 : arrays / ArrayOfGenerics.java
II Es posible crear matrices de genricos.
impo rt java.util. *
public class ArrayOfGenerics
@SuppressWarnings ( "unchecked")
public static void main (String [] args ) {
List<String> [] lSi
List [] la = new List [10] ;
15 = (List<String>[] ) la II Advertencia no comprobada
15 [O] = new ArrayList<String> {) ;
II La comprobac i n en tiempo de compilacin produce un error :
II ! lS[l] = new ArrayList<I nteger>();
II El problema: List<String> es un subtipo de Object
Object[] objects = ls II Por loq eu la a signacin es correcta
II Se compila y se ejecuta sin n i ngn pr oblema :
objects [1] = new ArrayList<Integer> () ;
II Sin embargo, si nuestras neces i dades son simples se
II puede crear una matriz de genricos, aunque con una
II advertencia no comprobada :
List<BerylliumSphere> [] s phe r es =
(Li st<BerylliumSphere>[])new Li st[lO];
for( i nt i = O; i < spheres . length i++)
spheres [ i] = new ArrayList<BerylliumSphere >()
Una vez que disponemos de una referencia a List<String>lI. podemos ver que se obti ene una cierta comprobacin en tiem-
po de compilacin. El probl ema es que las matrices son covariantes, por lo que una matri z List<String>1I es tambin una
matri z ObjectrL y podemos utili zar esto para asignar un objeto ArrayList<Integer> a nuestra matri z, sin que se produzca
ningn error ni en ti empo de compilacin ni en ti empo de ejecucin.
Sin embargo, si sabemos que no vamos a efectuar ninguna generalizaci n y nuestras necesidades son relati vamente simples,
es posi bl e crear una matriz de genri cos, lo que nos proporciona una cierta comprobacin de tipos bsica en ti empo de com-
pil acin. No obstante, casi si empre un contenedor genri co ser preferibl e a una matri z de genri cos.
En general, nos encontraremos con que los genricos son efecti vos en los lmites de una clase o mtodo. En los interiores,
el mecani smo de borrado de tipos suele hacer inutilizables los genri cos. De este modo, no podemos, por ej emplo, crear una
matriz de un tipo genri co:
11 : arrays / ArrayofGenericType.java
II Las matrices de tipos genricos no se pueden compilar.
public class ArrayOfGenericType<T>
T[] arr ay; II OK
@SuppressWarnings ( "unchecked")
public ArrayOfGenericType(int size)
JJ ! array = new T[si z e] II I legal
array = (T[])new Object [size] IJ Advert e ncia no comprobada
II Ilegal,
JI ! public c:U> Uf] makeArray( ) { r e turn new U[lO] }
111 ,-
16 Matrices 493
De nuevo, el mecanismo de borrado de tipos interfiere con nuestros propsitos; en este ejemplo, se intenta crear matrices
de tipos que se han visto sometidos al mecanismo de borrado de tipos y que son, por tanto, de tipo desconocido. Observe
que podemos crear una matriz de tipo Object , y proyectarla, pero si quitamos la anotacin @SuppressWarnings obtendre-
mos una advertencia "no comprobada" en tiempo de compilacin, porque la matriz no almacena realmente, ni comprueba
de fanna dinmica el tipo T. En otras palabras, si creamos una matriz St ringll. Java impondr tanto en tiempo de compila-
cin como en tiempo de ejecucin que slo podemos colocar objetos String en dicha matriz. Sin embargo, si creamos una
matriz Obj ectf] , podemos almacenar en ella cualquier cosa menos tipos primitivos.
Ejercicio 8: (1) Demuestre las afinnaciones del prrafo anterior.
Ejercicio 9: (3) Cree las clases necesarias para el ejemplo Peel<Banana> y demuestre que el compilador no lo acep-
ta. Corrija el problema utilizando un contenedor ArrayList.
Ejercicio 10: (2) Modifique ArrayOfGeneri cs.j ava para emplear contenedores en lugar de matrices. Demuestre que
puede eliminar las advertencias de tiempo de compilacin.
Creacin de datos de prueba
Cuando se experimenta con las matrices y con los programas en general , resulta til poder generar fcilmente matrices lle-
nas de datos de pnleba. Las herramientas de esta seccin penniten rellenar una matriz con valores u objetos.
ArraysJiIIO
La clase Arrays de la bibl ioteca estndar de Java tiene un mtodo fiU() bastante trivial: se limita a duplicar un mismo valor
en cada posicin o, en el caso de los objetos, inserta en cada posicin copias de la misma referencia. He aqu un ejemplo:
JJ : arraysJFillingArrays.java
JJ Utilizacin de Arrays.fill()
import java.util.*
import static net.mindview.util.Print.*
public class FillingArrays {
public static void main(String[] args)
int size = 6;
boolean[] al = new boolean[size] i
byte[] a2 = new byte [size] i
char[] a3 = new char[sizel;
short(] a4 = new short[sizel i
int [] aS = new int (size] i
long [] a6 = new long [size] ;
float(l a7 = new float[sizel i
double[] aS = new double[size)
String [] a9 = new String [size]
Arrays.fill(al,
print (11 al = 11 +
Arrays.fill(a2,
true) i
Arrays.toString(a1)) ;
(byte) 11) ;
print(lI a 2 = 11 + Arrays.toString(a2)) i
Arrays.fill(a3, 'x');
print (lIa3 = 11 + Arrays. toString (a3) ) ;
Arrays. fill (a4, (short) 17)
print (lIa4 = 11 + Arrays. toString (a4) ) i
Arrays.fill(aS, 19)
print{"aS = " + Arrays.toString(aS));
Arrays.fill(a6, 23);
print ("a6 = " + Arrays. toString (a6) ) i
Arrays.fil l{a7, 29);
print{lIa7 = 11 + Arrays.toString(a7));
Arrays.fill{a8, 47);
494 Piensa en Java
al
a2
a3
a4
a5
a6
a7
/ *
print ( "aS '" " + Arrays. toString (aS) ) i
Arrays.fill {a9, "HelIo");
print ( "a9 = 11 + Arrays. toString (a9) ) i
II Manipulating ranges:
Arrays.fill (a9, 3, 5, "World")
print ( "a9 = 11 + Arrays. toString (a9 )) ;
Output:
[true, true, true, true, true, trueJ
[11, 11, 11, 11, 11, 11J
[x, x, x, x, x, xJ
[17, 17, 17, 17, 17, 17J
[19, 19, 19, 19, 19, 19J
[23, 23, 23, 23, 23, 23J
[29. O, 29 . O, 29.0, 29.0, 29 . 0, 29 . 0J
aS [47.0, 47.0, 47.0, 47.0, 47 . 0, 47 . 0J
a9 [HelIo, HelIo, HelIo, HelIo, HelIo, HelIo]
a9 [HelIo, HelIo, HelIo, World, Wor l d, HelIo]
* /// , -
Podemos rellenar la matriz completa o, como muestran las dos ltimas instrucciones, rellenar tan solo un rango de elemen-
tos. Pero como slo se puede invocar Arrays.fi ll ( ) con un nico valor de datos, los resultados no son especialmente til es.
Generadores de datos
Para crear matrices de datos ms interesantes, pero de una manera flexible utilizaremos el concepto de Generador introdu-
cido en el Captulo 15, Genricos. Si una herramientas utiliza un obj eto Generator, podemos generar cualquier clase de
datos eligiendo el objeto Generator adecuado (se trata de un ejemplo del patrn de di seo basado en estrategia), cada uno
de los diferentes generadores representa una estrat egia diferente. ]
En esta seccin proporcionaremos algunos generadores y, como ya hemos visto en ejemplos anteriores, tambin podemos
definir fcilmente otros generadores que deseemos.
En primer lugar, he aqu un conjunto bsico de generadores de recuento para todos los tipos envoltorio de primitivas y para
las cadenas de caracteres. Las clases generadoras estn anidadas dentro de la clase CountingGenerator, de modo que pue-
den utilizar el mi smo nombre que los tipos de obj eto que estn generando; por ejemplo, un generador que cree obj etos de
tipo Integer podra generarse mediante la expresin new CountingGcnerator.lnteger( ):
11: net/mindview/util/CountingGenerator.java
II Implementaciones simples de generadores .
package net.mindview. util;
public class CountingGenerator
public static class
Boolean impleme nts Generator<java . lang . Boolean>
private boolean value = false
public java.lang.Boolean next()
value = !value; II s610 salta hacia atrs y hacia adelante
return value;
public static class
Byte implements Generator<java . lang . Byte> {
private byte value = O;
public java . lang.Byte next{) { return value++;
I Si bicn hay quc recalcar que en este tema las fronteras resultan un tanlo borrosas. Tambin podra mos argumentar quc un objeto Generator representa
el patrn de diseo de Comando: sin embargo. en mi opinin, la tarea consiste en rellenar una matTi z y el objeto Generator lleva a cabo parte de dicha
tarea, as que se trata mas de una estrategia que de un comando.
static char[] chars = (tlabcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJK.LMNOPQRSTUVWXYZ") . toCharArray () ;
public static class
Character implements Generator<java.lang.Character>
int index := -1;
publ ie java .lang. Character next () {
index = (index + 1) % chars .length;
return chars[index];
public static class
String implements Generator<java.lang.String>
prvate int length = 7;
Generator<java.lang.Character> eg = new Character{);
public String O ()
public String ( int length) ( this.length = length; )
public java .lang. String next () (
char[J buf = new char[length];
for(int i = O; i < length; i++)
buf[i) = cg.nex tO;
return new java.lang.String(buf);
public static class
Short implements Generator<java.lang.Short> {
prvate short value = O;
public java.lang.Short next{) ( return value++;
public sta tic class
Integer implements Generator<java.lang.Integer>
private int value = O;
public java .lang. Integer next () { return value++;
public static class
Long implements Generator<java.lang.Long> (
private long value = O;
public java .lang. Long next () ( return value++;
public static class
Float implements Generator<java.lang . Float>
private float value = O;
public java.lang.Float next{)
float result = value
value += 1.0;
return result;
public static class
Double implements Generator<java.lang.Double>
private double value = 0.0;
public java.lang.Double next {)
double result = value;
value += 1.0;
return resul ti
}
///0-
16 Matrices 495
Cada clase impl ement a su propio significado del trmino " recuent o". En el caso de CountingGenerator.Character, se trata
simpl emente de las letras maysculas y minsculas repet idas una y otra vez. La clase CountingGcnerator.String uti liza
496 Piensa en Java
CountingGenerator.Character para rellenar una matriz de caracteres, que luego se transfonna en un objeto de tipo String.
El tamao de la matriz est detenl1inado por el argumento del constructor. Observe que CountingGenerator.String utili za
un objeto Generator<java.lang.Character> bsico en lugar de una referencia especfica a CountingGenerator.
Character. Posteriormente, este generador puede sustituirse para generar RandornGenerator.String en
RandornGcnerator.java.
He aqu una herrami enta de prueba que utiliza el mecani smo de reflexin con la sintaxi s de generadores anidados, de mane-
ra que pueda utilizarse para probar cualquier conj unto de generadores que se adapten a esta estmctura:
1/: arrays/GeneratorsTest.java
import net.mindview.util.*
public class GeneratorsTest
public static int size = 10;
public static void test (Class<?> surroundingClass) {
for (Cl ass<?> type : surroundingClass.getClasses(})
System.out . print (type.getSimpleName () + lO: ");
try {
Generator<?> 9 = (Generator<?type.newInstance () ;
for(int i = O i < size; i++}
System.out .printf(g.nex t() + " " ) ;
System. out.println() ;
catch (Exception e) {
throw new RuntimeEx ception(e} i
public sta tic void main (String [] args) {
test (CountingGenerator.class ) i
1* Output:
Double, 0 . 0 1.0 2 . 0 3 . 0 4.0 5.0 6.0 7 . 0 8.0 9.0
Float, o . O 1. O 2. O 3 . O 4. O 5. O 6 . O 7. O 8 . O 9. O
Long: O 1 2 3 4 5 6 7 8 9
Integer: O 1 2 3 4 5 6 7 8 9
Short: O 1 2 3 4 5 6 7 8 9
String: abcdefg hijklmn opqrstu vwxyzAB CDEFGHI JKLMNOP QRSTUVW XYZabcd efghijk lmnopqr
Character: a bcd e f 9 h i j
Byte , O 1 2 3 4 5 6 7 8 9
Boolean: true false true false true false true false true false
* ///,-
Esto presupone que la clase que estemos probando contiene un conjunto de objetos Generator anidados, cada uno de los
cuales di spone de un constructor predeterminado (sin argumentos). El mtodo de reflexin getClasses( ) genera todas las
clases anidadas. Entonces, el mtodo test() crea una instancia de cada uno de estos generadores e imprime el resultado pro-
ducido invocando next( ) diez veces.
He aqu un conjunto de objetos Generator que utilizan el generador de nmeros aleatorios. Puesto que el constructor
Random se inicial iza con un valor constante, la salida es repetibl e cada vez que ejecutemos un programa empleando uno
de estos generadores:
11 : net/mindview/util/RandomGenerator.java
/1 Generadores que producen valores aleatorios.
package net.mindview.util
import java . util.*;
public class RandomGenerator
private static Random r = new Random(47)
public static class
Boolean implements Generator<java .lang.Boolean>
public java.lang.Boolean next()
return r.nextBoolean() i
public static class
Byte implements Generator<java.lang.Byte>
public java .lang. Byte next () {
return (byte)r .next lnt() i
public static class
Character implements Generator<java.lang .Character>
public java .lang . Character next () {
return CountingGenerator.chars[
r.nextlnt(CountingGenerator . chars.length)] i
public static class
String extends CountingGenerator.String
/1 I nsertar el generador aleatorio de caracteres:
( eg = new Character(); } JI Inicializador de instancia
public String () {}
public String(int length) { super (length) ;
public static class
Short implements Generator<java.lang.Short>
public java .lang. Short next () {
return (short)r .nextlnt ();
public static class
Integer implements Generator<java . lang . lnteger> {
private int mod = 10000
public Integer () {}
public Integer (int modulo) { mod = modulo }
public java .lang. Integer next () {
return r.nextlnt{mod);
public static class
Long implements Generator<java.lang.Long>
private int mod = 10000;
public Long() {}
public Long(int modulo) mod = moduloi }
public java.lang.Long next() {
return new java.lang.Long(r.nextlnt(mod)) i
public static class
Float implements Generator<java.lang.Float>
public java .lang. Float next () {
JI Eliminar todos los decimales salvo los dos primeros:
int trimmed = Math.round(r . nextFloat{) * 100);
return (( float ltrimmed) J 100;
public static class
Double implements Generator< java .lang.Double> {
public java .lang. Double next () {
16 Matrices 497
498 Piensa en Java
long crimmed = Math.round(r.nextDouble() * 100) i
return ((double)trirnrned) 1 100;
Puede ver que RandomGenerator.String hereda de Counti ngGenerator.String y simplemente inserta el nuevo generador
de tipo Character.
Para generar nmeros que no sean demasiado grandes, RandomGenerator.Integer utiliza de fonna predetenninada un
mdulo igual a 10.000, pero el constructor sobrecargado nos pennite elegir un valor ms pequeii.o. La misma tcnica se usa
para RandornGenerator.Long. Para los generadores de tipo Float y Double. los valores situados detrs del punto decimal
se recortan.
Podemos reutili zar GeneratorsTest para probar RandomGenerator:
11 : arrays/RandomGeneratorsTest.java
import net.mindview.util.*;
public class RandomGeneratorsTest
public static void main(String[1 args)
GeneratorsTest.test(RandomGenerator.class) ;
1* Output:
Doub1e. 0 . 73 0.53 0.16 0.19 0.52 0.27 0 . 26 0.05 0 . 8 0.76
F1oat. 0.53 0.16 0.53 0.4 0.49 0 . 25 0.8 0.11 0.02 0 . 8
Long . 7674 8804 8950 7826 4322 896 8033 2984 2344 5810
Integer: 8303 314 1 7138 6012 9966 8689 7185 6992 5746 3976
Short: 3358 20592 284 26791 12834 -8092 13656 29324 -1423 5327
String: bklnaMe sbtWHkj UrUkZPg wsqPzDy CyRFJQA HxxHvHq
XumcXZJ oogoYWM NvqeuTp nXsgqia
Character : x x E A J J m z M s
Byte. -60 -17 55 -14 -5 115 39 -37 79 115
Boolean: false true false false true true true true true true
* /// .-
Podemos modificar el nmero de valores producidos cambiando el valor GeneratorsTest.size, que es de tipo public.
Creacin de matrices a partir de generadores
Para tomar un objeto Generator y generar una matri z, necesitamos dos herramientas de conversin. La primera ut ili za cual-
quier generador para producir una matriz de subtipos Object. Para resolver el problema de las primitivas, la segunda herra-
mienta toma cualquier matri z de tipos envoltori o de primiti vas y produce la matriz de primitivas asociada.
La primera berramienta tiene dos opciones, representadas por un mtodo esttico sobrecargado que se denomina array( ).
La primera versin del mtodo toma una matriz existente y la rell ena utili zando un generador, mi entras que la segunda ver-
sin toma un objeto Class, un generador y el nmero deseado de elementos y crea una nueva matriz, de nuevo reUenndo-
la mediante el generador elegido. Observe que esta herramienta slo produce matrices de subtipos Object y no pennite crear
matrices de primitivas:
11 : net/mindview/util/Generated.java
package net.mindview.util;
import java . util .*;
public class Generated
1/ Rellenar una matriz existente
public static <T> T(] array(T[] a, Generator<T> gen) {
return new CollectionData<T> (gen, a.length) .toArray (a);
11 Crear una nueva matriz:
@SuppressWarnings ("unchecked" )
public static cT> T[] array(ClasscT> type,
GeneratorcT> gen, int sizel (
T [) a =
(T[J ) java.lang.reflect.Array.newlnstance (type, sizel;
return new CollectionDatacT> (gen, size) .toArray(a) i
16 Matrices 499
La clase CollectionData se definir en el Captulo 17, Anlisis detallado de los contenedores. Esta clase crea un objeto
ColJection reUeno con elementos producidos por el generador gen. El nmero de elementos est detenninado por el segun-
do argumento del constmctor. Todos los subtipos de CoUection tienen un mtodo toArray() que rellena la matriz argumen-
to con [os elementos del objeto eollection.
El segundo mtodo emplea el mecanismo de reflexin para crear dinmicamente una matriz del tipo y tal11ai10 apropiados.
Entonces esta matriz se rellena empleando la misma tcnica que con el primer mtodo.
Podemos probar Generated utilizando una de las clases CountillgGenerator definidas en la seccin anterior:
1/ : arrays/TestGenerated.java
import java.util.-;
import net.rnindview.util.*
public class TestGenerated {
public static void main{String(] argsl {
Integer [) a = { 9, 8, 7, 6 };
System.out.println(Arrays.toString{a)) ;
a = Generated.array(a,new CountingGenerator.lnteger()) i
System.out.println(Arrays.toString{a)) i
Integer[] b = Generated.array{Integer.class,
new CountingGenerator.lnteger(), 15) i
System.out.println(Arrays.toString{b)) i
1* Output:
[9, 8, 7, 6)
[O, 1, 2, 3)
[O, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
* /// ,-
An cuando la matriz a est inicializada, dichos valores se sobreescriben al pasarla a travs de Generated.array(), que sus-
tinlye los valores (pero deja la matriz original en su lugar). La inicializacin de b muestra cmo puede crearse una matriz
rellena partiendo de cero.
Los genricos no funcionan con valores primitivos, y queremos utilizar los generadores para rellenar matrices de primiti-
vas. Para resolver este problema, creamos un convertidor que toma cualquier matriz de objetos envoltorio y la convierte en
una matriz de los tipos primitivos asociados. Sin esta herramienta, tendramos que crear generadores especiales para todas
las primitivas.
11 : net/mindview/util/ConvertTo.java
package net.mindview.util
public class ConvertTo {
public static boolean(] primitive (Boolean [] in)
boolean[] result = new boolean[in.lengthl ;
for (int i = O; i < in .length i i++)
result [il = in [i]; 11 Conversin automtica
return result
public static char [] primitive (Character [] in)
char [] result = new char [in .lengthl
for(int i = O; i < in.length i++}
result[il = inri] i
500 Piensa en Java
return result;
public static byte [] primitive {Byte (] in)
byte[] result = new byte [in.length] ;
for(int i = O; i < in.length; i++)
result (i] = in ti] ;
return result;
public static short [] primitive (Short [] in)
short[] result = new short[in . length] i
for(int i = O; i < in . length i++)
result (i] = in ti] ;
return result;
public static int[] primitive{Integer[] in) (
int [] result = new int [in.length]
for{int i = O; i < in . length; i++)
result(i] = inri] i
return result;
public static long [] primitive (Long (] in)
long[) result = new long(in.length];
for(int i = O; i < i n. length; i++)
result (i] = in ti] ;
return result;
public static float [] primitive {Float (] in)
float[J result = new float(in . length];
f or(int i = O; i < in.length; i++)
resul t (i] = in [i] ;
return result;
public static double [] primitive {Double (] in)
double[] result = new double[in.length];
for(int i = O; i < in.length; i++)
result (i] = in [i] ;
return resul t;
)
/// , -
Cada versin de primitive() crea una matri z de primitivas apropi ada con la longitud correcta, y luego copia los elementos
desde la matri z in de tipos envoltori o. Observe que el mecani smo de conversin automti ca entra en accin en la expresin:
result ti] = in ti] ;
He aqu un ej emplo que muestra cmo puede utili zarse ConvertTo con ambas versiones de Generated.array( ):
jj : arraysjPrimitiveConversionDemonstration.java
import java.util .* ;
import net.mindview.util .* ;
public class PrimitiveConversionDemonstration
public static void main (String [] args) (
Integer[] a = Generated.array{Integer . class,
new CountingGenerator.lnteger(), 15);
int[] b = ConvertTo.pr imitive(a);
System.out.println{Arrays . toString(bl)
boolean[] e = ConvertTo . primitive{
Generated.array{Boolean . class,
new CountingGenerator.Boolean(), 7;
System.out.println{Arrays . toString(c ;
/ * Output:
[O, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
[true, false, true, false, true, false, truel
*///,-
16 Matrices 501
Finalmente, he aqu un programa que prueba las herramientas de generacin de matrices utilizando clases
RandomGenerator:
JI: arraysjTestArrayGeneration.java
JI Comprobar las herramientas que utilizan generadores
/1 para rellenar matrices .
impert java.util.*;
import net.mindview.util.*
import static net.mindview.util.Print.*
public class TestArrayGeneration {
public static void main(String[] argsl
int size = 6;
boolean(] al = ConvertTo.primitive{Generated.array(
Boolean.class, new RandomGenerator.Boolean(), size)) i
print ("al = 11 + Arrays. toString (al));
byte[] a2 = ConvertTo.primitive(Generated.array(
Byte.class, new RandomGenerator.Byte(), size)) i
print (
OI
a2 = 01 + Arrays. toString (a2) ) i
char[] a3 = ConvertTo.primitive(Generated.array(
Character.class,
new RandomGenerator.Character(), size)} i
print (01 a3 = 01 + Arrays. toString (a3) ) ;
short[] a4 = ConvertTo.primitive(Generated.array(
Short.class, new RandomGenerator.Short(), size));
print (
OI
a4 = 01 + Arrays. toString (a4) ) ;
int[] aS = ConvertTo.primitive(Generated.array(
Integer.elass, new RandomGenerator.lnteger(), size)) i
print(OIaS = JI + Arrays.toString(aS))
long[] a6 = ConvertTo.primitive(Generated.array(
Long . class, new RandomGenerator.Long(), size))
print (
OI
a6 = 01 + Arrays. toString (a6) } i
float[] a7 = ConvertTo.primitive(Generated.array(
Float.elass, new RandomGenerator.Float(}, size));
print(
OI
a7 = n + Arrays.toString{a7}) i
double[] aS = ConvertTo .primitive {Generated.array(
Double.class, new RandomGenerator.Double(}, size));
print (" aS = ti + Arrays. toString (aS) ) i
/* Output:
al (true, false, true, false, false, true]
a2 [104, -79, -76, 126, 33, -641
a3 (Z, n, T, e, Q, r]
a4 [-13408, 22612, 15401, 15161, -28466, -126031
a5 [7704, 7383, 7706, 575, 8410, 63421
a6 [7674, 8804, 8950, 7826, 4322, 8961
a7 [0.01, 0.2, 0.4, 0 .79, 0.27, 0.451
a8 [0.16, 0.87, 0.7, 0.66, 0 .87, 0.591
*///,-
Esto garantiza tambin que cada versin de ConvertTo.primitive() funcione correctamente.
Ej ercicio 11: (2) Demuestre que el mecanismo de conversin automt ica (autoboxing) no funciona con las matrices.
Ejercicio 12: (1 ) Cree una matriz ini ciali zada de valores double utili zando CountingGenerator. Imprima los resul-
tados.
502 Piensa en Java
Ejercicio 13: (2) Rellene un objeto String utilizando CountingGenerator.Cbaracter.
Ejercicio 14: (6) Cree una matriz de cada tipo primitivo y luego rellene cada matriz utilizando CountingGenerator.
fmprima cada matriz.
Ejercicio 15: (2) Modifique ContainerComparison.java creando un generador para BerylliumSphere y efecte los
cambios necesarios en main() para utilizar dicho generador con Generated.array( ).
Ejercicio 16: (3) Comenzando con CountingGenerator.java, cree una clase SkipGenerator que produzca nuevos valo-
res por el procedimiento de apli car un incremento que se fijar de acuerdo con un argumento del construc-
tor. Modifique TestArrayGeneration.java para demostrar que la nueva clase funciona correctamente,
Ejercicio 17: (5) Cree y pruebe un objeto Generator para BigDecimal, y compruebe que funciona con los mtodos
Generated.
Utilidades para matrices
En java.util, podr encontrar la clase Arrays, que contiene un conjunto de mtodos estticos de utilidad para matrices. Hay
seis mtodos bsicos: equals(), para comprobar si dos matrices son iguales (y un mtodo deepEquals() para matrices mul-
tidimensionales); ml(), del que ya hemos hablado en este captulo; sort( ), para ordenar una matriz; binarySearch(), para
encontrar un elemento en una matriz ordenada; toString(), para generar una representacin de tipo String para una matri z;
y hashCode(), para generar el valor hash de una matriz (veremos qu significa esto en el Captulo 17, Anlisis detallado
de los contenedores), Todos estos mtodos estn sobrecargados para poder usarlos con todos los tipos primitivos y con obje-
tos. Adems, Arrays.asList( ) toma cualqui er secuencia o matriz y la transfomla en un contenedor de tipo List; ya hemos
hablado de este mtodo en el Captulo 11 , Almacenamiento de objetos.
Antes de analizar los mtodos de Arrays, existe otro mtodo til que no fOnTIa parte de Arrays.
Copia en una matriz
La biblioteca estndar de Java proporciona un mtodo esttico, System.arraycopy(), que permite copiar matrices de fonua
bastante ms rpida que se si utiliza un bucle for para reali zar la copia manualmente, System.arraycopy( ) est sobrecar-
gado para aceptar todos los tipos. He aqu un ejemplo donde se manipulan matrices de valores ot:
JI : arrays/CopyingArrays.java
1/ Utilizacin de System. arraycopy()
import java.util.*;
import static net,mindview.util.Print. *
public class CopyingArrays {
public static void main(String[] args) {
int[] i = new int[7];
int [] j = new int {lO] ;
Arrays.fill(i, 47);
Arrays.fill(j, 99);
print ( " i = !I + Arrays. toString (i) ) ;
print ( n j = " + Arrays. toStrng (j) ) ;
System.arraycopy(i, O, j, 0, i.length) i
print ("j = " + Arrays. toStrng (j) ) i
int[] k = new int[S];
Arrays.fill(k, 103);
System.arraycopy(i, O, k, O, k.length);
print("k = " + Arrays.toString(k));
Arrays.fill(k, 103);
System. arraycopy (k, O, i , O, k.length} i
print ( " i = " + Arrays. toString (i) } i
II Objetos,
Integer[] u
Integer [] v
new Integer(lO] i
new Integer(5] i
/ *
i
j
j
k
i
u
v
u
Arrays.fill (u, new Integer (47 ) ;
Arrays.fill (v, new I nteger (99 ;
print ( "u = " + Arrays . toString (u ) ) ;
print ( "v =- " + Arrays. toString (v ;
System. arraycopy (v, o , u, u.length/ 2, v.length) i
print ( "u = " + Arrays. toString (u ) ) ;
Output:
[47, 47, 47, 47, 47, 47, 47J
[99, 99, 99, 99, 99, 99, 99, 99, 99, 99J
[47 , 47, 4 7, 47, 47, 47, 47, 99, 99, 99J
[47, 47, 47 , 47, 4 7J
[103, 103, 103, 103, 103, 47, 47J
[47, 47, 47, 47, 47, 47, 47, 47, 47, 47J
[99, 99, 99, 99, 99J
[47, 4 7, 47, 47 , 4 7, 99, 99 , 99, 99, 99J
* /// , -
16 Matrices 503
Los argumentos de arraycopy() son la matriz de origen, o el desplazamiento dentro de la matriz de ori gen a partir del cual
hay que empezar a copiar, la matriz de destino, el desplazamiento dentro de la matriz de destino donde debe empezar la
copia y el nmero de elementos que hay que copiar. Naturalmente, cualquier violacin de las fronteras de las matrices gene-
rar una excepcin.
El ej emplo muestra que pueden copiarse tanto matrices de primitivas como matrices de objetos. Sin embargo, si copiamos
matrices de objetos, entonces slo se copian las referencias, no producindose ninguna duplicacin de los propios objetos.
Esto se denomina copia superjicial (consulte los suplementos en lnea del libro para conocer ms detalles).
System.arraycopy() no realiza conversiones automticas para los tipos primitivos: las dos matrices deben tener exactamen-
te el mismo tipo.
Ejercicio 18: (3) Cree y rellene una matriz de objetos BerylliumSphere. Copie esta matriz en otra nueva y demuestre
que se trata de una copia superficial.
Comparacin de matrices
Arr.ys proporciona el mtodo equals( ) para comprobar si dos matrices son iguales; dicho mtodo est sobrecargado para
poder trabajar con todos los tipos de primitivas y con Object. Para ser igual es, las matrices deben tener el mi smo nmero
de elementos y cada elemento tiene que ser equi valente al elemento correspondiente de la otra matri z, utili zndose el mto-
do equals() para cada elemento (para las primiti vas, se utiliza el mtodo equals( ) de la correspondiente clase envoltorio,
por ej empl o, Integer.equals() para in!). Por ej emplo:
11 : arrays / ComparingArrays.java
II Utilizacin de Arrays . equals( )
import java . util . *;
import static net.mindview. util . Print .* ;
public class ComparingArrays {
public static void mai n (String[] argsl {
int [] al = new i nt[lO] ;
i nt[J a2 = new int[lOl ;
Ar r ays.fill(al, 47 } ;
Ar r ays. f ill(a2, 47 ) ;
pr int {Arrays.equals (al, a2 )} ;
a2 [3J = 11;
print (Arrays.equals (al, a2 ) );
String[J sI = new String(4];
Arrays.fill(sl, "Hi " );
String [] s2 = { new String ( IIHi JI) I new Stri ng ("Hi" ) ,
new String ( UHi
Jl
), new String ( "Hi
ll
) };
print {Arrays.equals {51, s2 )) ;
504 Piensa en Java
/ * Output:
true
false
true
* ///,-
Originalmente, a 1 y a2 son exactamente iguales, por lo que la salida es "true", pero despus se cambia uno de los elemen-
tos, lo que hace que el resultado sea "false". En el ltimo caso, todos los elementos de sI apuntan al mi smo objeto, pero 52
tiene cinco objetos diferentes. Sin embargo, la igualdad de matrices est basada en los contenidos (a travs de
Object.equals( )), por lo que el resultado es "true".
Ejercicio 19: (2) Cree una clase con un campo int que se inicialice a partir de un argumento de un CQnstruclOr. Cree dos
matrices de estos objetos, utilizando valores de iniciali zacin idnticos para cada matriz, y demuestre que
Arrays.equals() dice que son distintas. Ailada el mtodo equals() a la clase para corregir el problema.
Ejercicio 20: (4) Ilustre mediante un ejemplo el uso de deepEquals() para matrices multidimensionales.
Comparaciones de elementos de matriz
Las operaciones de ordenacin deben realizar comparaciones basadas en el tipo real de los objetos. Por supuesto, una solu-
cin consiste en escribir un mtodo de ordenacin distinto para cada uno de los posibles tipos, pero dicho cdigo no ser
reutili zable para tipos nuevos.
Uno de los objetivos principales del diseo en el campo de la programacin consiste en "separar las cosas que cambian de
las cosas que no lo hacen", y aqu el cdigo que no vara es el algoritmo de ordenacin general, siendo la nica cosa que
cambia entre un uso y el siguiente la forma en que se comparan los objetos. Por tanto, en lugar de incluir el cdigo de com-
paracin en muchas rutinas de ordenacin diferentes, se utiliza el patrn de diseo basado en estrategia
2
. Con una Estrategia,
la parte del cdigo que vara se encapsula dentro de una clase separada, (el objeto Estrategia). Lo que se hace es entregar
un objeto Estrategia al cdigo que permanece invariable, el cual utiliza dicha Estrategia para implementar su algoritmo. De
esa fonna podemos hacer que los diferentes objetos expresen diferentes formas de comparacin y entregarles a todos ell os
el mismo cdigo de ordenacin.
Java dispone de dos maneras para proporcionar la funcionalidad de comparacin. La primera es con el mtodo de compa-
racin "natural" que se aade a una clase implementando la interfaz java.lang.Comparable. Se trata de una interfaz muy
simple con un nico mtodo, compareTo( ). Este mtodo toma como argumento otro objeto del mismo tipo y produce un
valor negativo si el objeto actual es inferior al argumento, cero si el argumento es igual y un valor positi vo si el objeto actual
es superior al argumento.
He aqui una clase que implementa Comparable e ilustra la comparabilidad empleando el mtodo de la biblioteca estndar
de Java Arrays.sort( ):
// : arrays/CompType.java
// Implementacin de Comparable en una clase.
import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*;
public class CompType implements Comparable<CompType>
int i;
int ji
private static int count = 1;
public CompType(int nI, int n2 )
i nI;
j = n2;
public String toString ()
String result = 11 [i = 11 + i + ", j = 11 + j + "]";
2 Design Paltems, Erich Gamma el al. (AddisonWesley, 1995). Consulte Thinking in Pattems (n-th Java) en IVlvw.MindViev.nel.
if{count++ % 3 == O)
result += "\n
H

return result;
public int compareTo(CompType rv) {
return (i < rV.i ? -1 : (i == rV.i ? O 1));
private static Random r = new Random(47)
public static generator()
return new Generator<CompType>{} {
public CompType next{) {
return new CompType(r.nextlnt{lOOl ,r.nextlnt(lOO)};
}
} ;
public static void main (String [] args) {
CompType [] a =
Generated.array(new CompType[12] , generator()) i
print ("befare sorting:") i
print (Arrays,toString (a)) i
Arrays.sort (a) ;
print ( "after sorting: 11) i
print{Arrays.toString(a)) ;
/ * Output:
befare sorting:
[[i = 58, j = 55], [i=93,j 61], [i=61,j=29]
[i 68, j O], [i = 22, j 7], [i = 88, j = 28]
[i
[i
51, j
20 I j
89] ,
58] ,
[i 9, j 78], [i = 98, j = 61]
[i = 16, j = 40], [i = 11, j = 22]
after sorting:
[[i = 9, j 78], [i = 11, j
[i 20, j 58], [i 22, j
[i 58, j 55], [i 61, j
[i 88, j 28], [i 93, j
* /// >
22], [i = 16, j = 40]
7], [i = 51, j = 89]
29], [i 68, j O]
61], [i = 98, j = 61]
16 Matri ces 505
Cuando definimos el mtodo de comparacin somos responsables de decidir qu quiere decir comparar uno de nuestros
objetos con otro. Aqu , slo se utilizan los valores i para la comparacin, mientras que los valores j se ignoran.
El mtodo generator( ) produce un objeto que implementa la interfaz Generator creando una clase interna annima. sta
construye objetos Comp1)rpe inicializndolos con valores aleatorios. En main(), se utiliza el generador para rellenar una
matriz de objetos CompType, que se ordena a continuacin. Si no hubiramos implementado Comparable obtendramos
una excepcin ClassCastException en tiempo de ejecucin cuando tratramos de invocar sort(). Esto se debe a que sort()
proyecta su argumento sobre Comparable.
Ahora suponga que alguien nos entrega una clase que no implementa Comparable, o que alguien nos entrega una clase que
s que implementa Comparable, pero decidimos que no nos gusta la fonna en que funciona y que preferiramos disponer
de un mtodo de comparacin di stinto para ese tipo de objeto. Para resolver el problema, creamos una clase separada que
implementa una interfaz llamada Comparator (ya la hemos presentado brevemente en el Captulo 11 , Almacenamiento de
objetos). Se trata de un ejemplo del patrn de di seo basado en Estrategia. Tiene dos mtodos, compare( ) y equals(). Sin
embargo, no tenemos necesidad de implementar equals() salvo por necesidades especiales de rendimiento, ya que cada vez
que se crea una clase, sta hereda implcitamente de Object, que tiene un mtodo equals(). Por tanto, podemos limitamos
a utilizar el mtodo equals() predetenninado de Object sin por ello dejar de satisfacer las imposiciones de la interfaz.
La clase Collections (que examinaremos con ms detalle en el siguiente captulo) contiene un mtodo reverseOrder() que
produce un objeto Comparator para invertir el sistema natural de ordenacin. Esto puede aplicarse a CompType:
506 Piensa en Java
11: arrays / ReverSe.java
II El comparador Collections.reverseOrder()
import java.util.*;
import net.mindview.util.*
import static net.mindview.util.Print.*;
public class Reverse {
public static void main(String(] args ) {
CompType[J a = Generated.array (
new CompType[12], CompType.generator());
print ("before sorting:" ) i
print (Arrays . toString (a )) ;
Arrays.sort (a, Collections.reverseOrder( ))
print ( "after sorting:");
print (Arrays.toString(a))
1* Output:
before sorting:
[ [i
58,
i

551 , [i
93,
i
611 , [i
61,
[i 68,
i
01 , [i 22,
i
71 , [i 88,
i
[i 51,
i
891 , [i 9,
i
781, [i
98,
[i 20,
i
581 , [i
16,
i

401 , [i
11,
after sorting:
[ [i
98,
i

611 , [i
93,
i

611, [i 88,
[i 68,
i
01 , [i

61,
i

291 , [i 58,
[i 51,
i
891 , [i 22,
i
71 , [i 20 ,
[i 16,
i
40] , [i 11,
i
221 , [i
9,
* ///,-
i

291

281
i

611
i

221
i
281
i
551
i
581
i
781
Tambin podemos escribir nuestro propio objeto Comparator. El siguiente ejemplo compara objetos CompType basados
en sus valores j , en lugar de en sus valores i:
11 : arraysjcomparatorTest.java
II Implementacin de un comparador para una cla s e .
import java.util .*
import net . mindvi ew . util .* ;
import s t atic net . mindview. util.Print. * ;
class CompTypeComparator implements Comparator <CompType>
public int compare(CompType 01, CompType 02) {
return (01. j < 02. j ? -1 : (01. j == 02. j ? O : 1))
public class ComparatorTest {
publ ic static void main (String (1 args ) {
CompType(] a = Generated.arra y(
new CompType[12] , CompType.generator()) i
print ("before sorti ng: ")
print(Arrays.toString(a)) ;
Arrays.sort(a, new CompTypeComparator())
print (" a fter sorting: ");
print(Arrays . toString(a))
1* Output :
befare sorting :
[[i 58, i 551, [i
[i 68, i 01, [ i
93, i
22, i
611, [i 61, i 291
71, [i 88, i 281
[i
[i
after
[ [i
=
[i
li
li
* ///0-
51, j
20, i
sorting:
68,
i =
88,
i
58,
i
98,
89], [i
58], [i
O] , [i
=
28]. [ i
55] , [i
61] , [i
9, j = 78], [i = 98, i = 61]
16, i = 40], [i = 11, j = 22]
22,
i
7] , [i
=
11,
i =
22]
61,
i =
29] , li =
16,
i =
40]
20,
i = 581 , li
=
93,
i =
61]
9,
i =
78] , li =
51,
i =
89]
16 Matrices 507
Ejercicio 21: (3) Trate de ordenar una matri z de objetos del Ejercicio 18. Implemente Comparable para corregir el pro-
blema. Ahora cree un objeto Comparator para disponer los objetos en orden inverso.
Ordenacin de una matriz
Con los mtodos de ordenacin predefinidos, podemos ordenar cualquier matriz de primitivas o cualquier matriz de objetos
que implementen Comparable o dispongan de un objeto Comparator asociado.
3
He aqu un ejemplo que genera objetos
Stri ng aleatorios y los ordena:
/1 : arrays/StringSorting.java
// Ordenacin de una matriz de objetos String.
import java.util.*;
import net.mindview.util. *
import static net.mindview.util.Print.*
public class StringSorting {
public static void main (String[] args)
String(] sa = Generated.array{new String[20],
new RandomGenerator.String {Sl l;
print ( "Before sort: " + Arrays. toString (sal 1 ;
Arrays.sort (sa) ;
print ( "After sort: ti + Arrays. toString (sa) ) ;
Arrays.sort (sa, Collections.reverseOrder() ) ;
print ( "Reverse sort: " + Arrays. toString (sal) ;
Arrays.sort (sa, String.CASE_INSENSITIVE_ORDER) ;
print ( nCase-insensitive sort: u + Arrays. toString (sal) i
/*
Output:
Before sort: (YNzbr, nyGcF, OWZnT, cQrGs, eGZMm, JMRoE,
suEcU, OneOE, dLsmw, HLGEa, hKcxr, EqUCB, bklna, Mesbt,
WHkiU, rUkZP, gwsqP, zDyCy, RFJQA, HxxHv]
After sort: (EgUCS, HLGEa, HxxHv, JMRoE, Mesbt, OWZnT,
OneOE, RFJQA, WHkjU, YNzbr, bklna, cQrGs, dLsmw, eGZMm,
gwsgP, hKcxr, nyGcF, rUkZP, suEcU, zDyCy]
Reverse sort: (zDyCy, suEcU, rUkZP, nyGcF, hKcxr, gwsgP,
eGZMm, dLsmw, cQrGs, bklna, YNzbr, WHkjU, RFJQA, OneOE,
OWZnT, Mesbt, JMRoE, HxxHv, HLGEa, EgUCB]
Case-insensitive sort: [bklna, cQrGs, dLsmw, eGZMm, EgUCB,
gwsqP, hKcxr, HLGEa, HxxHv, JMRoE, Mesbt, nyGcF, OneOE,
OWZnT, RFJQA, rUkZP, suEcU, WHkjU, YNzbr, zDyCy)
* ///,-
Una de las cosas que observamos al analizar la salida en los algoritmos de ordenacin para objelos String es que la ordena-
cin es lexicogrfica, por lo que se ponen primero ladas las palabras que comienzan por letras maysculas y despus todas
las palabras que comienzan con minscula. Si queremos agrupar las palabras con independencia de si las letras son mays-
culas o minsculas, se utiliza String,CASE_INSENSIT IVE_ ORDER, como se muestra en la ltima llamada a sort() del
ejemplo anterior.
3 Sorprendentemente, Java 1.0 y J.l no incluan ningln soporte para la ordenacin de objetos String.
508 Piensa en Java
El algoritmo de ordenacin que se utiliza en la biblioteca estndar de Java est diseado para comportarse de manera p-
tima para el tipo concreto que se est ordenando: un algoritmo Quicksort para primitivas y una ordenacin estable por com-
binacin para objetos. No es necesario preocuparse acerca de las cuestiones de rendimiento a menos que la herramienta de
perfilado nos indique que el proceso de ordenacin est actuando como un cuell o de botella.
Bsquedas en una matriz ordenada
Una vez ordenada una matriz, podemos real izar una bsqueda rpida de un elemento concreto invocando Arrays.
binarySearch( ). Sin embargo, si tratamos de utilizar binarySearch( ) en una matri z desordenada los resultados sern
impredecibles. El siguiente ejemplo utiliza un objeto RandomGenerator.lnteger para rellenar una matriz y luego emplea
el mismo generador para producir valores de bsqueda:
11: arrays/ArraySearching .java
II Utilizacin de Arrays.binarySearch{) .
import java.util. *;
import net.mindview.util.*
import static net.mindview.util.Print.*
public class ArraySearching {
public static void main(String(] args)
Generator<Integer> gen =
new RandamGeneratar.lnteger(lOOO);
int[J a = CanvertTa . primitive(
Generated.array(new Integer(25] , gen
Arrays. sort (a)
print( "Sorted array: " + Arrays.toString(a
while (true) {
int r = gen.next()
int location = Arrays.binarySearch(a, r);
if(location >= O) {
print("Location af " + r + " is " + lacation +
", a [" + lacatian + "J = " + a [location);
break; 11 Salir del bucle while
1* Output:
Sorted array' [128, 140, 200, 207, 258, 258, 278, 288, 322, 429, 511, 520, 522, 551, 555,
589,693,704,809,861,861,868,916,961,9981
Location of 322 is 8, a[8] = 322
*///,-
En el bucle while, se generan elementos de bsqueda con valores aleatorios hasta que se encuentra uno de ellos.
Arrays.binarySearch( ) devuelve un valor mayor o igual que cero si se encuentra el elemento que se est buscando. En
caso contrario, devuelve un valor negativo que representa el lugar en el que debera insertarse el elemento, si se est man-
teniendo la ordenacin de la matriz de forma manual. El valor devuelto es:
- (punto de insercin) - 1
El punto de insercin es el ndice del primer elemento superi or a la clave, o bien a.size(), si todos los elementos de la matri z
son inferiores a la clave especi ficada.
Si una matriz contiene elementos dupli cados, no hay ninguna garanta en lo que respecta a cul de esos duplicados se encon-
trar. El algoritmo de bsqueda no est diseado para soportar elementos duplicados, aunque s que los tol era. Si necesita-
mos una li sta ordenada de elementos no duplicados, hay que emplear TreeSet (para mantener la ordenacin) o
LinkedHashSet (para mantener el orden de insercin). Estas clases se encargan de resolver por nosotros todos los detall es,
de manera automti ca. Slo en aquellos casos en los que aparezcan cuellos de botella de rendimiento debera sust ituirse una
de estas clases por una matriz cuyo mantenimiento se realizar de forma manual.
16 Matrices 509
Si ordenamos una matri z de objetos utilizando un objeto Comparator (las matrices primiti vas no penniten realizar ordena-
ciones con un objeto Comparator), hay que incluir ese mi smo objeto Comparator cuando se invoque a binarySearch()
(empleando la versin sobrecargada de este mtodo). Por ejemplo, podemos modificar el programa StringSorting.java para
realizar una busqueda:
JI: arrays/AlphabeticSearch.java
JI Bsqueda con un comparador .
import java . util .*;
import net.mindview.util.*
public class AlphabeticSearch
public static void main (String [) args) {
String[} sa Generated.array(new String[30] ,
new RandomGenerator.String(5));
Arrays.sort{sa, Strlng.CASE INSENSITIVE ORDER),
System.out.println (Arrays. toString (sa) ) ;
int index = Arrays.binarySearch(sa, sa[lO],
String.CASE_INSENSITIVE_ORDER) i
System. out.println("Index: 11+ index + n\n"+ sa(indexl) i
/ * Output:
[bklna, cQrGs, cXZJo, dLsmw, eGZMm, EqUC8, gwsqP, hKcxr, HLGEa, HqXum, HxxHv, JMRoE,
JmzMs, Mesbt, MNvqe, nyGcF, ogoYW, OneOE, OWZnT, RFJQA, rUkZP, sgqia, slJrL, suEcU,
uTpnX, vpfFv, WHkjU, xxEAJ, YNzbr, zDyCy]
Index: la
HxxHv
*///>
El obj eto Comparator debe pasarse como tercer argumento al mtodo binarySearch( ) sobrecargado. En este ejempl o, el
xito de la operacin de bsqueda est garantizado porque el elemento de bsqueda se selecciona a partir de la propia matri z.
Ejercicio 22: (2) Demuestre que los resultados de reali zar una bsqueda binaria con binarySearch( ) eo una matriz
desordenada son impredecibles.
Ejercicio 23: (2) Cree una matri z de objetos (nteger, relloela con valores int aleatorios (utili zando conversin autom-
tica) y di spngala en orden inverso utili zando Comparator.
Ejercicio 24: (3) Demuestre que se pueden realizar bsquedas en la clase del Ejerci cio 19.
Resumen
En este captulo, hemos visto que Java proporciona un soporte razonable para las matrices de tamao fijo y bajo nivel. Este
tipo de matri z pone nfasis en el rendimiento ms que en la flexibilidad, de fonna similar al modelo de matrices de C y C++.
En la versin inicial de Java, las matrices de tamao fijo y bajo ni vel eran absolutamente necesarias, no slo porque los dise-
adores de Java el igieron incluir tipos primiti vos (tambin por razones de rendimiento), sino tambi n porque el soporte para
contenedores en dicha versin era mnimo. Por esa razn, en las primeras versiones de Java siempre era razonable elegir las
matrices como fonna de implementacin.
En las versiones subsigui entes de Java, el soporte de contenedores mejor significativamente y ahora los contenedores ti en-
den a ser preferibles a las matrices desde todos los puntos de vista, salvo el de rendimiento. Incluso en lo que al rendimien-
to respecta, el de los contenedores ha mejorado significativamente. Como hemos indicado en otros puntos del libro, los
problemas de rendimiento nunca suelen presentarse, de todos modos, en los lugares donde nos los imaginamos.
Con la adicin del mecani smo de conversin automtico de tipos primiti vos y del mecanismo de genricos, resulta muy sen-
cillo almacenar primiti vas en los contenedores, lo que const ituye un argumento adicional para sustituir las matrices de bajo
nivel por contenedores. Puesto que los genricos penniten producir contenedores seguros en lo que respecta a los tipos de
obj etos, las matri ces han dejado tambin de suponer una ventaja a ese respecto.
Como se ha indicado en este captulo, y como podr ver cuando trate de utili zarlos, los genricos resultan bastante hostiles
en lo que a las matri ces se refiere. A menudo, incluso cuando conseguimos que los genricos y las matrices funcionen con-
510 Piensa en Java
juntamente de alguna manera (como veremos en el siguiente captulo). seguimos teniendo advertencias "no comprobadas"
durante la compilacin.
En muchas ocasiones, los di sei'ladores del lenguaje Java me han dicho directamente que se deberan utilizar contenedores
en lugar de matri ces; esos comentarios surgan a la hora de discutir ejemplos concretos (yo he estado empleando matrices
para ilustrar tcnicas especficas y no tena, por tanto, la opcin de utilizar contenedores).
Todas estas cuestiones indican que los contenedores son preferibles, por regla general. a las matrices a la hora de programar
con las versiones recientes de Java. Slo cuando est demostrado que el rendimiento constituye un problema (y que utilizar
una matriz pemlitir resolverlo) deberamos recurrir a la utilizacin de matrices.
Puede ser una afinnacin demasiado categrica, pero algunos lenguajes no di sponen en absoluto de matrices de tamao fijo
y bajo nivel. Slo disponen de contenedores de tamao variable con una funcionalidad significativamente superior a la de
las matrices estilo C/e ++/Java. Python,4 por ejemplo, tiene un tipo li st que utiliza la sintaxis bsica de matrices, pero dis-
pone de una funcionalidad muy superior; podemos incluso heredar a partir de ese tipo:
#: arraysjPythonLists.py
aList = [1, 2, 3, 4, 5]
print type (aList) # <type 'list' >
print aList # [1, 2, 3, 4, 5]
print aList[4] # 5 Indexacin bsica de la lista
aList.append(6) # Ae puede cambiar el tamao de las listas
aList += [7, 8] # Aadir una lista a una lista
print aList # [1, 2, 3, 4, 5, 6, 7, 8]
aSlice = aList[2:4]
print aSlice # [3, 4]
class MyList (list): # Heredar de una lista
# Definir un mtodo, puntero 'this' es explcito:
def getReversed{self) :
reversed = self[:J # Copiar lista utilizando fragmentos
reversed.reverse () # Mtodo predefinido de la lista
return rever sed
list2 MyList{aList) # No hace falta 'new' para crear objetos
print type{list2) # <class ' __ main __ .MyList'>
print list2.getReversed() # [8, 7, 6, 5, 4, 3, 2, 1]
#,-
La sintaxis bsica de Python se ha presentado en el captulo anterior. En este ejemplo, se crea una lista simplemente inser-
tando una secuencia de objetos separados por comas entre corchetes. El resultado es un objeto cuyo tipo en tiempo de eje-
cucin es list (la salida de las instrucciones print se muestra en fonna de comentarios en la misma lnea). El resultado de
impri mi r un objeto Iist es el mi smo que si utilizramos Arrays.toString( ) en Java.
La creacin de una subsecuencia de un obj eto list se ll eva a cabo a partir de los "fragmentos", incluyendo el operador ' :'
dentro de la operacin de ndi ce. El tipo Iist tiene muchas otras operaciones predefinidas.
MyList es una defi ni cin de class; las clases base se indican entre parnt esis. Dentro de la clase, las instrucciones def espe-
ci fi can mtodos y el primer argumento al mtodo es automti camente equivalente a this en Java, salvo porque en Python es
explcito y se utiliza el identifi cador self por conveni o (no se trata de una palabra clave). Observe que el constructor se bere-
da automticamente.
Aunque todo en Pytbon es realmente un obj eto (incluyendo los tipos enteros y de coma flotante), seguimos di sponiendo de
una puerta de escape, en el sentido de que se pueden optimizar partes del cdi go cuyo rendimi ento sea crtico escribiendo
extensiones en e, e++ o con una herramientas especial llamada Pyrex, que est diseada para acelerar fcilmente la ej ecu-
cin del cdi go. De esta fonna, podemos mantener la pureza de la orientacin a obj etos si n que ello nos impida reali zar
mejoras de rendimi ento.
4 Vease www.Pylhon.org.
16 Matrices 511
El lenguaje PHp5 va un paso ms all toda\ a al disponer de un nico tipo de matriz, que acta tanto como una matriz con
ndices de tipo entero cuanto como una matriz asociativa (un mapa).
Resulta interesante preguntarse despus de todos estos aos de evolucin del lenguaje Java, si los diseadores incluiran las
primitivas y las matrices de bajo nivel en el lenguaje si tuvieran que comenzar de nuevo con la tarea de diseo. Si se deja-
ran estas funcionalidades fuera del lenguaje, sera posible construir un lenguaje orientado a objetos verdaderamente puro (a
pesar de lo que se diga, Java no es un lenguaje orientado a objetos puro, precisamente debido a los remanentes de bajo nivel).
El argumento de la eficiencia siempre parece bastante convincente, pero el paso del tiempo ha ido mostrando una evolucin
en el sentido de alejarse de esta idea para utilizar componentes de ms nivel , como los contenedores. Y hay que tener en
cuenta, que si se pudieran incluir los contenedores en el cuerpo principal del lenguaje, como sucede en otros lenguajes,
entonces el compilador dispondria de mejores oportunidades para mejorar el cdigo.
Dejando aparte estas especulaciones tericas, 10 cierto es que las matrices siguen estando presentes y que nos encontrare-
mos con ellas una y otra vez a la hora de leer cdigo. Sin embargo, los contenedores son casi siempre una mejor opcin.
Ejercicio 25: (3) Reescriba Python Lists.py en Java.
Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico rile rhinking in Jav(I A/ll/orafed Sollltio1/ Cuide, disponible para
la venta en w\\w.MindJ1ew.1IC'I.
s Vase www.php.net.
Anlisis detallado
de los contenedores
En el Captulo 11, Almacenamiento de objetos, helLos introducido los conceptos y la
funcionalidad bsica de la bi bl ioteca de contenedores de Java, y aquellas explicaciones son
suficientes para poder empezar a utilizar contenedores. En este captulo se analiza dicha
importante biblioteca con ms profundidad.
Para poder sacar el mximo contenido de la biblioteca de contenedores necesitamos conocer ms detalles de los que se pre-
sentaron en el Captulo 11 , Almacenamiento de los objetos, pero el material que en este captulo se presenta depende de
temas ms avanzados (como los genricos), lo cual es la razn de que se pospusiera hasta este momento.
Despus de incluir una panormica completa de los contenedores veremos cmo funcionan los mecanismos de hash y cmo
escribir mtodos hashCode() y equals() que funcionen con los contenedores a los que se les haya aplicado un mecanismo
de hasJ. Veremos tambin por qu hay diferentes versiones de algunos contenedores y qu criterios usar para elegir una ver-
sin u otra. El capnllo finaliza con una exploracin de las utilidades de propsito general y de una serie de clases especiales.
Taxonoma completa de los contenedores
La seccin "Resumen" del Capnilo 11 , Almacenamiento de objetos, mostraba un diagrama simplificado de la biblioteca de
contenedores de Java. He aqu un diagrama ms completo de la biblioteca de colecciones, incluyendo las clases abstractas
y los componentes heredados (con la excepcin de las implementaciones de Queue) :
Genera Genera
, era or ,-4-------------, o ee Ion , ... ------------, ap ,

. ________ i_
-------. ._L_ _l_ .. _.J __ : AbstractMap :
'Listlterator ,..-_ql!n-e!L-' List' I Set I 'Queue' _ L __ ______
, , " I
.- - ---- - - A-e,
_____________ )J,
: AbstraetCollection : .. ,,1. __ J_
, , , -- ---
" ____ 4
: SortedMap
._----("---
---- .
. " : Sort
. __ L _____ .,:.:' \ ____ __
: AbstractList: : AbstraetSet :
.--e,--- - ----
edSet :
____ 4
I HasMap I I TreeMap I
f
/ldentil YHaShMap I
I LinkedHashMap I
Hashtable I
I WeakHashMa
pi (heredado)
Colleetions
Arrays
Taxonoma completa de los contenedores
514 Piensa en Java
Java SES aade:
La interfaz Queue (para implementarla se ha modificado LinkedList como vimos en el Captulo 11.
A/macenomienro de objetos) y sus implementaciones PriorityQueue (cola con prioridad) y diversas variantes de
BlockingQueuc (cola bloqueante) que se vern en Capitulo 21. Concurrencia.
Una interfaz ConcurrentMap y su implementacin ConcurrentHashMap (mapa hash concurrente), que tam-
bin se utiliza para el mecanismo de hebras de programacin que se aborda en el Capinllo 21. Concl/rrencia.
CopyOnWrteArrayList y CopyOnWriteArraySet. tambin para concurrencia.
EnumSet y EnurnMap. implementaciones especiales de Set y Map para utilizar con enumeraciones, como se
explica en el Captulo 19, Tipos enumerados.
Varias utilidades en la clase Collections.
Los recuadros de trazos representan clases abstractas y podemos ver varias clases cuyos nombres comienzan por
"Abstraet". Estas clases pueden resultar un poco confusas al principio, pero son simplemente herramientas que implemen-
tan parcialmente una interfaz concreta. Si estuviramos creando nuestra propia clase Set. por ejemplo, no comenzaramos
con la interfaz Set e implementaramos todos los mtodos; en lugar de ello heredaramos de AbstractSet y haramos el tra-
bajo rnn.imo necesario para definir nuestra nueva clase. Sin embargo, la biblioteca de contenedores contiene la suficiente
funcionalidad como para satisfacer nuestras necesidades casi siempre, por lo que nom1almente podemos ignorar cualquier
clase que comience con "Abstract".
Relleno de contenedores
Aunque el problema de imprimir contenedores est resuelto, el proceso de rellenar contenedores sufre la misma deficiencia
que java.util.Arrays. Al igual que con Arrays, existe una clase de acompaamiento denominada CoUections que contiene
mtodos de utilidad estticos, incluyendo uno que se llama ftll() Y que sirve para rellenar colecciones. Al igual que la ver-
sin de Arrays, este mtodo fill( ) se limita a duplicar una nica referencia a objeto por todo el contenedor. Adems. slo
funciona para objetos List, aunque la li sta resultante puede pasarse a un constructor o a un mtodo addAI1( ):
11: containers/FillingLists.java
11 Los mtodos Collections.fill() y Collections.nCopies () .
import java.util.*
class StringAddress
private String Si
public StringAddress (String s) { this. s Si}
public String toString ( ) {
return super. toString ( ) + 11 + S;
public class FillingLists {
public static void main (String [] args) {
List<StringAddress> list= new ArrayList<StringAddress>(
Collections.nCopies(4, new StringAddress("Hello"))) i
System.out.println(list) i
Collections.fill(list, new StringAddress("World!"));
System.out.println{list) i
/ * Output, ISample)
[StringAddress@82ba41 HelIo, StringAddress@82ba41 HelIo,
StringAddress@82ba41 HelIo, StringAddress@82ba41 HelIo]
[StringAddress@923e30 World!, StringAddress@923e30 World!,
StringAddress@923e30 World!, StringAddress@923e30 World!J
*///,-
17 Anlisis detallado de los contenedores 515
Este ejemplo muestra dos formas de rellenar un objeto Collection con referencias a un nico objeto. La primera,
Collections.nCopies( ), crea un objeto List que se pasa al constmctor; el cual rellena el objeto ArrayList.
El mtodo toString() en StringAddress llama a Object.toString(), que genera el nombre de la clase seguido por la repre-
sentacin hexadecimal sin signo del cdigo hash del objeto (generada por un mtodo hashCode( . Podemos ver. analizan-
do la salida, que todas las referencias apuntan al mi smo objeto y ste tambin se cumple despus de invocar un segundo
mtodo, Collections.fill( J. El mtodo fill () es todava menos til debido al hecho de que slo puede sustituir elementos que
ya se encuentren denlro del objeto List no pennitiendo aadir nuevos elementos.
Una solucin basada en generador
Casi todos los subtipos de CoUection tienen un constructor que toma como argumento otro objeto Coll ection, a partir del
cual puede rellenar el nuevo contenedor. Por tanto, para poder crear fcilmente datos de prueba, todo lo que necesitamos es
construir una clase que tome como argumentos del constructor un objeto Generator (definido en el Captulo 15, Genricos,
y analizado con ms detalles en el Captulo 16, Matrices) y un valor quantt)' (cantidad):
11: net/mindview/util/CollectionData.java
II Una coleccin rellena con datos utilizando un objeto generador.
package net.mindview.utili
import java . util.*;
public class CollectionData<T> extends ArrayList<T> {
public CollectionData(Generator<T> gen, int quantity}
for {int i = Di i < quantitYi i++}
addlgen.nextl)) ;
}
/1 Un mtodo genrico de utilidad:
public static <T> CollectionData<T>
list(Generator<T> gen, int quantity)
return new CollectionData<T> (gen, quantity);
Este ejemplo utiliza Generator para insertar en el contenedor tantos objetos como necesitemos. El contenedor resultante
puede entonces pasar al constructor de cualquier tipo Collection, y dicho constructor copiar los datos dentro de la instan-
cia. Tambin puede utilizarse el mtodo addAU() que fonma parte de todos los subtipos de Collection para rellenar un obje-
to Collection existente.
El mtodo genrico de utilidad reduce la cantidad de texto que hay que escribir a la hora de uti lizar la clase.
Coll ectionDat a es un ejemplo del patrn de dise.'o A daptador
I
; adapta un objeto Generator al constructor de un tipo
Coll ecti on.
He aqu un ejemplo que inicializa un contenedor LinkedHashSet:
11: containers/CollectionDataTest.java
import java.util. *;
import net.mindview.util. *
class Government implements Generator<String> {
String() foundation = ("strange women lying in ponds +
"distributing swerds is no basis fer a system of " +
"government
tl
) .split(" 11 );
private int indexi
public String next() { return foundation[index++] }
1 Puede que esto no encaje estrictamente en la defmicin de adaptador, tal como se define en el libro de Design Pallerns, pero creo que cumple con el espi-
ritu de este palrn.
516 Piensa en Java
public class CollectionDataTest {
public static void main (String [] argsJ {
Set<String> set = new LinkedHashSet<String>(
new COllectionData<String>(new Government(), 15));
/1 Utilizacin del mtodo de util i dad:
set . addAl l{CollectionData.list(new Government {), 15));
Systern.out.println(set) ;
/* Output:
[strange, women, lying, in, ponds, distributing, swords,
is, no, basis, tor, a, system, of, government]
* /// : -
Los elementos estn en el mi smo orden en que se insertaron, porque un contenedor LinkedHashSet manti ene una li sta enla-
zada que preserva el orden de insercin.
Todos los generadores definidos en el e apndo 16, Matrices, estn ahora di sponibles a travs del adaptador Coll ectionDat a.
He aqu un ej empl o donde se utili zan dos de ellos:
1/ : containersjCollectionDataGeneration. java
/1 Utilizacin de los generadores definidos en el Captulo 16, Matrices .
import java . util.*
import net.rnindview.util. *
public class CollectionDataGenerat i on
public static void mai n (String [] args)
System. out.println(new ArrayList<String>(
CollectionData.list( // Convenience method
new RandomGenerator . String(9) , 10)))
System. out.pr intln(new HashSet<Integer>(
new CollectionData<Integer>(
new RandornGenerator . lnteger(), la})) i
/ * Output:
(YNzbrnyGc, FOWZnTcQr, GseGZMmJM, RoEsuEcUO, neOEdLsmw,
HLGEahKcx, rEqUCBbkI, naMesbtWH, kjUrUkZPg, wsqPzDyCyl
[573,4779,871,4367,6090,7882,2017,8037,3455,299]
*///:-
La longitud de la cadena de caracteres producida por Ra ndornGener ator. Slr ing se controla mediant e el argumento del
constructor.
Generadores de mapas
Podemos usar la misma tcni ca para un objeto Map, pero eso requiere una clase Pair (par), ya que es necesario producir
una parej a de obj etos (una cl ave y un valor) en cada llamada al mtodo next( ) de un generador, con el fin de rell enar un
conlenedor Map:
1/: net/rnindview/util/Pair.java
package net . mindview.util
public class Pair<K,V> {
public final K keYi
publ ic final V va l ue
public Pair(K k, V v)
key = k;
value = Vi
}
/// : -
Los campos key (clave) y v. lue (valor) son de tipo pblico y final , de modo que Pa .. se convierte en un objeto de transfe-
rencia de dalos de slo lectura (o mensajero).
17 Anli sis detallado de los contenedores 517
El adaptador para Map puede ahora utilizar disti ntas combinaciones de generadores, irerabl es y val ores constantes para
rell enar obj etos de ini ciali zacin de Map:
// : net / mindview/ util / MapData.java
// Un mapa relleno con datos utilizando un objeto generador.
package net.mindview.util
import java.util. * ;
public class MapData<K,V> extends LinkedHashMap<K,V> {
// Generador de un nico par:
public MapData {Generator<Pair<K,V gen, int quantity)
for ( int i = O; i < quantity; i++ ) {
pair<K,V> p = gen.next () ;
put(p.key, p.value) i
11 Dos generadores separados :
public MapData(Generator<K> genK, Generator<V> genV,
int quantity) {
}
for ( int i = O; i < quantity; i++ }
put (genK, next () I genV. next () );
/1 Un generador de clave y un nico valor:
public MapData (Generator<K> genK, V value, int quantity) {
for {int i "" O; i < quantitYi i++ ) {
put {genK.next () , value ) ;
}
II Un iterable y un generador de valor:
public MapData ( Iterable<K> genK, Generator<V> genV) {
for ( K key , genK) {
put (key, genV . next ( i
}
II Un iterable y un nico valor:
public MapData (Iterable<K> genK, V value ) {
for ( K key genK) {
put (key, value ) ;
II M t odos genricos de ut i lidad:
public static <K,V> MapData<K,V>
map (Generator<Pair<K,V gen, int quantity)
return new MapData<K,V> (gen, quantiey) i
public s tatic <K,V> MapData<K,V>
map (Generator<K> genK, Generator<V> genV, int quantity)
return new MapData<K,V> (genK, genV, quantity) i
public static <K,V> MapData<K,V>
map (Generator<K> genK, V value, int quantity) {
return new MapData<K,V> {genK, value, quantity);
public static <K,V> MapData<K,V>
map ( Iterable<K> genK, Generator<V> genV)
return new MapData<K,V> (genK, genV) ;
public static <K,V> MapData<K,V>
518 Piensa en Java
map (Iterable<K> genK, V value) {
return new MapData<K, v> (genK, valuel i
)
///,-
Esto nos permite decidir si queremos utili zar un nico objeto Generator<Pair<K,V, dos generadores separados, un
generador y un valor constante, un objeto Iterable (lo que inc luye cualquier tipo ColJection) y un generador, o un objeto
lterable y un nico valor. Los mtodos genricos de utilidad reducen la cantidad de texto que es necesario escribir a la hora
de crear un objeto MapDat .
He aqu un ejemplo en el que se utili za MapData. El generador Letters tambin implementa Iterable produciendo un obje-
to Iteralor; de esta fonna, puede utilizarse para probar los mtodos MapData.map() que funcionan con un objeto Iterable:
1/: containers/MapDataTest.java
import java.util . *
import net . mindview.util. *
impor t static net.mindview.util.Print.*
class Letters implements Generator<Pair<Integer ,String,
Iterable<Integer> {
private int size = 9;
private int number = 1;
private char letter = 'A'
public Pair<Integer,String> next()
return new Pair<Integer,String>(
number++, "" + letter++) i
public Iterator<Integer> iterator()
return new Iterator<Integer> () {
public Integer next () { return number++;
public boolean hasNext () { return number < size
public void remove{) {
}
} ;
throw new UnsupportedOperationException();
public class MapDataTest {
public static void main{String[] args) {
II Generador de un par:
print(MapData.map(new Letters(), 11)) i
II Dos generadores separados:
print{MapData.map(new CountingGenerator . Character(),
new RandomGenerator.String{3), 8))
II Un generador de clave y un nico valor:
print{MapData . map(new CountingGenerator . Character(),
"Value",6));
II Un Iterable y un generador de valor :
print(MapData.map(new Letters(),
new RandomGenerator.String(3)))
II Un Iterable y un nico valor:
print(MapData.map(new Letters(), "Pop"))
} / * Output ,
{l=A, 2=B, 3=C, 4=D, 5=E, 6=F, 7=G, 8=H, 9=I, lO=J, ll=K}
{a=YNz, b=brn, c=yGc, d=FOW, e=ZnT, f=cQr, g =Gse, h=GZM}
{a=Value, b=Value, c=Value, d=Value, e=Value, f =Value}
{1 =mJM, 2=RoE , 3=suE, 4=cUO, 5=neO, 6=EdL, 7=smw, 8=HLG}
{l=Pop, 2=Pop, 3=Pop, 4=Pop, 5=Pop, 6=Pop, 7=Pop, B=Pop}
* /// ,-
17 Anlisis detallado de los contenedores 519
Este ejemplo tambin util iza los generadores del Captul o 16, Matrices.
Podemos crear cualqui er conj unto de datos generados para mapas o colecciones usando estas herrami entas. y luego inicia-
lizar Ull objeto Ma p o Coll eeti on ut ili zando el constructor o los mtodos Map.putAII() o Colleetion.addAII Q.
Utilizacin de clases abstractas
Una solucin alternati va al problema de generar datos de prueba para contenedores consiste en crear implementaciones per-
sonalizadas de Coll ection y Map. Cada contenedor de j ava.ut il tiene su propia clase abstracta que proporciona una imple-
mentacin parcial de di cho contenedor, por lo que lo nico que hace falta es impl ementar los mtodos necesari os para
obtener el contenedor deseado. Si el contenedor resultante es de slo lectura, como suele suceder para los datos de prueba,
el nmero de mtodos que es necesari o proporcionar se minimi za.
Aunque no resulta parti cul anneme necesari o en este caso, la sigui ente solucin tambi n proporciona la oportunidad de ilus-
trar otro patrn de di seo: el patrn de di seo denominado Peso mosca. Utilizamos este patrn de di seo cuando la solu-
cin nonnal requiere demasiados objetos, o cuando la produccin de obj etos nonnales requi ere demasiado espacio. El patrn
de di seo Peso mOSCa extemali za parte del objeto de modo que, en lugar de que todo lo del objeto est contenido en el pro-
pio objeto, parte del objeto o la totalidad del mismo se busca en una tabla externa, ms efi cient e (o se genera medi ant e algn
otro clcul o que permita ahorrar espacio).
Un aspecto important e de este ejemplo consiste en demostrar lo relat ivament e simple que es crear sendos obj etos Map y
Coll eeti on personalizados heredando a part ir de las clases de java.utiJ.Abstraet. Para crear un mapa Map de slo lectura
heredamos de Abstract Map e implementamos entrySet( ). Para crear un conjunto Set de slo lecrura, heredamos de
AbstraetSet e impl ementamos iter ator( ) y size( ).
El conjunto de datos de este ejemplo es un mapa de los paises del mundo y sus capitales
2
El mtodo eapital s() genera un
mapa de paises y capitales. El mtodo names() genera una lista de los nombres de paises. En ambos casos, podemos obte-
ner un listado parcial proporcionando un argumento int que indique el tamao deseado:
// : net / mindview/ util / Countries.java
// Mapas y listas nPeso mosca " de datos de ejemplo.
package net.mindview.util
import java.util .* ;
import static net . mindvi e w.util.Print.*
public class Countries {
public sta tic final String [J [J DATA = {
// f r ica
{"ALGERIA", IIAlgiers"}, {"ANGOLA
II
, "Luanda"},
{"BENIN", " Po rto-Novo"}, {"BOTSWANA", 11 Gaberone" },
{ "BULGARIA", "Sof ia"}, {"BURKINA FASO" , 11 Ouagadougou " },
{ "BURUNDI", "Buj umbura " },
{ II CAMEROONII , "Yaounde"}, {"CAPE VERDE", IIpraia
ll
},
{"CENTRAL AFRICAN REPUBLIC", "Bangui"},
{"CHAO", "N I djamena"}, {"COMOROS
II
, "Moroni "},
{ 11 CONGO" , "Brazzaville"}, {"DJIBOUTI 11 , "Dij ibouti" },
{ "EGYPT", II Cairo "}, { "EQUATORIAL GUINEA", "Malabo"},
{ "ERITREA", IIAsmara"} , {"ETHIOPIA","Addis Ababa"},
{"GABON" , IILibreville"} , {"THE GAMBIA
II
,"Banjul"},
{ "GHANA", "Acera"}, {"GUINEA", "Conakry"},
{"BISSAU", "Bissau"},
{"COTE D' IVOIR ( IVORY COASTl ", "Yamoussoukro"},
{ "KENYA" , "Nairobi"}, {"LESOTHOII, "Maseru " } ,
{ "LIBERIA" , "Monrovia"}, {"LIBYA 11 , IITripoli " } ,
{"MADAGASCAR" , "Antananarivo" }, {"MALAWI" , 11 Lilongwe " },
{ "MALI " , 11 Bamako 11 }, {"MAURITANIA 11 , "Nouakchot t " } ,
{ "MAURITIUS", " Por t Louis " }, {"MOROCCO", "Rabat " },
2 Estos datos se han extrado de Internet. A lo largo del tiempo diversos lectores me han enviado correcciones.
520 Piensa en Java
{"MOZAMBIQUE", "Maputo"}, {"NAMIBIA", "Windhoek"},
{"NIGER", ItNiamey"}, {"NIGERIA", "Abuja"},
{"RWANDA", "Kigali"},
{"SAO TOME E PRINCIPE" I "Sao Tome"},
{IISENEGAL", "Dakar"}, {"SEYCHELLES
II
, "Victoria"},
{"SIERRA LEONEII, tlFreetown"}, {"SOMALIA", "Mogadishu"},
{ "SOUTH AFRICA 11 , "Pretoria/Cape Town 11 },
{"SUDAN", "Khartoum"},
{"SWAZlLANDII,IIMbabane"}, {"TANZANIA". "Dedama"},
{IITOGO
II
, "Lome"}, {IITUNISIA" I "Tunis"} I
{IIUGANDA", "Kampala"},
{"DEMOCRATIC REPUBLIC OF THE CONGO (ZAIRE) ",
"Kinshasa") ,
{"ZAMBrA", "Lusaka"}, { "ZIMBABWE", "Harare"},
II Asia
{ "AFGHANISTAN" I "Kabul " } I { " BAHRAIN" , "Manama " } ,
{"BAN'GLADESH" , "Dhaka II}, {"BHUTAN", "Thimphu 11 } I
{uBRUNEI
II
,"Bandar Seri Begawan"},
{"CAMBODIA", IIPhnom Penh"},
{ " CHINA" , "Beij ing" }, {"CYPRUS ", "Nicosia"} ,
{" INDIA" ,"New Delhi u}, {" INDONESIA u, "Jakarta" } ,
{"IRAN", "Tehran"} , {"IRAQ","Baghdad
ll
},
{" ISRAEL", "Jerusalem" }, {"JAPAN", "Tokyo"},
{"JORDAN", "Amman"}, {"KUWAIT", "Kuwait City"},
{"LAOS" , "Vientiane" }, { " LEBANON" , "Beirut" } ,
{"MALAYSIA", "Kuala Lumpur"}, { "THE MALDIVES", "MaIe"},
{HMONGOLIA", "UIan Bator"},
{"MYANMAR {BURMA) ", "Rangoon
H
},
{"NEPAL", "Katmandu"}, {"NORTH KOREA", "p'yongyang"},
{"OMAN", "Muscat"}, {"PAKISTAN", "Islamabad"},
{ "PHILIPPINES" , "Manila" }, {IIQATAR", "Doha"} ,
{"SAUOI ARABIA", "Riyadh" }, {"SINGAPORE", "Singapore"},
{ "SOUTH KOREA"," Seoul " }, {" SRI LANKA", " Colombo" } ,
{"SYRIA", "Damascus"},
{"TAIWAN (REPUBLIC OF CHINA}", "Taipei"),
{"THAlLAND", "Bangkok"}, { "TURKEY", "Ankara"},
{"UNITED ARAB EMlRATES","Abu Dhabi
ll
},
{"VIETNAM", "Hanoi"}, {"YEMEN", "Sana ' a"},
II Australia y Oceania
{"AUSTRALIA" , "Canberra"}, {" FIJI" , "Suva" } ,
{" KIRIBATI" , "Bairiki"} ,
{"MARSHALL ISLANDS", "Dalap-Uliga-Darrit"},
{ "MICRONESIA 11 , "Pal ikir"}, { "NAURU" , " Yaren"} ,
{"NEW ZEALAND", "Wellington
ll
}, {"PALAU", "Koror"},
{"PAPUA NEW GUINEA", "Port Moresby"},
{" SOLOMON ISLANDS", "Honaira"}, {"TONGA", "Nuku' alofa"} ,
{"TUVALU", "Fongafale"}, {"VANUATU", "< Port-Vila"},
{"WESTERN SAMOA", "Apia"},
II Europa del Este y la Unin Sovitica
{"ARMENIA", "Yerevan"}, { "AZERBAIJAN", "Baku"},
{"BELARUS (BYELORUSSIA) ", "Minsk"),
{"GEORG lA" , "Tbilisi"} ,
{"KAZAKSTAN", "Almaty"} , {"KYRGYZSTAN", HAlma-Ata"},
{"MOLDOVA", "Chisinau"}, {"RUSSIA", "Moscow"},
{"TAJIKISTAN", "Dushanbe
ll
}, {"TURKMENISTAN", "Ashkabad"},
{"UKRAINE", "Kyiv"}, { IIUZBEKISTAN", "Tashkent"},
II Europa
{" ALBANIA" , "Tirana" } ,
{" AUSTRIA" , "Vienna" } ,
{"ANDORRA", 11 Andorra la Vella"},
{"BELGIUM", IIBrussels"},
} ;
17 Anlisis detallado de los contenedores 521
{"BOSNIA", II_II}, {IIHERZEGOVINA", "Sarajevo"},
{ "CROATIA" , 11 Zagreb"}, {"CZECH REPUBLIC"," Prague" } I
{ 11 DENMARK 11 I "Copenhagen 11 } I {" ESTONIA 11 I "Tall inn 11 } I
{" FINLAND" , "Helsinki" }, {" FRANCE 11 , "Paris"} ,
{"GERMANY", "Berlin"}, {"GREECE", "Athens
ll
},
{"HUNGARY", "Budapest"} I {"ICELAND", "Reykjavik"},
{ltIRELAND" , "Dublin"} , {"ITALY" , "Rome"},
{"LATVIA", "Riga"}, {"LIECHTENSTEINII, "Vaduz"} I
{" LITHUANIA", "Vilnius"}, {"LUXEMBOURG", "Luxembourg"},
{"MACEDONIA" , 11 Skopj e t1} t {"MALTA 11 , "Valletta" } ,
{"MONACO" I "Monaco"}, {"MONTENEGRO" I 11 Podgorica "} I
{"THE NETHERLANDS", "Amsterdam"} I {"NORWAY", "Oslo"} I
{"POLAND" I "warsaw"} I {"PORTUGAL", "Lisbon" },
{"ROMANIA ti, "Bucharest"}, {uSAN MARINO", 11 San Marino"},
{IISERBIAu, IIBelgrade
ll
}, {IISLOVAKIAu, IIBratislava"},
{"SLOVENIA", "Ljuijana
u
}, {"SPAIN", "Madrid"},
{"SWEDEN", "Stockholm"}, {"SWITZERLAND", "Berne"},
{"UNITED KINGDOM" , ULondon
u
} , {"VATICAN CITY","--_u},
1I Amrica del Norte y Amrica Central
{ "ANTIGUA ANO BARBUDA"," Saint John' Sil} ,
{"BAHAMAS", "Nassau"},
{"BARBADOS", IIBridgetown" }, {"BELIZE", uBelmopan"},
{ "CANADA", "Ottawa"}, {"COSTA RICA", "San Jose
ll
},
{"CUBAI!, IIHavana"}, {"DOMINICA
u
, "Roseau" },
{"DOMINICAN REPUBLIC" , IISanto Domingo"},
{"EL SALVADOR", 11 San Salvador"},
{ "GRENADAII , "Saint George' s''},
{uGUATEMALA", "Guatemala City"},
{uHAITI", UPort-au-Prince"},
{"HONDURAS ", "Tegucigalpa"}, {IIJAMAICA
II
, "Kingston"},
{uMEXICO",IIMexico City"}, {IINICARAGUA" I "Managua" },
{UPANAMA", " Panama Cityll}, {"ST. KITTS", u_,,},
{"NEVIS", "Basseterre"}, {"ST. LUCIA", "Castries
ll
},
{"ST. VINCENT ANO THE GRENADINES", "Kingstown
u
},
{"UNITED STATES OF AMERICAu, "Washington, D. C."},
II Amrica del Sur
{" ARGENTINA" , "Buenos Aires"},
{"BOLIVIA", "Sucre (legal) ILa Paz (administrative) u},
{uBRAZILII, "Brasilia
u
}, { "CHILE", "Santiago"},
{uCOLOMBIA", "Bogota"} I {"ECUADOR", "Quito"},
{"GUYANAII , "Georgetown"}, {"PARAGUAY", "Asuncion"},
{II PERU" I 11 Lima "}, {u SURINAMEII , u paramaribo"} ,
{"TRINIDAD ANO TOBAGO", "Port of Spain"},
{"URUGUAY", "Montevideo"}, {"VENEZUELA", "Caracas
lI
},
11 Utilizacin de AbstractMap implementando entrySet()
private static class FlyweightMap
extends AbstractMap<String,String> (
private static class Entry
implements Map.Entry<String,String>
int index
Entry(int index) { this.index = index
public boolean equals (Object o) {
return DATA[index] [O] .equals(o);
public String getKey () { return DATA [index] [O]; }
public String getValue () ( return DATA [index] [1] ;
public String setValue(String value) {
throw new UnsupportedOperationException();
522 Piensa en Java
public int hashCode () (
return DATA [index] [O] .hashCode();
JJ Utilizar AbstractSet implementando size{) e iterator()
static class EntrySet
extends AbstractSet<Map.Entry<String,String
private int size;
EntrySet(int size)
if (size < O)
this.size = O;
JJ No puede tener un tamao mayor que la matriz:
else if(size > DATA.length)
this. size DATA.length
else
this.size size
public int size () { return size; }
private class lter
implements lterator<Map.Entry<String,String
JJ Slo un objeto Entry por cada lterator:
private Entry entry = new Entry(-1);
public boolean hasNext () {
return entry.index < size - 1:
public Map.Entry<String,String> next()
entry.index++;
return entrYi
public void remove()
throw new UnsupportedOperationException();
public
lterator<Map. Entry<String, String iterator () {
return new lter{);
private static Set<Map.Entry<String,String entries
new EntrySet (DATA.length};
public Set<Map. Entry<String, String entrySet () {
return entries;
JI Crear un mapa parcial de un nmero 'size
'
de pases:
static Map<String, String> select (final int size) {
return new FlyweightMap () {
public Set<Map.Entry<String,String entrySet()
}
} ;
return new EntrySet(size) i
static Map<String,String> map = new FlyweightMap {);
public static Map<String, String> capitals () {
return map; JJ El mapa completo
public static Map<String, String> capitals (int size) {
return select(size) i JI Un mapa parcial
sta tic List<String> names
new ArrayList<String>(map.keySet());
JI Todos los nombres:
public statie List<String> names{)
JI Un lista parcial:
return names;
public statie List<String> names (int size) {
17 Anlisis detallado de los contenedores 523
return new ArrayList<String> (select (size) . keySet () ) i
public statie void main (String [] args) {
print(capitals{lO)) ;
print (names (10)) i
print{new HashMap<String,String>{capitals(3)));
print{new LinkedHashMap<String,String>(capitals(3)));
print{new TreeMap<String,String>(capitals(3)));
print(new Hashtable<String,String>(capitals(3)));
print(new HashSet<String> (names (6) )) i
print(new LinkedHashSet<String>(names(6}));
print(new TreeSet<String> (names (6) )) i
print(new ArrayList<String>(names(6))) i
print(new LinkedList<String>(names(6))) i
print (capi tal s () . get ("BRAZIL") ) i
} /* Output,
{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto- Novo,
BOTSWANA=Gaberone, BULGARIA=Sofia, BURKINA FASO
=Ouagadougou, BURUNDI=Bujumbura, CAMEROON=Yaounde,
CAPE VERDE=Praia, CENTRAL AFRICAN REPUBLIC=Bangui}
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO,
BURUNDI, CAMEROON, CAPE VERDE, CENTRAL AFRICAN REPUBLIC]
{BENIN=Porto - Novo, ANGOLA=Luanda, ALGERIA=Algiers}
{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=porto-Novo}
{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo}
{ALGERIA=Algiers, ANGOLA=Luanda, BENIN=Porto-Novo}
[BULGARIA, BURKINA FASO, BOTSWANA, BENIN, ANGOLA, ALGERIA]
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO]
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO]
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO]
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO]
Brasilia
*///,-
La matriz bidimensional de cadenas de caracteres DATA es pblica, por lo que se la puede emplear en cualquier otro lugar.
FlyweightMap debe implementar el mtodo entrySet(), que requiere tanto una implementacin personalizada de Set como
una clase Map.Entry personalizada. He aqu parte de la solucin "peso mosca": cada obj eto Map.Entry si mpl emente alma-
cena su ndi ce, en lugar de almacenar la clave y el valor reales. Cuando invocamos getKey() o getYalue(), utiliza el ndi-
ce para devolver el elemento de DATA apropiado. El contenedor EntrySet asegura que su tamao (size) no sea superior al
de DATA.
Podemos ver la otra parte de la solucin "peso mosca" implementada en EntrySet.lterator. En lugar de crear un objeto
Map.Entr)' para cada pareja de datos en DATA, slo existe un objeto Map,Entry por cada iterador. El objeto Entr)' se
utili za como una ventana a los datos: slo contiene un ndice (index) a la matri z esttica de cadenas de caracteres. Cada vez
que invocamos next() para iterator, se incrementa la variable ndi ce de un objeto Entry, de modo que apunte a la siguien-
te pareja de elementos y luego el ni co objeto Entry de ese objeto Iterator es devuelto por next()3
El mtodo select( ) genera un contenedor FlyweightMap que contiene un conjunto EntrySet del tamao deseado, el cual
se usa en los mtodos eapitals( ) y names() sobrecargados que se ilustran en main().
3 Los mapas de ja\'a.util realizan copias masivas utilizando getKcy( ) y gctValue( ), de modo que esta solucin funciona. Si un mapa personalizado fuera
a copiar simplemente el objeto l\1ap.Enlry completo entonces esta tcnica causara problemas.
524 Piensa en Java
Para algunas pmebas, el tamao limitado de Counlries es un problema. Podemos usar la mi sma tcnica para generar con-
tenedores personali zados ini cializados que tengan un conjunto de datos de cualquier tamao. A continuacin se muestra una
clase de tipo List que puede tener cualquier tamafi o y que se preiniciali za con datos de tipo Int eger:
// : net / mindviewf util / CountinglntegerList.java
// Lista de cualquier longitud con datos de ejemplo.
package net.mindview.util
import java.util.*;
public class CountinglntegerList
extends AbstractList<Integer> {
priva te int size;
public CountinglntegerList ( int size )
this.size = size < O ? O : size
public Integer get (int index)
return Integer.valueOf (index ) ;
public int size () { return size;
public static void main (String[] args )
System.out.println(new CountinglntegerList (30 )) ;
1* Output:
[O, 1, 2, 3, 4, 5, 6, 7, 8 , 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
* /// ,-
Para crear una li sta de slo lectura a partir de AbstractList , hay que implementar get( ) y size( ). De nuevo, se utiliza una
solucin de tipo "peso mosca": get( ) produce el valor cuando lo pedimos, por lo que la lista no ti ene, en la prctica, que
estar rellena.
He aqu un mapa que conti ene valores de tipo Integer y String un vocos preiniciali zados; puede tener cualquier tamao:
11 : net / mindview/ util / CountingMapData.java
II Mapa de longitud limitada con datos de ejemplo.
package net.mindview.util;
import java.util.*
public class CountingMapData
extends AbstractMap<Integer,String>
private int size;
private static String[] chars
"A BCD E F G H 1 J K L M N O P Q R S T U V W X y Z"
.split ( " " ) ;
public CountingMapData(int size)
if {size < O) this.size = O;
this.size = size;
private sta tic class Entry
implements Map.Entry<Integer,String>
int index
Entry (int index ) { this. index =: index;
public boolean equals (Object o ) {
return Integer. valueOf (index ) . equals (o ) ;
public Integer gecKey () { return index; }
public String getValue ( ) {
return
chars[index % chars . length) +
Integer . toString{index I chars.length) ;
17 Anlisis detallado de los contenedores 525
public String setValue (String value ) {
throw new UnsupportedOperationException () ;
public int hashCode ( ) {
return Integer. valueOf (index) . hashCode () i
public Set<Map. Entry< I nteger, String entrySet () {
// LinkedHashSet mantiene el orden de inicializacin:
Set<Map.Entry< I nteger, String entries =
new LinkedHashSet<Map . Entry<Integer,String();
for ( int i "" O; i < size i ++ )
entries.add (new Entry ( i i
return entries;
public static void main (String [} args ) {
System. out.println (new CountingMapData ( 60 ) ;
} / * Output,
{O=AO, 1=80,
1 4 =00, 15=PO,
26 =A1, 27=81,
38=M1 , 39=N1,
SO=Yl , Sl=Zl,
* ///,-
2=CO, 3=DO, 4=EO, 5=FO, 6 =GO,
16=QO, 17=RO, 18=50, 19=TO,
28=Cl, 29=D1, 30=El, 31=Fl,
40=01, 41=Pl, 42=Q1, 43=R1,
S2 =A2, 53=8 2, 54=C2, 55=D2,
7=HO,
20=UO ,
32=G1,
44=51,
56=E2 ,
8",10, 9 =JO, lO=KO, 11=LO, 12=MO, 13=NO,
21=VO, 22=WO, 23 =XO, 24=YO, 25=ZO,
33 =H1 , 34=I1 , 35=J1, 36 =K1, 37 =L1,
45=Tl , 46=Ul, 47=Vl, 48=Wl, 49=Xl,
57=F2 , 58=G2 , 59 =H2}
Aqu , se utili za un contenedor LinkedHashSet en lugar de crear una clase Set personalizada, por lo que la soluci n de tipo
"peso mosca" no est completamente implementada.
Ejercicio 1:
Ejercicio 2:
Ejercicio 3:
Ejercicio 4:
Ejercicio 5:
( 1) Cree una li sta (intntelo tanto con ArrayLi st como con LinkedLi st) y rellnela utili zando Countries.
Ordene la lista e imprmala, luego aplique Colleelions,shufOe() a la lista repetidamente, imprimindola
cada vez de modo que pueda ver cmo el mtodo shufOe() aleatoriza la lista de manera diferente cada vez.
(2) Defina un mapa y un conjunto que contengan todos los pases que comi encen por ' A' .
( 1) Utili zando Countries, rellene un conjunto mltiples veces con los mi smos datos y verifique que el con-
junto termina conteniendo una nica copia de cada instanci a. Pruebe a hacer lo mi smo con HashSet,
LinkcdHashSet y TreeSet.
(2) Cree un inici ali zador de Collcetion que abra un archivo y lo descomponga en palabras usando
Text Fi le, y luego emplee las palabras como ori gen de los datos para el contenedor de Colleetion resultan-
te. Demuestre que la solucin funciona.
(3) Modifique CountiogMapData.java para implementar completamente la solucin de tipo 'peso
mosca" aadiendo una clase EntrySct personalizada como la de Countries.j ava.
Funcionalidad de las colecciones
La tabla de la pgina siguiente muestra todo lo que se puede hacer con un contenedor de tipo Coll ection (sin incluir los
mtodos que provienen automti camente de Obj eet), y, por tanto, todo lo que se puede hacer con un contenedor de tipo Set
o Li st (List tiene tambi n funcionalidad adi cional). Los contenedores de tipo Map no heredan de Colleelion, por lo que los
trataremos por separado.
Observe que no existe ningn mtodo get( ) para seleccionar elementos mediante acceso al eatorio. Eso se debe a que
Collection tambi n incluye Set, que mantiene su propia ordenacin interna (y que hace, por tanto, que las bsquedas por
acceso al eatorio no tengan ningn significado). Por tanto, si queremos examinar los elementos de un contenedor Collection,
debemos utilizar un terador.
El siguiente ejemplo il ustra todos estos mtodos. Aunque estos mtodos funcionen con cualquier cosa que implemente
Coll ection, se utili za un contenedor ArrayList como "mnimo denominador comn":
526 Piensa en Java
boolean add(T) Garantiza que el contenedor almacene el argumento que es el del tipo genrico T.
Devuelve fal sc si no aade el argumento (ste es un mtodo "opcional", descrito en
la seccin siguiente).
boolean addAII (Collection<? exl ends T Aade todos los elementos del argumento. Devuelve true si se aii.ade algn ele-
mento. ("Opcional").
void c1ear( ) ELimina todos los elementos del contenedor ("Opcional").
boolean contains(T) Devuelve true si el contenedor almacena el argumento que es de tipo genrico T.
Boolean containsAII(ColJection<? DcvucJve t.-ue si el contenedor almacena todos los elementos del argumento.
boolean isEmpty( ) Devuelve truc si el contenedor no tiene ningn elemento.
lterator<T> iterator( ) Devuelve un objeto lterator<T> que puede uti lizarse para recorrer los elemel110s
del contenedor.
Boolean remove(Object) Si el argumento est en el contenedor, se elimi na una instancia de dicho elemento.
Devuelve true si se ha producido alguna eliminacin ("Opcional").
boolean removeAlI(Collection<? Elimina todos los elementos contenidos en el argumento. Devuelve tru' si se ha
producido alguna elimi nacin ("Opcional").
Boolean retainAU(CoUection<? Retiene nicamente los elementos que estn contenidos en el argumento (una
"interseccin", segn la teora de conjuntos). Devuelve truc si se ha producido
algn cambio. ("Opcional").
int size( ) Devuelve el nmero de elementos del contenedor.
Objectll toArray( ) Devuelve una matriz que contiene todos los elementos del contenedor.
<T> TII toArray(TII a) Devuelve una matriz que contiene todos los elementos del contenedor. El tipo en
tiempo de ejecucin del resultado es el que corresponde a la matriz argumento a en
lugar de ser simplemente Obj ect .
11 : containers/ CollectionMethods.java
II Cosas que se pueden hacer con todas las colecciones.
import java.ut i l.*
import net.mindview.util.*
import static net.mindview.util.Print.*
public elass CollectionMethods {
publie static void main (String[] args )
Collection<String> e = new ArrayList<String> ( )
e.addAII (Countries . names (6 })
c.add("ten
ll
)
e.add("el even")
p:r' int (c)
II Hacer una matriz a partir de la lista:
Object[] array = c.toArray()
II Hacer una matriz de objetos String a partir de la lista:
String[] str = c.toArray(new String[O] )
II Enontrar elementos max y min elements esto significa
II diferentes cosas dependiendo de la forma
II en que est implementada la interfaz Comparable:
print (IICollections . max (e ) + Collections. max (e ) ) ;
print("Co llee tions.min (c ) = " + Collections.min (c ))
JI Aadir una coleccin a otra coleccin
Collection<String> c2 = new ArrayList<String> () ;
c2.addAll (Countries.names (6)) ;
e .addAll ( e2) ;
print (e ) i
C.remove ( Countries . DATA [O] [O] ) i
print(c} ;
c. remove (Countries.DATA [1] [O));
print(c) ;
/1 Eliminar todos los componentes contenidos
// en la coleccin proporcionada como argumento:
c.removeAll(c2 ) ;
print (e ) ;
e. addAll ( e2 ) ;
print (e ) ;
// Est un elemento en esta coleccin?
String val = Countries.DATA[3J [O] ;
17 Anlisis detallado de los contenedores 527
print ("c. contains ( " + val + It ) = It + c. contains (val ) ) ;
1/ Est una coleccin dentro de esta coleccin?
print ( "e. containsAll (c2 ) = ti + c. containsAll ( c2 ) ) ;
Collection<String> c3 =
(( List<String e l . subList (3, 5 ) i
JI Conservar todos los elementos que estn tanto
11 en c2 como en c3 (una interseccin de conjuntos) :
c2. retainAll (c3) i
print (c2) ;
11 Eliminar todos los elementos de
11 c2 que tambin aparezcan en c3:
c2.removeAll (c3 ) ;
print ( "c2. isEmpty () = n + c2. isEmpty () ) ;
e = new ArrayList<String> () ;
c.addAll (Countries.names (6)) ;
print (e ) ;
c . clear () ; 11 Eliminar todos los elementos
print ( "after c.clear () :" + e ) ;
1* Output:
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO, ten, eleven)
Collections.max(c) = t e n
Col lections.min(c) = ALGERIA
[ALGERIA, ANGOLA, BENI N, BOTSWANA, BULGARIA, BURKINA FASO,
ten, eleven, ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO)
[ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO, ten, eleven, ALGERIA,
ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO)
[BENIN, BOTSWANA, BULGARIA, BURKINA FASO, ten, eleven, ALGERIA, ANGOLA,
BENIN, BOTSWANA, BULGARI A, BURKINA FASO)
[ten, eleven]
[ten, eleven, ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA,
BURKINA FASO)
c.contains(BOTSWANA) = true
c.containsAll(c2 ) = true
[ANGOLA, BENIN)
c2.isEmpty {) = true
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO)
after e.elear () , [)
* /1/,-
Se crean contenedores ArrayList que cont ienen diferentes conjuntos de datos y se los generaliza a objetos CollectioD, por
lo que queda claro que no se est utili zando ms que la interfaz ColJection. maine ) utiliza una serie de ej ercicios simples
para ilustrar todos los mtodos de CoUection.
528 Piensa en Java
Las siguientes secciones del captulo describen las diversas implementaciones de List, Set y Map y se indica en cada caso
(con un asterisco) cul deberia ser la opcin preferida. Las descripciones de las clases heredadas Vector, Stack y Hashtable
se dejan para el final del capitulo; aunque no deberan utilizarse estas clases, lo ms probable es que tengamos oportunidad
de verlas al leer cdigo antiguo.
Operaciones opcionales
Los mtodos que realizan diversos tipos de operaciones de adicin y eliminacin son operaciones opcionales en la interfaz
Collection. Esto significa que la clase implementadora no est obligada a proporcionar definiciones de estos mtodos que
funcionen.
Se trata de una fanna bastante inusual de definir una interfaz. Como hemos visto, una interfaz es una especie de contrato
dentro del di seo orientado a objetos. Ese contrato dice: " Independientemente de cmo decidas implementar esta interfaz,
te garantizo que puede enviar estos mensajes al objeto"4. Pero el hecho de que exista una operacin "opcional" viola este
principio fundamental ; ya que implica que al invocar algunos mtodos 110 se obtendr un comportamiento con significado.
En lugar de ello, esos mtodos generarn excepciones. Podra parecer que estamos renunciando a la seguridad de tipos en
tiempo de compilacin.
Pero las cosas no son en realidad as. Si una operacin es opcional, el compilador sigue imponiendo la restriccin de que
slo se puedan invocar los mtodos especifi cados en dicha interfaz. Esto no se parece a los lenguajes dinmicos, en los que
se puede invocar cualquier mtodo para cualquier objeto y averiguar en tiempo de ejecucin si una llamada concreta fun-
ciona
s
. Adems, la mayora de los mtodos que toman un contenedor Collection como argumento slo leen de dicha colec-
cin, y todos los mtodos de "lectura" de CoHection no son opcionales.
Para qu querramos defrnir mtodos como "opcionales"? Al hacerle as, evitamos una explosin de interfaces en el dise-
o. Otros diseos de bibliotecas de contenedores siempre parecen tenninar en una confusa pltora de interfaces, para des-
cribir cada una de las variantes del tema principal. Ni siquiera resulta posible capturar todos los casos especiales en las
interfaces, porque alguien puede siempre inventar una nueva interfaz. La tcnica de "operaciones no soportadas" pennite
conseguir un objetivo importante de la biblioteca de contenedores de Java: los contenedores son simples de aprender y de
utilizar. Las operaciones no soportados son un caso especial que pueden retardarse hasta que sean necesarias. Sin embargo,
para que esta tcnica funcione:
1. La excepcin UnsupportedOperationException debe ser un suceso raro. En otras palabras, para la mayoria de
las clases, todas las operaciones deben funcionar, y slo en casos especiales esa operacin no estar soportada.
Esto es asi en la biblioteca de contenedores de Java, ya que las clases que se utilizan el 99 por ciento del tiempo
(ArrayList, LinkedList, HashSet y HashMap, as como las otras implementaciones concretas) soportan todas
las operaciones. El diseo proporciona una "puerta trasera" si queremos crear un nuevo contenedor de tipo
Collection sin proporcionar definiciones significativas para todos los mtodos de la interfaz Collection, sin que
por ello ese nuevo contenedor deje de encajar dentro de la biblioteca existente.
2. Cuando una operacin no est soportada, puede existir una probabilidad razonable de que aparezca una excep-
cin UnsupportedOperationException en tiempo de implementacin, en lugar de despus de haber enviado el
producto al cliente. Despus de todo, esa excepcin indica que hay un error de programacin: se ha empleado una
implementacin incorrectamente.
Merece la pena resear que las operaciones no soportadas slo son detectables en tiempo de ejecucin y representan, por
tanto, una comprobacin dinmica de tipos. Si el lector proviene de un lenguaje con tipos estticos como e++, Java puede
parecer simplemente otro lenguaje con tipos estticos. Por supuesto que Java tiene comprobacin esttica de tipos, pero
tambin tiene una cantidad significativa de comprobacin de tipos dinmica, por lo que resulta dificil decir si Java es un
tipo de lenguaje u otro. Una vez que comience a entender esto, ver otros ejemplos de comprobacin dinmica de tipos en
Java.
4 Utilizo aqu el tmlino intcrfaz tanto para describir la palabra clave interface como el significado ms general de "los mtodos soportados por lLna clase
o subclasc".
s Aunque esto suene extraiio y posiblemente sea nutil al ser descrito de esta fonna. hemos visto, especialmente en el Captulo 14, "formacin de tipos,
que esta especie de comportamiento dinmico puede ser importanle.
17 Anlisis detallado de los contenedores 529
Operaciones no soportadas
Un origen bastante comn de la aparici n de operaciones no soportadas es cuando di sponemos de un contenedor que est
respaldado por una estructura de datos de tamao fij o. Obtenemos dichos contenedores cuando transfonnamos una matri z
en una li sta con el mtodo Arrays.asList( ). Tambi n podemos decidir que algn contenedor (incluyendo los mapas) gene-
re la excecpin UnsupportedOperation Exception util izando los mtodos "no modificables" de la clase Colleetions. Este
ejemplo ilustra ambos casos:
// : containers/Unsupported . java
/1 Operaciones no soportadas en los contenedores de Java.
import java.util.*;
public class Unsupported
static void test (String msg, List<String> list)
System.out . println{ " --- " + msg + 1I ___ 1');
Col l ection<String> e = list;
COllection<Str i ng> subList = l ist . subList(l,S) i
/1 Copia de la sublista:
Collection<String> c2 = new ArrayList<String> (subList) ;
try ( c.retainAll (c2); ) eateh( Exeeption e) (
System.out . println("retainAll() : " + e) i
t r y { c. removeAl l (c2); } catch ( Exc eption e)
System.out . p r intln("removeAll() : 11 + e) ;
t ry ( e.elear() ; } eateh(Exception e) (
System.out.println("clear{): " + e);
try ( e . add (" X" ); } eateh (Exeeption e)
System.out.println( "add(): n + e);
t ry ( e . addAll(e2); } eateh (Exeeption e)
Sy stem.out.println( "addAll(): " + e) ;
try { c.remove("C" ); } catch(Exception e)
System. out.println( " remove(): " + e);
// El mtodo List.set() modifica el valor pero no
// cambia el tamao de la estructura de datos:
try (
list.set(O, "X") i
catch(Exception e)
System.out.println( "List.set(): " + e) i
public static void main(String[] args) {
List<String> list =
Arrays.asList{I' A B e o E F G H I J K L". split(" ");
test ( "Modifiable Copy", new ArrayList<String> (list ;
test (l'Arrays.asList () " , l ist);
test ( " unmodifiableList()11,
Collections . unmodifiableList(
new ArrayList<String>(list) i
/ * Output:
--- Modif i able Copy - --
- - - Arrays. asList () - --
retai nAll (): java . lang. UnsupportedOperat i onException
removeAll(): java.lang.UnsupportedOperationException
530 Piensa en Java
clear(): java.lang UnsupportedOperationException
add(): java.lang.UnsupportedOperationException
addAll(): java.lang.UnsupportedOperationException
remove(): java.lang.UnsupportedOperationException
--- unmodifiableList () ---
retainAll (): java. lang. UnsupportedOperationException
removeAll{): java.lang.UnsupportedOperationException
clear(): java.lang.UnsupportedOperationException
add(): java.lang.UnsupportedOperationException
addAll (): java .lang. UnsupportedOperationException
remove(): java.lang.UnsupportedOperationEx ception
List . set(}: java.lang.UnsupportedOperationException
'/1/,-
Como Arrays.asList( ) produce un contenedor List que est respaldado por una matriz de tamao fijo. tiene bastante sen-
tido que las nicas operaciones soportadas sean aquellas que no cambien el famailo de la matriz. Cualquier mtodo que pro-
voque un cambio del tamao de la estructura de datos subyacente generar una excepcin Unsupported-
OperationExeeption, para indicar que se ha producido una llamada a un mtodo no soportado (un error de programacin).
Observe que siempre podemos pasar el resultado de Arrays.asList() como argumento de un constructor de cualquier colec-
cin (o utilizar el mtodo addAll() o el mtodo esttico Collections.addAll()) para crear un contenedor nonnal que per-
mita el uso de todos los mtodos; esta tcni ca se muestra en la primera llamada a test() de main(). Dicba llamada produce
una nueva estructura de datos subyacente de tamao variable.
Los mtodos "no modificables" de la clase Colleetions envuelven el contenedor en un proxy que genera una excepcin
UnsupportedOperationException si se realiza cualqui er operacin que modifique el contenedor de alguna romla. El obje-
tivo de utilizar estos mtodos es producir un objeto contenedor "constante". Posteriomlente, describiremos la li sta comple-
ta de mtodos "no modificables" de Collections.
El ltimo bloque try de test( ) examina el mtodo set( ) que fom,. parte de Lis!. Este bloque es interesante, porque pode-
mos ver cmo nos resulta til la granularidad de la tcnica de "operaciones no soportadas": La "interfaz" resultante puede
variar en un mtodo entre el objeto devuelto por Arrays.asList( ) y el que devuelve Collections.unmoditiableList( ).
Arrays.asList( ) devuelve una lista de tamaii.o fijo, mientras que Colleetions.unmodifiableList() genera una lista que no
se puede modificar. Como puede verse analizando la salida, resulta posible modificar los elementos de la lista devuelta por
Arrays.asList( ), porque esto no viola la caracterstica de "tamao tija" de dicha lista. Pero es obvio que el resultado de
unmodifiableList( ) no debe ser modificable de ninguna forma. Si usramos interfaces, esto habra requerido dos interfa-
ces adicionales: una con un mtodo set( ) funcional y otra sin dicho mtodo. Asimismo, podran requerirse interfaces adi-
cionales para diversos subtipos no modificables de Colleedon.
La documentacin de un mtodo que tome un contenedor como argumento deber especificar cules de los mtodos opcio-
nales debe implementarse.
Ejercicio 6: (2) Observe que List tiene operaciones "opcionales" adicionales que no estn incluidas en Colleetion.
Escriba una versin de Unsupported.java que pruebe estas operaciones opcionales adicionales.
Funcionalidad de List
Como hemos visto, el contenedor List bsico es bastante simple de usar: la mayor parte del tiempo nos Limitaremos a invo-
car add( ) para insertar los objetos, o a utili zar get( ) para extraerlos de uno en uno o a llamar a iterator() para obtener un
objeto Iterator para la secuencia.
Los mtodos del siguiente ejemplo cubren, cada uno de ellos, un gmpo distinto de actividades: las cosas que todas las lis-
tas pueden hacer (basicTest( )): el desplazamiento por el contenedor con un iterador (iterMotion( )) comparado con la
modificacin de valores mediante un iteradar (iterManipulation( ) , la comprobacin de los efectos de la manipulacin de
una li sta (testVisual( )); y las operaciones di sponibles nicamente para contenedores de tipo LinkedLists:
jj: containers/Lists.java
// Cosas que se pueden hacer con las li stas .
import java . util. * ;
import net.mindview. util .* ;
import static net.mindview.util.Print.*
public class Lists {
private sta tic boolean b
private static String Si
private static int i;
private static Iterator<String> it;
private static Listlterator<String> lit;
public static void basicTest(List<String> a)
a.add(l, "x") i /1 Agregar en la posicin 1
a.add("x!1); 1/ Agergar al final
// Agregar una coleccin:
a.addAll(Countries.names(25 i
17 Anlisis delallado de los conlenedores 531
// Agregar una coleccin comenzando en la posicin 3:
a.addAll(3, Countries.names(25 i
b "" a.contains{"l"l JI 15 it in there?
1/ Est toda la coleccin contenida?
b = a.containsAll {Countries.names (25l ) i
/1 Las listas permiten el acceso aleatorio, que es poco
1/ costoso para ArrayList, y caro para LinkedList:
s a.get(1); II Obtener objeto (con tipo) en la posicin 1
i = a. indexOf ("1
11
); II Determinar ndice del obj eto
b = a.isEmpty(); II Hay algn elemento?
it = a.iterator(); II Iterador normal
lit = a.listlterator(); II Listlterator
lit = a.listlterator{3) II Empezar en la posicin 3
i = a .lastlndexOf ( " 1"); II l tima correspondencia
a.remove{l); II Eliminar posicin 1
a.remove("3
11
); II Eliminar este objeto
a.set(l, "yU) II Asignar "y" a la posicin 1
II Conservar todo lo que forme parte del argumento
II (la interseccin de dos conjuntos):
a.retainAll(Countries.names{25 ;
II Eliminar todo lo que forme parte del argumento:
a.removeAll(Countries.names(25 ;
i = a.size(); II Qu tamao tiene?
a.clear() i II Eliminar todos los elementos
public static void iterMotion(List<String> al
Listlterator<String> it = a.listlterator();
b it.hasNext();
b it.hasPrevious();
s i t . next ()
i it.nextlndex()
s it.previous();
i it.previouslndex(l
public static void iterManipulation (List<String> al {
Listlterator<String> it = a.listlterator();
it.add("47")
II Hay que desplazarse a un elemento despus de add () :
it.next()
II Eliminar el elemento situado despus del recin generado:
it.remove() ;
II Hay que desplazarse un elemento despus de remove () :
it . next () i
II Cambiar el elemento situado despus del borrado:
it. set ("47
11
)
532 Piensa en Java
public statle vold testVisual (List<String> al {
print (al;
List<String> b Countries . names(2S);
print("b: " + b);
a.addA11 lb) ;
a. addA11 lb) ;
print(a) ;
1/ Insertar, eliminar y reeemplazar elementos
1/ utilizando Listlterator:
Listlterator<String> x = a,listlterator(a.size()/2} i
x .add (tiene") ;
print (al;
print(x.next()) ;
x.remove() ;
print (x.next ()) i
x.set("47") ;
print (al;
// Recorrer la lista hacia atrs:
x = a.listlterator(a.size());
while(x.hasPrevious()}
printnb(x.previous() + 11 "}i
print 1);
print ("testVisual finished");
JI Hay algunas cosas que s610 los contenedores
1/ tipo LinkedLists pueden hacer:
public statie void testLinkedtist () {
LinkedList<String> 11 = new LinkedList<String>();
ll.addAll(Countries . names(2S)) ;
print (11);
II Tratarlo como una pila, insertando :
ll.addFirst(tlone
tl
) ;
ll.addFirst("two
tl
) ;
print (11);
II Como si se "cons\J.ltara" la cima de la pila:
print 111. getFirst ()) ;
II Como si se extrajera de una pila:
print (ll.removeFirst () );
print(ll.removeFirst{)) ;
II Tratarlo como una cola, extrayendo elementos
II del final,
print{ll.removeLast()) ;
printlll);
public static void main(String[] args)
JI Crear y rellenar una nueva lista cada vez:
basicTest(
new LinkedList<String>(Countries.names(25)));
basicTest(
new ArrayList<String>(Countries.names(25)));
iterMotion(
new LinkedList<String>(Councries.names(25)));
iterMotion(
new ArrayList<String>(Countries.names(25)));
iterManipulation(
new LinkedList<St ring> (Countries.names (25) ));
iterManipulation{
new ArrayLi st<String>{Countries.names{25)));
testVisual(
17 Anlisis detallado de los contenedores 533
new LinkedList<String>(Countries.names ( 25 }) );
testLinkedList( ) ;
/ * (Execute to see output l * /// :-
En basicTest() e iterMotion(), las llamadas se reali zan en orden para mostrar la sintaxis apropiada, y aunque se captura
el valor de retomo. dicho valor no se utili za. En algunos casos, el valor de retomo no se captura en absoluto. Consulte los
detalles completos de utilizacin de cada uno de estos mtodos en la documentacin del JDK antes de utilizarlos.
Ej ercicio 7:
Ej ercicio 8:
(4) Cree tanto un contenedor ArrayList como otro de tipo Li nkedList y rellnel os utilizando el genera-
dor Countries.names( ). Imprima cada lista utili zando un iterador normal y luego inserte una li sta en la
otra empleando un iterador Listlterator , realizando las inserciones en una de cada dos posiciones. Ahora
realice la insercin comenzando por el final de la primera li sta y desplazndose hacia atrs.
(7) Cree una clase que represente una li sta genrica simplemente enlazada denominada SList, la cual, para
hacer las cosas simples, 110 impl emente la interfaz Lisl. Cada objeto Li nk (enlace) de la lista puede con-
tener una referencia al sigui ente elemento de la lista, pero no al anterior (LinkedList, por contraste, es una
li sta doblemente enlazada, lo que significa que manti ene enlaces en ambas direcciones). Cree su propio
iterador SLisllterator que, de nuevo por simplicidad, no implemente Listlterator. El unico mtodo de
SList aparte de toString(), debe ser iterator(), que generar un elemento SListlterator. La unica mane-
ra de insertar y eliminar elementos de un contenedor SList es mediante SListlterator. Escriba el cdi go
necesario para ilustrar el uso de SList.
Conjuntos y orden de almacenamiento
Los ejemplos del contenedor Set del Captulo 11 , Almacenamiento de objetos, proporcionan una buena introduccin a las
operaciones que pueden realizarse con los conjuntos bsicos. Sin embargo, di chos ejemplos utili zan, por comodidad, tipos
de Java predefinidos como Integer y String, que estaban disenados para poderlos utilizar dentro de contenedores. A la hora
de crear nuestros propios tipos, debemos tener en cuenta que un cont enedor Set necesita una forma de mantener el orden de
almacenamiento. El cmo se mantenga ese orden de almacenamiento vara de una implementacin de Set a otra. Por tanto,
las diferentes implementaciones de Set no slo tienen diferentes comportamientos, sino tambi n diferentes requisitos, adap-
tados al tipo de objeto que puede introducirse dentro de un contenedor Set concreto:
Set (interfaz) Cada elemento que se aada al conjunto debe ser diferente; en caso contrario, el objeto Ser no aadir
el elemento duplicado. Los elementos aadidos a un conjunto deben al menos definir equals( ) con el
fin de establecer la unicidad de los objetos. Set tiene exactamente la misma interfaz que Collection. La
interfaz Set no garant iza que vaya a mantener sus elementos en ningn orden detenninado.
HashSet * Para los conjuntos en los que el tiempo de bsqueda sea importante. Los elementos debe definir tam-
bin hashCode( ).
TreeSet Un conj unto ordenado respaldado por un rbol. De esta fomla, se puede extraer una secuencia ordena-
da de un conjunto. Los elementos tambin deben implementar la interfaz Comparable.
LinkedHashSet Tiene la velocidad de bsqueda de un contenedor HashSet. pero mantiene internamente el orden en que
se ailaden los elementos (el orden de insercin) utilizando Wla lista enlazada. Por tanto, cuando itera-
mos a travs del conjunto, los resultados aparecen en orden de insercin. Los elementos tambin deben
definir hashCode( ).
El asterisco en HashSet indica que, en ausencia de otras restricciones, sta debe ser la opcin preferida, porque est opti-
mizada para conseguir la mxima velocidad.
La definicin de hashCode() se describir ms adelante en el captulo. Es necesario crear un mtodo equals() para el alma-
cenamiento tanto de tipo hash como de tipo rbol, pero el mtodo hashCode( ) slo es necesario si se va a almacenar la
clase en un contenedor HashSet (lo cual es bastante probable, ya que esa debera ser nuestra primera eleccin como imple-
mentacin del conjunto) o LinkedHasbSet. Sin embargo, con el fin de mantener un buen estilo de programacin, convie-
ne sustituir siempre hashCode() cada vez que se sustituya equals( ).
534 Pi ensa en Java
Este ejemplo ilustra los mtodos que deben definirse para ut ilizar convenientemente un tipo de datos con una impl ementa-
cin concreta de Set:
/1: concainersfTypesForSets.java
1/ Mtodos necesarios para almacenar nuestro propio
JI tipo de datos en un conjunto .
tipos de datos.
import java.util.*;
class SetType {
int i i
public SetType (int ni { i = n; }
public boolean equals (Object o) {
return o instanceof SetType && (i == SetType)o) .i) i
}
public String toString() { return Integer.toString(il; }
class HashType extends SetType {
public HashType (int ni { super(nl; }
public int hashCode () { return i; }
class TreeType extends SetType
implements Comparable<TreeType>
public TreeType (int n) { super (n);
public int compareTo(TreeType arg}
return (arg . i < i ? -1 : (arg.i
public class TypesForSets {
i ? O 111 ;
static <T> Set<T> fill(Set<T> set, Class<T> typeJ
try {
for(int i = O; i < la; i++)
set.add(
type.getConstructor{int.class) .newI nstance{i));
catch(Exception el {
throw new RuntimeException{e) i
return set i
static <T> void test(Set<T> set, Class<T> type} {
fill (set, type) i
fill(set, typel; II I ntentamos aadir duplicados
fill(set, type}
System. out.println(set) i
public static void main (Str i ng [] a r gs) {
test(new HashSet <HashType>{), Ha shType.c l ass);
test(new LinkedHashSet<HashType>(), HashType . classl;
test(new TreeSet <TreeType>(), Tr eeType . classl i
II Cosas que no funcionan:
test(new HashSet<SetType>{l, SetType . classl;
test(new HashSet<TreeType>(), TreeType . class);
test(new LinkedHashSet<SetType>(), SetType.class) i
test(new LinkedHashSet<TreeType>(}, TreeType . c l assl;
try {
test(new TreeSet<SetType>(), SetType.class} j
} catch (Exception el {
System.out.println {e . getMessage ()) i
)
try (
test(new TreeSet<HashType>(), HashType.class ) i
catch (Exception e ) {
System.out.println (e.getMessage ()) i
1*
Output: (Sample )
[2, 4, 9, 8, 6, 1, 3, 7, 5, O)
(O, 1, 2, 3, 4, 5, 6, 7, 8, 9)
(9, 8, 7, 6, 5, 4, 3, 2, 1, O)
(9, 9, 7, 5, 1, 2, 6, 3,
,
7, 2, 4, 4, 7, 9, 1, 3,
4, 3,
,
5, O, 8, 8, 8, 6, 5, 1)
(O, 5, 5, 6, 5, O, 3, 1, 9, 8, 4, 2, 3, 9, 7, 3, 4,
7, 1, 9, 6, 2, 1, 8, 2, 8, 6, 7)
(O, 1, 2, 3, 4, 5, 6, 7, 8, 9, O, 1, 2, 3, 4, 5, 6,
9, O, 1, 2, 3, 4, 5, 6, 7, 8, 9)
17 Analisis detallado de los contenedores 535
6, 2,
4, O,
7, 8,
[O, 1, 2, 3, 4, S, 6, 7, 8, 9, O, 1, 2, 3, 4, 5, 6, 7, 8,
9, O, 1, 2, 3, 4, 5, 6, 7, 8, 91
java.lang.ClassCastException: SetType cannot be cast to java.lang.Comparable
java.lang.ClassCastException: HashType cannot be cast to java.lang.Comparable
*/11,-
Para demostrar qu mtodos son necesarios para un contenedor Set concreto y para evitar, al mismo tiempo, la duplicacin
de cdigo, hemos creado tres clases. La clase base. SctType, simplemente almacena un valor int y 10 imprime mediante
toString(). Puesto que todas las clases almacenadas en conjuntos deben tener un mtodo equals(), tambin incluimos dicho
mtodo en la clase base. La igualdad est basada en el valor de la variable int i .
HashType hereda de SetType y aade el mtodo hashCode() necesario para insertar un objeto en una implementacin has/
de un conjunto.
La interfaz Comparable, implementada mediante TreeTypc, es necesaria si vamos a usar un objeto en algn tipo de con-
tenedor ordenado, como por ejemplo SortedSet (del cual TreeSet es la nica implementacin). En compareTo(), observe
que no hemos usado la forma "simple y obvia"' return -2. Aunque se trata de un error de programacin comn, slo fun-
cionara adecuadamente si i e i2 fueran valores int "si n signo" (si Java tuviera una palabra clave "unsigned", que no es el
caso). La expresin no funciona para los valores int con signo de Java, que no son lo suficientemente grandes como para
representar la diferencia de dos valores int con signo. Si i es un entero positivo de gran tamao y j es un entero negativo de
gran tamao, i-j producir un desbordamiento y devolver un valor negativo, lo cual es incorrecto.
Nonnalmente, lo que queremos es que el mtodo compareTo() pennita obtener una ordenacin natural que sea coherente
con el mtodo equals(). Si equals() devuelve true para una comparacin concreta, entonces compareTo() deberia devol-
ver un resultado igual a cero para dicha comparacin, y si equals( ) devuelve false para una comparacin, entonces
compareTo() debera dar un resultado distinto de cero para dicha comparacin.
En TypesForScts, tanto fil1( ) como test( ) se definen utilizando genricos, con el fin de evi tar la duplicacin de cdigo.
Para verificar el comportamiento de un conjunto, test() invoca fill() sobre el conjunto de prueba set tres veces, tratando de
introducir objetos duplicados. El mtodo fil1() toma un contenedor Set de cualquier tipo y un objeto Class del mismo tipo.
Uti li za el objeto Class para descubrir el constructor que admite un argumento int, e in voca dicho constructor para aadir
elementos al conjunto.
Anali zando la salida, podemos ver que HashSet mantiene los elementos en alguna especie de orden misterioso (que enten-
deremos claramente ms adelante en el captulo), LinkedHashSet mantiene los elementos en el orden en que fueron
tados y TreeSet manti ene los elementos ordenados (debi do a la forma en que se implementa compareTo( ), dicho orden
resulta ser descendente).
Si tratamos de utili zar tipos de datos que no soporten apropiadamente las operaciones necesarias con conjuntos que
ren dichas operaciones, el funcionamiento es incorrecto. Al insertar un objeto SetType o TreeType, que no incluye un
do hashCode() redefinido, en una implementacin hash se generan valores duplicados, con lo que se viola la caracterstica
principal de un conjunto. Este error es bastante molesto, porque ni siquiera se produce un error en tiempo de ejecucin: sin
536 Piensa en Java
embargo, el mtodo hashCode() predetemnado es legit imo, y por tanto es un comportamiento legal, aun cuando sea inco-
rrecto. La nica fonn8 fiable de garanti zar la correccin de ese programa consiste en incorporar cdigo de pruebas en el sis-
tema final de produccin. (consulte el suplemento en http://MindView. net/Books/BetterJava para obtener ms infonnaci n).
Si tratamos de utili zar un tipo de datos que no implemente Comparable en un cont enedor TreeSet, se obtiene un resultado
ms definido: se genera una excepcin cuando el contenedor TreeSet trata de utili zar el objeto como si fuera de tipo
Comparable.
SortedSet
Los contenedores SortedSet garanti zan que sus elementos estn ordenados, lo que pennite proporcionar funcionalidad adi-
cional mediante los siguientes mtodos, definidos en la interfaz SortedSet:
Comparator comparator( ): genera el objeto Comparalor utili zado para este cont enedor Set, o null para el
caso de una ordenacin natura l.
Object first( ) : devuel ve el elemento ms bajo.
Object last() : devuel ve el elemento ms alto.
SortedSct subSet(fromElement, toElement) : genera UDa vista de este contenedor Set de los elementos com-
prendidos entre from_Element, incl uido, y toElement, excluido.
SortedSet headSet(toElement) : genera una vista de este contenedor Set con los elementos inferiores a
toElemen!.
SortedSet tailSet(fromElement): genera una vista de este contenedor Set con los elementos superiores o igua-
les a fromElemen!.
He aqu un ejemplo simple:
JI: containers/SortedSetDemo.java
II Lo que se puede hacer con un contenedor TreeSet.
import java.util .*;
import static net.mindview.util.Print.*;
public class SortedSetDemo {
public static void main{String[] args) {
SortedSet<String> sortedSet = new TreeSet<String> {) i
Collections.addAll{sortedSet,
"one two three four five six seven eight"
.split{" " ));
print(sortedSet) i
String low = sortedSet.first() i
String high = sortedSet.last{);
print (low) ;
print Ihigh) ;
Iterator<String> it = sortedSet.iterator{);
for(int i = O; i <= 6; i++) {
if(i 3) low = it.next {);
if (i 6) high = it .next () ;
else it.next();
print (low) ;
print Ihigh) ;
print (sortedSet.subSet (low, high));
print(sortedSet.headSet(high)) i
print(sortedSet . tailSet(low)) ;
1* Output:
[eight, five, four, one, seven, six, three, two]
eight
tWQ
one
two
[ane, seven, six, three]
[eight, five, four, ane, seven, six, three]
[one, seven, six, three, two]
* /// ,-
17 Anlisis detallado de los contenedores 537
Observe que SortedSet quiere decir "ordenado de acuerdo con la funcin de comparacin del objeto", no "arde.n de inser-
cin". El orden de insercin puede conservarse utili zando LinkedHashSet.
Ejercicio 9: (2) Utilice RandomGenerator.String para rellenar un contenedor TreeSet, pero empleando ordenacin
alfabtica. imprima el contenedor TreeSet para verificar la ordenacin.
Ejercicio 10: (7) Uti li zando un contenedor LinkcdList como implementacin subyacente, defina su propio contenedor
SortedSet.
Colas
Dejando aparte las aplicaciones de concurrencia, las dos unicas implementaciones de colas en Java SES son LinkedList y
PriorityQueue, que se diferencian por el comportamiento en lo que respecta a la ordenacin ms que por el rendimiento.
He aqu un ejemplo bsico donde se ilustran la mayora de las implementaciones de Queue (no todas ellas funcionan en este
ejemplo), incluyendo las colas basadas en concurrencia. Los elementos se insertan por un extremo y se extraen por el otro:
1/ : contai ners/QueueBehavior.java
11 Compara el comportamiento de algunas de las colas
import java .util.concurrent.*;
import java.util.*;
import net . mindview.util.*;
publi c class QueueBehavior {
priva te static int count = 10;
static <T> void test (Queue<T> queue, Generator<T> gen) {
for(int i = O; i < count; i++}
queue.offer{gen.next(;
while(queue.peek{) != null)
System. out.print(queue.remove() + " ");
System.out.println{) ;
static class Gen implements Generator<String>
String[] s = ("one two three four five six seven 11 +
"eight nine ten").split(1I 11);
int i;
public String next () { return s (i++J;
public static void main(String[] args) {
test(new LinkedList<String>() I new Gen(;
test(new PriorityQueue<String>(), new Gen(;
test (new ArrayBlockingQueue<String> (count) , new Gen(;
test(new ConcurrentLinkedQueue<String>(} I new Gen(;
test(new LinkedBlockingQueue<String>(), new Gen(;
test(new PriorityBlockingQueue<String>(), new Gen( i
1* Output:
one two three four five six seven eight nine ten
eight five four nine one seven six ten three two
one two three four five six seven eight nine ten
one two three four five six seven eight nine ten
one two three four five six seven eight nine ten
eight five four nine one seven six ten three two
*/ // ,-
538 Piensa en Java
Podemos ver que. con la excepcin de las colas con prioridad. un contenedor Queue devuelve los elementos exactamente
en el mismo orden en que fueron insertados en la cola.
Colas con prioridad
Ya hemos proporcionado una breve introduccin a las colas con prioridad en el Capnli o 11. Almacenamiento de objetos.
Un problema ms interesante que los que all analizamos sera el de una li sta de tareas que hacer, en la que cada objeto con-
tenga una cadena de caracteres y sendos va lores de prioridad principal y secundaria. La ordenacin de esta li sta est, de
nuevo, control ada por la implementacin de Comparable:
JI: containersjToDoList.java
JI Un uso ms complejo de PriorityQueue.
import java.util. *;
class ToDoList extends PriorityQueue<ToDoList.ToDoItem> {
static class ToDoItem implements Comparable<ToDoItem> {
private char primarYi
private int secondary
private String item
public ToDoItem(String td, char pri, int sec) {
primary = pri;
secondary = sec
item = td;
public int compareTo(ToDoItem arg) {
if(primary > arg.primary)
return +1;
if{primary == arg.primary)
if(secondary > arg.secondary)
return +1;
else if(secondary == arg.secondary)
return O;
return -1;
public String toString()
return Character.toString(primary) +
secondary + " : u + i tem;
public void add (String td, char pri, int sec)
super.add(new ToDoItem(td, pri, sec));
public static void main(String[) args)
ToDoList toDoList = new ToOoList();
toDoList.add(UEmpty trash
ll
, 'C', 4);
toDoList.add(uFeed dog
ll
, 'A', 2);
toDoList.add(UFeed bird", 'B', 7);
toDoList.add(UMow lawn
u
, 'C', 3);
toDoList.add(IIWater lawn
ll
, 'A' I 1);
toDoList .add{UFeed cat", 'B', 1);
while(!toDoList.isEmpty() )
System.out.print1n(toDoList.rernove()) ;
/ *
Output:
Al, Water lawn
A2, Feed dog
BL Feed cat
B7, Feed bird
C3, Mow lawn
C4: Empty trash
* /// ,-
17 Anlisis detallado de los contenedores 539
Podemos ver cmo la ordenacin de los elementos se realiza automticamente gracias a la cola con prioridad.
Ejercicio 11 : (2) Cree ulla clase que contenga un objeto Integer que se inicialice con un valor comprendido entre O y
100 utilizando java.utiJ.Random. Implemente Comparable empleando este campo Integer. Rellene una
cola de tipo PriorityQueuc con objetos de dicha clase y extraiga los valores usando poll( ) para demos-
trar que se obtiene el orden deseado.
Colas dobles
Una cola doble es similar a una cola nannal, pero se pueden aadir y eliminar elementos de cualquier extremo. Existen
mtodos en LinkedList que soportan las operaciones de doble cola, pero no existe ninguna interfaz explcita para una doble
cola en las bibliotecas estndar de Java. Por tanto, LinkedList no puede implementar esta interfaz y no resulta posible efec-
mar una generalizacin a una interfaz Deque (cola doble), a diferencia de lo que podramos hacer con Queue en el ejerci-
cio anterior. Sin embargo, podemos crear una clase Deque empleando el mecanismo de composicin y exponer,
simplemente, los mtodos relevantes de LinkedList:
11: net/mindview/util/Deque.java
II Creaci6n de una cola doble a partir de LinkedList.
package net.mindview.util
import java . util.*
public class Deque<T>
private LinkedList<T> deque = new LinkedList<T>();
public void addFirst{T e) ( deque.addFirst{e)
public void addLast(T el { deque.addLast(e); }
public T getFirst () { return deque. getFirst ()
public T getLast () ( return deque. getLast () }
public T removeFirst () ( return deque. removeFirst () ;
public T removeLast () { return deque. removeLast () }
public int size () { return deque. size () }
public String toString () { return deque. toString () ;
II y otros mtodos segn sean necesarios ...
///,-
Si utilizamos esta clase Deque en nuestros propios programas, es posibl e que descubramos que necesitamos aadir otros
mtodos para que la clase resulte prctica.
He aqui un ejemplo simple de prueba de la clase Deque:
11: containers/DequeTest.java
import net.mindview.util.*
import static net . mindview.util.Print.*;
public class DequeTest {
static void fillTest{Deque<Integer> deque) {
for(int i = 20; i < 27; i++}
deque.addFirst(i) ;
for(int i = 50; i < 55; i++}
deque.addLast{i) ;
public static void main(String [] args) {
Deque<Integer> di = new Deque<Integer>();
fillTest (di);
print(di) ;
while(di.size() != O)
printnb(di.removeFirst(} + 11 11)
print ();
fillTest (di);
while (di . size () ! = O)
540 Piensa en Java
printnb(di.removeLast {) + 11 ");
1* Output:
[26, 25, 24, 23, 22, 21, 20, 50, 51, 52, 53, 54]
26 25 24 23 22 21 20 50 51 52 53 54
54 53 52 51 50 20 21 22 23 24 25 26
* /// ,-
Resulta poco probable que tengamos que insertar y extraer element os por ambos extremos, por lo que Deque no se emplea
tan comnmente como Queue.
Mapas
Como vimos en el Captulo 11 , Almacenamiento de objetos, la idea bsica de un mapa (tambin denominada matriz asocia-
tiva) es la de almacenar asociaciones clave-valor (pares), de modo que se pueda buscar un valor utilizando una clave.
La bibli oteca estndar de Java contiene diferentes implementaciones bsicas de mapas: HashMap, TrecMap,
LinkedHashMap, WeakHashMap, ConcurrentHashMap y IdentityHashMap. Todas tienen la mi sma interfaz bsica
Map, pero difieren en cuanto a su comportamiento, incluyendo la eficiencia, el orden en el que se almacenan y se presen-
tan los pares, el tiempo que el mapa conserva los obj etos, el funcionamiento de los mapas en los programas multihebra y el
modo de determinar la igualdad de claves. La gran cantidad de implementaciones de la interfaz Map nos indica la impor-
tancia de este tipo de contenedor.
Para comprender mejor los mapas resulta til ver cmo se construye una matri z asociativa. He aqu una implementacin
extremadamente simpl e:
JJ : containersJ AssociativeArray.java
11 Asocia claves con valores.
import static net.mindview.util.Print.*
public class AssociativeArray<K,V>
private Object [] [] pairs;
private int indexi
public AssociativeArray(int length}
pairs = new Object [length] [2] i
public void put (K key, V value ) {
if(index >= pairs.length)
throw new ArraylndexOutOfBoundsException( ) ;
pairs (index++l = new Object [] { key, value };
@SuppressWarnings ( "unchecked 11 )
public V get (K key) (
for ( int i = O; i < index; i++ )
if (key. equals (pairs [i] [ O] ) )
return (V) pairs[i] [1] i
return null; 11 Clave no encontrada
public String toString( )
StringBuilder result = new StringBuilder( ) ;
for(int i = O; i < index; i++ ) {
result. append (pairs [i] [O) . toString ( ) )
result. append{" : 11 );
result.append (pairs[i] [11.toString ()) ;
if (i < index - 1 )
result.append {"\n") ;
return result.toString {) ;
public static void main (String ( ] args ) {
AssociativeArray<String,String> map =
new AssociativeArray<String,String> (6);
map.put ( "sky", "blue" ) i
map.put ( "grass", "green" l i
map.put {"ocean", "dancing" ) ;
map.put ( lItree", "tall" ) ;
map.put ( "earth", IIbrown" ) ;
map .put ( "sun", "warm" ) ;
try {
map.put ( "extra", "object" l i JI Past the end
catch (ArraylndexOut OfBoundsException e l
print ( "Too many objects!" ) ;
print (map ) ;
print (map.get ( "ocean" )) ;
/* Output:
Too many objects!
sky : blue
grass : green
ocean : dancing
tree : tall
earth : brown
sun : warm
dancing
* /// ,-
17 Anlisis detallado de los contenedores 541
Los mtodos esenciales en una matriz asociativa son put( ) y get( ), pero para facilita r la visualizacin se ha susti-
tuido toString( ) con el fm de imprimir los pares clave-valor. Para demostrar que funciona, main() carga una matri z
AssociativeArray con pares de cadenas de caracteres e imprime el mapa resultante, extrayendo a cont inuacin con get( )
uno de los valores.
Para utilizar el mtodo get(), se pasa la clave (key) que queremos buscar y el mtodo devuel ve como resultado el valor aso-
ciado, o bien devuelve null si no se puede encontrar esa clave. El mtodo get( ) utili za la que posiblemente sea la tcnica
menos eficiente imaginable con el fin de localizar el valor: comienza por la parte superior de la matri z y usa equals() para
comparar las claves. Pero el objetivo del ejemplo es la simplicidad no la eficiencia.
Por tanto, la versin anterior es instmctiva, pero no resulta muy eficiente y tiene un tamao fijo, lo que da como resultado
una implementacin poco flexible. Afommadamente, los mapas de java.util no tienen estos problemas y pueden emplear-
se perfectamente en el ejemplo anterior.
Ejercicio 12: (I)Ut ilice mapas de tipo HashMap, TreeMap y LinkedHashMap en el mtodo main( ) de
AssociativeArray.java.
Ejercicio 13: (4) Utilice AssociativeArray.java para crear un contador de apariciones de palabras, que establezca la
correspondencia entre un valor de tipo String y un va lor de tipo Integer. Empleando la utilidad net.mind-
view.util.TextFile de este libro, abra un archivo de texto y extraiga las palabras de dicho archivo utilizan-
do los espacios en blanco y los signos de puntuacin como delimitadores. Cuente el nmero de veces que
cada palabra aparece en dicho archivo.
Rendimiento
El rendimiento es una de las cuestiones fundamentales en los mapas, y resulta demasiado lento utilizar una bsqueda li-
neal en get() a la hora de intentar local izar una clave. Es en este aspecto donde HashMap pennite acelerar las operaciones.
En lugar de real izar una lenta bsqueda de la clave, este contenedor utiliza un valor especial denominado cdigo hash. El
cdigo hash es una fonna de tomar una cierta informacin contenida en el objeto en cuestin y transfonnarla en un valor
int "relativamente unvoco" que se utilizar para representar dicho objeto. hashCode( ) es un mtodo de la clase raz
542 Piensa en Java
Object, por lo que todos los objetos Java pueden generar un cdigo has/. Un contenedor HashMap toma el cdigo hash
devuelto por hashCodc() y lo utiliza para localizar rpidamente la clave. Esto pemlite mejorar enonncmente el rendi-
miento.
6
He aqu las implementaciones bsicas de Map. El asterisco sinlado junto a HashMap indica que, en ausencia de otras res-
tri cciones, sta debera ser la opcin preferida, ya que est optimizada para maximizar la velocidad. Las otras implementa-
ciones enfatizan otras caractersticas. por lo que no resultan tan rpidas como HashMap.
HashMap* Implementacin basada en una tabla !IOS/ (utilice esta clase en lugar de Hashtable).
Proporciona un rendimiento de tiempo constante para la insercin y localizacin de
pares. El rendimiento puede ajustarse mediante constructores que penniten fijar la
capacidad y el/actor de cwga de la tabla has/.
LinkedHashMap Como l.JashMap. pero cuando se realiza una iteracin a su travs, se extraen los pares
en orden de insercin o en orden LRU (Ieast-recenrl)'-used, menos recientemente utili-
zado). Es ligeramente ms lento que HashMap, salvo cuando se est realizando una ite-
racin, en cuyo caso es ms rpido debido a que se emplea una lista enlazada para man-
tener la ordenacin interna.
TreeMap Implementacin basada en un rbol rojo-negro. Cuando se exami nen las claves o las
parejas, estarn ordenadas (la ordenacin est dctenninada por Comparable o
Comparalor). La ventaja de un contenedor Treel\1ap es que los resultados se obt ienen
ordenados. TrecMap es el imico tipo de mapa con el mtodo subMap(), que permite
devolver una parte del rbol.
\VeakHashMap Un mapa de claves dbiles que pennite eliminar los objetos a los que hace referencia el
mapa; est diseado para resolver ciertos tipos especiales de problemas. Si no se con-
serva ninguna referencia a una clave concreta fuera del mapa, dicha clave puede ser
depurada de la memoria.
ConcurrentHashMap Un mapa preparado para hebras de programacin que no lncluye bloqueo de sincroni-
zacin. Hablaremos de este tema en el Captulo 21, Concurrencia.
IdentityHashMap Un mapa hash que utiliza - en lugar de equals( ) para comparar las claves. Se utiliza
para resolver ciertos tipos especiales de problemas, no para uso general.
El almacenamiento hash es la forma que ms comnmente se utiliza para almacenar elementos en un mapa. Posterionnente
veremos cmo funciona este tipo de almacenamiento.
Los requisitos para las claves utilizadas en un mapa son iguales que para los elementos de un conjunto. Ya hemos visto una
ilustracin de estos requisitos e11 'JypesForSets.java. Todas las claves deben disponer de un mtodo equals( j . Si la clave
se utiliza en un mapa hash, tambin debe disponer de un mtodo hashCode( j apropiado. Si la clave se utiliza en un mapa
de tipo TreeMap, deber implementar Comparable.
El siguiente ejemplo muestra las operaciones disponibles en la interfaz Map, utilizando el conjunto de datos de prueba
CountingMapData anterionnente defmido:
jj : containersjMaps.java
JI Cosas que se pueden hacer con mapas.
import java.util.concurrent.*;
import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*;
public class Maps
6 Si este tipo de ut ilizacin sigue sin satisracer sus requisitos de rendimiento, puede acelerar todava ms las bsquedas en tablas escribiendo su propio
contenedor Map y personalizndolo para los tipos particulares de datos que est utilizando, con el fin de evitar tos retardos asociados con las proyeccio-
nes de tipo hacia y desde Object. Para obtener niveles todavia mejores de rendimiento, los entusiastas de la velocidad pueden consultar el libro de Donald
Knuth, Tite Art o/Computer Programming, Va/lime 3:Sorring and Sean:hing. Segunda edicin, con el fin de sustittlir las listas de segmentos con desbor-
damiento por matrices que tienen dos ventajas adicionales: pueden optimizarse de acuerdo con las caractersticas de almacenamiento del disco y pcmten
ahorrar la mayor pane delliempo invenido en crear y depurar los registros individuales.
17 Anlisis detallado de los contenedores 543
public static void printKeys(Map<Integer,String> map)
printnb("Size = " + map.size() + ." ");
printnb ("Keys: 11) i
print{map.keySet()); // Generar un conjunto con las claves
public static void test(Map<Integer,String> map) {
print(map.getClass() .getSimpleName()};
map.putAll(new CountingMapData(25)} i
1/ El mapa presenta el comportamiento de un conjunto para las claves:
map.putAll(new CountingMapData(2S));
printKeys (map) ;
/1 Generacin de una coleccin con los valores:
printnb ("Values: ");
print(map.values()) i
print (map) ;
print("map . containsKey(ll): 11 + map.containsKey(ll);
print{"map.get(ll): "+ map.get(ll))
print("map.containsValue(\"FO\II): 11
+ map . containsValue (" FO") ) ;
Integer key = map. keySet () . i terator () . next () ;
print (ti First key in map: ti + key);
map. remove (key) ;
printKeys (map) ;
map. clear () ;
print (IImap . isEmpty() : " + map.isEmpty());
map.putAll(new CountingMapData(25));
11 Las operaciones efectuadas sobre el conjunto modifican el mapa:
map.keySet() .removeAll (map.keySet () );
print ("map . isEmpty () : " + map. isEmpty () ) ;
public static void main(String[] args)
test (new HashMap<Integer,String>());
test(new TreeMap<Integer,String>());
test (new LinkedHashMap<Integer,String>());
test(new IdentityHashMap<Integer,String>());
test(new ConcurrentHashMap<Integer,String>()) ;
test(new WeakHashMap<Integer,String> ());
1* Output:
HashMap
Size = 25, Keys: [15, B, 23, 16, 7, 22, 9, 21, 6, 1, 14,
24, 4, 19, 11, 18, 3, 12, 17, 2, 13, 20, 10, 5, O]
Values: [PO, ID, XO, QO, HO, WO, JO, VO, GO, 80, 00, YO,
EO, TO, LO, SO, DO, MO, RO, CO, NO, UO, KO, FO, AO]
{ls=PO, 8=rO, 23=XO, 16=QO, 7=HO, 22=WO, 9=JO, 21=VO, 6=GO,
1=80, 14=00, 24=YO, 4=EO, 19=TO, 11=LO, 1B=SO, 3=00, 12=MO,
17=RO, 2=CO, 13=NO, 20=UO, 10=KO, s=FO, O=AO}
map. containsKey (11) : true
map.get(l1) , LO
map. containsValue ( " FO"): true
First key in map: 15
Size = 24, Keys: {B, 23, 16, 7, 22, 9, 21, 6, 1, 14, 24, 4,
19, 11, lB, 3, 12, 17, 2, 13, 20, 10, 5, O]
map.isEmpty(): true
map.isEmpty(): true
El mtodo printKeys( ) muestra cmo generar lIna vista de tipo Collection para un mapa. El mtodo keySet( ) genera un
conjunto con las claves del mapa. Debido a las mejoras en el soporte de impresin introducidas en Java SES, podemos impri-
544 Piensa en Java
mir los resultados del mtodo va lues(), que genera una coleccin con todos los valores del mapa (observe que las claves
debe ser unvocas, pero que los valores pueden contener duplicados). Puesto que estas colecciones estn respaldadas por el
propio mapa, cualquier cambio efectuado en una coleccin se reflejar en el mapa asociado.
El resto del programa proporciona ejemplos simples de cada operacin efectuada con el mapa y pnleba cada tipo bsico de
mapa.
Ejercicio 14: (3) Demuestre que java.util.Properti es funciona en el programa anterior.
SortedMap
Si uti lizamos un contenedor SortedMap (del cual la nica implementacin disponible es TreeMap), se garantiza que las
claves estarn ordenadas, lo que pennite proporcionar funciona li dad adicional con los siguientes mtodos de la interfaz
SortedMap:
Comparator comparator( ): genera el comparador utilizado para este mapa o null si se util iza la ordenacin
natural.
T IirstKey(): devuelve la clave ms baja.
T lastKey( ): devuelve la clave ms alt a.
SortedMap subMap(fromKey, toKey): genera una vista de este mapa, con las claves comprendidas entre
fromKey, incluido, y toKey, excluido
SortedMap headMap(toKey): genera una vista de este mapa con las claves que sean inferiores a toKey.
SortedMap taiIMap(fromKey): genera una vista de este mapa COI1 las claves que sean iguales o superiores a
fromKey.
He aqu un ejemplo simi lar a Sort edSetDemo.j ava donde se ilustra este comportamiento adiciona l de los mapas TreeMap:
jI: containersjSortedMapDemo.java
/1 Lo que se puede hacer con TreeMap.
import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*;
public class SortedMapDemo {
public static void main (String [J args) {
TreeMap<Integer,String> sortedMap
new TreeMap<Integer,String> (new CountingMapData(lO}};
print(sortedMap} i
Integer low = sortedMap.firstKey()
Integer high = sortedMap.lastKey() i
print(low) ;
print Ihigh) ;
Iterator< Integer> it = sortedMap keySet(} .iterator{)
for (int i = O i <'= 6; i++} {
if{i == 3} low = it.next ();
ifli == 6) high = it.next();
else it.next(};
print {low} ;
print (hi gh) ;
print (sort edMap. subMap(low, high);
print (sortedMap.headMap{high)} ;
print (sortedMap.tailMap {low)} ;
} 1* Output,
{O=AO, 1=80, 2=CO. 3=DO, 4=EO, 5=FO, 6=GO, 7=HO, 8=IO, 9=JO}

9
3
7
{3=00, 4""EO, 5=FO,
{O=AO, 1=80, 2=CO,
{3=00, 4=EO, 5=FO,
"///,-
6=GO}
3=00, 4=EO,
6=GO, 7=HO,
5=FO, 6=GO}
8=IO, 9=JO}
17 Anlisis detallado de los contenedores 545
Aqu, los pares se almacenan ordenados segn la clave. Puesto que existe el concepto de orden en TreeMap, el concepto de
"posicin" tambin tiene sentido, as que se pueden tener subrnapas y tambin se puede detenninar el primer elemento yel
ltimo.
LinkedHashMap
LinkedHashMap utiliza un almacenamiento hash para conseguir velocidad, pero tambin genera las parejas en orden de
insercin cuando se recorre el mapa (System,out.pri ntln( ) itera a travs del mapa, por lo que se pueden comprobar los
resultados de ese recorrido). Adems, LinkedHashMap puede configurarse mediante el constructor para utilizar un algo-
ritmo LRU (leasl-recenlly-used) basado en el acceso a los elementos, de modo que los elementos a los que se haya accedi-
do menos (y sean, por tanto, candidatos a la eliminacin) aparezcan al principio de la li sta. Esto pennite crear fcilmente
programas que realizan una limpieza peridica con el fin de ahorrar espacio. He aqu un ejemplo donde se ilustran ambas
caractersticas:
j/ : containersjLinkedHashMapDemo.java
// Lo que se puede hacer con LinkedHashMap.
import java.util.*j
import net.mindview.util.*;
import static net.mindview.util.Print.*;
public class LinkedHashMapDemo {
public static void main(String[) args)
LinkedHashMap<Integer,String> linkedMap
new LinkedHashMap< Integer,String>(
new CountingMapData(9));
print(linkedMap) ;
// Orden LRU,
linkedMap =
new LinkedHashMap<Integer, String> (16, O. 7Sf, true);
linkedMap.putAll(new CountingMapData(9));
print(linkedMap) ;
for(int i = O; i < 6; i++) // Provocar accesos:
linkedMap.get(i) ;
print(linkedMap) ;
linkedMap.get(OI;
print(linkedMap) j
}
/ "
Output:
{O=AO, 1=80, 2=CO, 3=DO,
{O=AO, l=BO, 2=CO, 3=00,
{6=GO, 7=HO, 8=IO, O=AO,
{6=GO, 7=HO, 8=IO, l=BO,
" /1/ ,-
4=EO, 5=FO, 6=GO, 7=HO, 8=IO}
4=EO, 5=FO, 6=GO, 7=HO, 8=IO}
1=80, 2=CO, 3=DO, 4=EO, 5=FO}
2=CO, 3=00, 4=EO, S=FO, O=AO}
Podemos ver, analizando la salida, que las parejas se recorren por orden de insercin, incluso para la versin LRU. Sin
embargo, despus de acceder exclusivamente a los primeros seis elementos en la versin LRU, los tres ltimos elementos
se desplazan al principio de la lista. Despus, cuando se vuelve acceder a "O", dicho elemento se desplaza al fInal de la li sta.
Almacenamiento y cdigos hash
Los ejemplos del Capnllo 11, Almacenamiento de objetos, utilizan clases predefinidas como claves para HashMap. Dichos
ejemplos funcionaban porque esas clases predefinidas contenan todos los elementos necesarios para poder comportarse
correctamente como claves.
546 Piensa en Java
Uno de los problemas ms comunes es el que se produce cuando creamos nuestras propias clases para utilizarlas como cla-
ves para mapas de tipo HashMap, y nos olvidamos de aadir los elementos necesarios. Por ejemplo, considere un sistema
de prediccin meteorolgica basado en el estudio del comportamiento de las marmotas donde se hagan corresponder obje-
tos Groundhog (mannota) con objetos Prediction (prediccin meteorolgica). La tarea parece sencilla: basta con crear las
dos clases y lIsar Groundhog como clave y Prediction como valor:
11: containers/Groundhog.java
II Parece plausible, pero no funciona como clave para HashMap.
public class Groundhog {
protected int number
public Groundhog(int n) {number n;}
public String toString () {
return "Groundhog #" + number;
11 : containers/Prediction.java
1I Prediccin del clima mediante marmotas .
import java.util . *;
public class Prediction
private static Random rand = new Random(47) ;
priva te boolean shadow = rand nextDouble () > 0.5 ;
public String toString () {
if {shadow}
return "Six more weeks of Winter!"
el se
return "Early Spring!" i
}
/// >
11: containers/SpringDetector.java
II Qu tiempo har?
import java.lang.reflect.*;
import java.util.*;
import static net . mindview.util.Print.*;
public class SpringDetector {
11 Utiliza Groundhog o una clase derivada de Groundhog:
public static <T extends Groundhog>
void detectSpring{Class<T> type) throws Exception {
Constructor<T> ghog = type.getConstructor(int.class)
Map<Groundhog,Prediction> map =
new HashMap<Groundhog,Prediction>();
for(int i = O; i < 10; i++ )
map.put {ghog.newInstance (i) , new Prediction()};
print ("map = !I + map);
Groundhog gh = ghog.newInstance{3);
print ( " Looking up prediction for !I + gh)
if(map.containsKey{gh
print(map.get(gh)) ;
else
print (" Key not found: " + gh) i
public static void main(String[] args) throws Exception {
detectSpring(Groundhog.class) ;
1* Output:
17 Anlisis detallado de los contenedores 547
map = {Groundhog #3=Early Spring!, Groundhog #7=Early Spring!,
Groundhog #5=Early Spring!, Groundhog #9=Six more weeks of Winter!,
Groundhog #8=Six more weeks of Winter!, Groundhog #O=Six more weeks
of Winter!, Groundhog #6=Early Spring!, Groundhog #4=Six more weeks
of Winter!, Groundhog #l=Six more weeks of Winter!, Groundhog #2=Early Spring!}
Looking up prediction for Groundhog #3
Key not found: Groundhog #3
* ///>
A cada objeto Groundhog se le da un nmero identificador, de modo que se puede buscar un objeto Prediction en el con-
tenedor HashMap diciendo: "Dame el objeto Prediction asociado con el objeto asociado Groundhog #3". La clase
Prediction contiene un valor de tipo boolean que se inicializa utilizando java.util.random() y un mtodo toString( ) que
interpreta el resultado por nosotros. El mtodo detectSprillg() (detectar la primavera) se crea empleando el mecani smo de
reflexin para instancias y usa la clase Groundhog o cualqui er clase derivada de Groundhog. Esto nos ser til posterior-
mente. cuando heredemos una nueva clase a partir de Groundbog para resolver el problema ilustrado en este ejemplo.
Rellenamos un objeto HashMap como objetos Groundhog y sus objetos Prediction asociados. El mapa HashMap se
imprime para poder ver que ha sido rellenado. Despus, se utiliza un objeto Groundhog con nmero identificador igual a
3 como clave para buscar la prediccin para Groundhog #3 (que, corno vemos, debe encontrarse en el mapa).
El ejemplo parece lo suficientemente simple, pero no funciona; ya que no se puede encontrar la clave correspondiente a #3.
El problema es que Groundhog hereda automticamente de la dase raz comn Object, y se est utilizando el mtodo
hasheode() de Object para generar el cdigo hash correspondiente a cada objeto. De manera predeterminada, dicho mto-
do se limita a utilizar la direccin de su objeto. Por tanto, la primera instancia de Groundhog(3) no produce un cdigo has/
igua l al cdigo has11 de la segunda instancia de Groundhog(3) que hemos tratado de utilizar como clave de bsqueda.
Podramos pensar que lo nico que hace falta es escribir un mtodo de sustitucin apropiado para hashCode( ). Sin embar-
go, esta solucin seguir sin funcionar hasta que hagamos una cosa ms: sustituir el mtodo equals( ) que tambin fonna
parte de Object. El mtodo equals() es utilizado por HashMap a la hora de detenninar si la clave es igual a cualquiera de
la claves contenidas en la tabla.
Un mtodo equals() apropiado deber sat isfacer las siguientes cinco condiciones:
1. Reflexiva: para cualquier x, x.equals(x) debe devolver true.
2. Simtrica: para cualesquiera x e y, x.equals(y) debe devolver troe si y slo si y.equals(x) devuelve true.
3. Transitiva: para cualesquiera x, y y z, si x.equals(y) devuelve true e y.equals(z) devuelve true, entonces
x.equals(z) devolver true.
4. Coherencia: para cualesquiera x e y, mltiples invocaciones de x.cquals(y) debern devolver continuamente true
o continuamente false, en tanto que no se modifique ninguna infornlacin utili zada en las comparaciones de
igualdad de los objetos.
5. Para cualquier x distinta de null, x.equals(null) debe devolver false.
De nuevo, el mtodo predetenninado Object.equals() simplemente compara las direcciones de los objetos. por lo que una
instancia Groundhog(3) no es igual a la otra instancia Groundhog(3). Por tanto, para poder usar nuestras propias clases
como claves en un contenedor HasbMap, debemos sustituir tanto hashCode( ) como equals( ), como se muestra en la
siguiente solucin al problema de las mannotas:
//: containers/Groundhog2 .java
JI Una clase utilizada como clave en un contenedor HashMap
/ / debe sustituir hashCode () y equals () .
public class Groundhog2 extends Groundhog {
public Groundhog2 (int n) { super{n) i }
public int hashCode () { return nurnber;
public boolean equals (Object o) {
return o instanceof Groundhog2 &&
(number == ((Groundhog2)o) .number) i
548 Piensa en Java
JI: containersjSpringDetector2.java
1/ Una clave adecuada.
public class SpringDetector2
public static void main(String[] args) throws Exception
SpringDetector.detectSpring(Groundhog2 . class) ;
/ * Output:
map = {Groundhog #2=Early Spring!, Groundhog #4=Six more weeks of Winter!, Groundhog
#9=Six more weeks of Winter!, Groundhog #8=Six more weeks of Winter!, Groundhog #6=Early
Spring!, Groundhog #l=Six more weeks of Winter ! , Groundhog #3=Early Spring!, Groundhog
#7=Early Spring!, Groundhog #5=Early Spring!, Groundhog #O=Six more weeks of Winter!}
Looking up prediction for Groundhog #3
Early Spring!
* /// ,-
Groundhog2.hashCode( ) devuel ve el nmero de marmota como valor de hash. En este ejemplo, el programador es res-
ponsable de garantizar que no existan dos mannotas con el mismo nmero identificador. El mtodo hashCode( ) no tiene
por qu devolver un identificador un voco (esto es algo que expli caremos con ms detalle ms adelante en el captulo), pero
el mtodo equals() debe detenninar de manera estricta si dos objetos son equivalentes. Aqu, equals( ) se basa en el nme-
ro de mannota, por lo que existen como claves dos objetos Groundhog2 en el mapa HashMap que tengan el mi smo n-
mero de marmota, el mtodo fallar.
Aunque parece que el mtodo equals() se limita a comprobar si el argumento es una instancia de Groundhog2 (usando la
palabra clave instanceof, de la que hemos hablado en el Captulo 14, Informacin de lipos), instanceof reali za, en realidad,
una segunda comprobacin automtica para ver si el objeto es null, ya que instanceof devuelve false si el argumento de la
izquierda es nuJI. Suponiendo que el objeto sea del tipo correcto y distinto de nuU, la comparacin se basa en los valores
number de cada objeto. Puede ver, analizando la salida, que ahora el comportamiento es correcto.
A la hora de crear su propia clase para emplearla en un contenedor HashSet, deber prestar atencin a los mismos proble-
mas que cuando se utiliza como clave en un mapa de tipo HashMap.
Funcionamiento de hashCode( )
El ejemplo anterior es slo un primer paso en la resolucin correcta del problema. Demuestra que si no susti nlimos
hashCode() y equals() para nuestra clave, la estructura de datos hash (HasIISe!. HashMap, LinkedHashSet o
LinkedHashMap) probablemente no podr gestionar nuestra clave apropiadamente. Sin embargo. para obtener una buena
sol ucin del problema, necesitamos comprender qu es lo que sucede dentro de la estructura de datos hash.
En primer lugar, consideremos cul es la motivacin para utilizar almacenamiento hash: lo que queremos es buscar un obje-
to empleando otro objeto. Pero tambin podramos hacer esto con un cont enedor TreeMap, o incluso podramos implemen-
tar nuestro propio conrenedor Map. Por contraste con una implementacin hash, el siguiente ejemplo implementa un mapa
usando una pareja de contenedores ArrayList. A diferencia de AssociativeArray.java, esto incluye una impl ementacin
completa de la interfaz Map, para poder di sponer del mtodo entrySet( ):
11: containers/SlowMap.java
II Un mapa implementado con ArrayList.
import java.util.*
import net.mindview.util.*
public class SlowMap<K,V> extends AbstractMap<K,V>
private List<K> keys = new ArrayList<K>()
prvate List<V> values = new ArrayList<V>()
public V put(K key, V value) {
V oldValue = get(key) II El valor anterior o null
if ( ! keys. contains (key)) {
keys. add Ikey) ;
values.add(value) i
else
17 Anlisis detallado de los contenedores 549
values.set (keys.indexOf (key) , value ) ;
return oldValue
public V get (Object key) { /1 La clave es de tipo Object, no K
if ( !keys.contains {key
return null;
return values . get (keys. indexOf (key) ) ;
public Set<Map. Entry<K, V entrySet () {
Set<Map.Entry<K,V set= new HashSet<Map.Entry<K,V (} ;
Iterator<K> ki = keys.iterator () ;
Iterator<V> vi = values.iterator () ;
while lki.hasNext l))
set.add (new MapEntry<K,V> (ki.next () , vi.next () ;
return set;
publi c static void main (String[] args ) {
SlowMap<String,Stri ng> m= new SlowMap<String,String> () ;
m.putAll (Countries . capitals ( lS ;
System.out.println (m) ;
System.out.println (m. get( "BULGARIA" ) ;
System.out.println (m. entrySet ())
1* Output:
{CAMEROON=Yaounde, CHAD=N'djamena, CONGO=Brazzaville, CAPE
VERDE=Praia, ALGERIA=Algiers, COMOROS =Moroni, CENTRAL AFRICAN
REPUBLIC=Bangui, BOTSWANA=Gaberone, BURUNDI =Bujumbura,
BENIN=Porto-Novo, BULGARI A=Sofia, EGYPT=Cairo,
ANGOLA=Luanda, BURKI NA FASO=Ouagadougou, DJIBOUTI=Dijibouti}
Sofia
[CAMEROON=Yaounde, CHAD=N'djamena, CONGO=Bra z zaville, CAPE
VERDE=Praia, ALGERIA=Algiers, COMOROS=Moroni, CENTRAL
AFRICAN REPUBLIC=Bangui, BOTSWANA=Gaberone, BURUNDI=Bujumbura,
BENIN=Porto-Novo, BULGARIA=Sofia, EGYPT=Cairo, ANGOLA=Luanda,
BURKINA FASO=Ouagadougou, DJIBOUTI=Dijibouti]
* /// , -
El mtodo put() simplemente coloca las claves y va lores en sendos contenedores ArrayList relacionados. De acuerdo con
la interfaz Map, ti ene que devol ver la clave anteri or o nuJl si no haba cl ave anterior.
De acuerdo tambi n con las especificaciones de Map, get() devuelve null si la clave no se encuentra en el mapa SlowMap.
Si la cl ave existe, se utiliza para buscar el ndice numrico que indica su posicin dent ro de la li sta de claves keys, y este
nmero se emplea como ndice para generar el va lor asoci ado a partir de la li sta values. Observe que el tipo de key es Object
en get(), en lugar de ser del tipo parametri zado K como cabra esperar (y que se utili zaba en AssociativeArray.java). Esto
es a consecuenci a de la introduccin de los genri cos dentro del lenguaje Java en una etapa tan tarda: si los genricos hubi e-
ran sido una de las caracter sti cas ori ginal es del lenguaje, get() podra haber especifi cado el tipo de su parmetro.
El mtodo Map.entrySet() debe generar un conjunto de obj etos Map.Entry. Sin embargo, Map.Entry es una interfaz que
describe una estructura dependiente de la impl ementacin, por lo que si queremos hacer nuestro propi o tipo de mapa, debe-
remos tambin definir una implementacin de Map.Entry:
11 : containers/ MapEnt r y.java
II Una definicin simple de Map.Entry para implementaciones
II de ejemplo de mapas.
import java.util .* ;
public class MapEntry<K,V> implements Map.Entry<K,V> {
private K key;
private V value
public MapEntry (K key, V value ) {
550 Piensa en Java
this. key = key i
this. value = value;
public K getKey{) ( return key; )
public V getValue () { return value;
public V setValue (V v) {
v resule = value;
value = Vi
return resul ti
public int hashCode()
return (key==null ? O : key. hashCode ()) ...
(value==null ? o : value.hashCode{);
public boolean equals (Object o) {
if(! (o instanceof MapEntry)) return false;
MapEntry me = (MapEntry)o ;
return
(key == null ?
me.ge tKey () == null , key.equalslme . gecKeyl))) &&
(value == null ?
me . getVal ue ()== nul l : value , equal s{me. ge t Value(})) ;
public String toStri ng () { r e turn key + "=" + va l ue i }
/// , -
Aqu, una clase muy simple denominada MapEntry almacena y extrae las claves y valores. sta se usa en entrySet( ) para
generar un conjunto de parejas clave-valor. Observe que entrySet() utiliza un conjunto HashSet para almacenar las pare-
jas, y MapEntry adopta una solucin sencilla consistente en limitarse a utilizar el mtodo hashCode( ) de la clave key.
Aunque esta solucin es muy simple y parece funcionar en la prueba trivial realizada en SlowMap.main(), no es una imple-
mentacin correcta porque se realiza una copia de las claves y valores. Una implementacin correcta de entrySet() propor-
cionara una vista del mapa en lugar de una copia, y esta vista pennitiria la modificacin del mapa original (lo que una copia
no pennite). El Ejercicio 16 proporciona la oportunidad de corregir este problema.
Observe que el mtodo equals() en MapEntry debe comprobar tanto claves como valores. El significado del mtodo
hashCode( ) se describir en breve.
La representacin del tipo String del contenido del mapa SlowMap se genera automticamente mediante el mtodo
toString() definido en AbstractMap.
En SlowMap.main(), se carga un mapa SlowMap y luego se muestran los contenidos. Una llamada a get() demuestra que
la solucin funciona.
Ej ercicio 15: (1) Repita el Ejercicio 13 utilizando un mapa SlowMap.
Ej ercicio 16: (7) Aplique las pmebas de Maps.java a SlowMap para verificar que funciona. Corrija cualquier cosa de
SlowMap que no funcione correctamente.
Ejercici o 17: (2) Implemente el resto de la interfaz Map para SlowMap.
Ejercicio 18: (3) Utilizando como modelo SlowMap.java, cree un conjunto SlowSet.
Mejora de la velocidad con el almacenamiento hash
SlowMap.java muestra que no resulta tan dificil producir un nuevo tipo de mapa. Pero, como su nombre en ingls sugiere,
SlowMap no es muy rpido, por lo que lo ms nonnal es que no lo utilicemos si disponemos de alguna otra alternativa. El
problema est en la bsqueda de la clave; las claves no se conservan en ningn orden concreto, as que no hay ms reme-
dio que usar una simple bsqueda lineal. La bsqueda lineal es la forma ms lenta de encontrar algo.
El almacenamiento hash tiene como nico objetivo la velocidad: este almacenamiento pennite realizar las bsquedas rpi-
damente. Puesto que el cuello de botella se encuentra en la velocidad de bsqueda de las claves, una de las soluciones al
17 Anlisis detallado de los contenedores 551
problema consiste en mantener las claves ordenadas y luego utilizar Collections.binarySearch() para realizar la bsqueda
(anali zaremos el proceso correspondiente en un ejercicio).
El almacenamiento fwsh va un paso ms all presuponiendo que en realidad lo nico que queremos hacer es almacenar la
clave en algln IlIgar de forma lal que pueda ser encont rada rpidamente. La estrucUlra ms rpida en la que se puede alma.
cenar un grupo de elementos es una matriz. as que eso es lo que se utilizar para representar la infonnacin de claves (obser-
ve que decimos "informacin de claves", y no las claves mismas). Pero, como una matriz no puede cambiar de 18maii.o,
tenemos un problema: queremos almacenar un nmero indctenninado de valores en el mapa, pero si el nmero de valores
est fijado por el tamaiio de la matriz. cmo podemos solucionar el problema?
La respuesta es que la matriz no almacena las claves. A partir del objeto clave. detenninaremos un nmero que servir como
ndice dentro de la matriz. Este nmero es el cdigo ha."h generado por el mtodo hashCode( ) (en trminos de la jerga
infonntica, este mtodo sera la /uncin de hash) definido en Object y, normalmente, susti tuido en la clase que estemos
utilizando.
Para resolver el problema de la matriz de tamaiio fijo, es perfectamente posible que haya ms de una clave que genere el
mismo ndice. En otras palabras, puede haber colisiones. Debido a esto, no importa lo grande que sea la matriz: el cdigo
hash del objeto clave estar almacenado en alguna parte de la matriz.
De modo que el proceso de buscar el valor comienza calculando el cdigo hash y utilizndolo como ndice para acceder a
la matriz. Si pudiramos garantizar que no habr colisiones (lo cual es posible si disponemos de un nmero fijo de valores),
tendramos unafimcin hash pe/fec/a, pero eso es un caso especial.1 En todos los dems casos, las colisiones se gestionan
mediante un mecanismo de encadenamiento externo. La matriz no apunta directamente a un valor, sino a una lista de valo-
res. Estos valores se exploran de forma lineal uti lizando el mtodo equals( ). Por supuesto, este aspecto de la bsqueda es
mucho ms lento, pero si la funcin hash es buena, slo habr unos pocos valores en cada posicin. De este modo, en lugar
de buscar en la lista completa, saltamos rpidamente a una posicin donde slo tenemos que comparar unas pocas entradas
para encontrar el valor. Esto es mucho ms rpido, que es la razn de que HashMap sea tan veloz.
Ahora que conocemos los fundamentos del almacenamiento hash, podemos implementar un mapa has), simple:
//: containers/SimpleHashMap.java
// Un mapa hash de demostracin.
import java.util.*
import net.mindview.util.*
public class SimpleHashMap<K,V> extends AbstractMap<K,V>
// Seleccione un nmero primo como tamao de la tabla hash,
// para conseguir una distribucin uniforme:
static final int SIZE = 997
II No se puede tener una matriz fsica de genricos,
II pero s que podemos generalizar para obtener una:
@SuppressWarnings("unchecked")
LinkedList<MapEntry<K,V[] buckets
new LinkedList[SIZE] i
public V put(K key, V value) {
V oldValue = null
int index = Math.abs{key.hashCode()) % SIZE
if{buckets[index] == null)
buckets [indexl = new LinkedList<MapEntry<K, V ()
LinkedList<MapEntry<K,V bucket = buckets[index] i
MapEntry<K,V> pair = new MapEntry<K,V> (key, value) i
boolean found = false;
Listlterator<MapEntry<K,V it = bucket.listlterator()
while lit .hasNext 1)) {
MapEntry<K,V> iPair = it.next{) i
ifliPair.getKey{) .equalslkey)) {
oldValue = iPair.getValue() i
7 El caso de una funcin hash perfecta est implementado en las estructuras EnumMap y EnumSct de Java SES, porque las enumeraciones definen un
numero fijo de valores. Consulte el Capitulo 19, Tipos enumerados.
552 Piensa en Java
it.set(pairl; /1 Sustituir antiguo por nuevo
found = true;
break;
if ( ! found)
buckets (index] . add (pai r ) ;
return oldValue;
public V get(Object key)
int index = Math.abs(key.hashCode()} % SIZE
if (buckets [index] == null) return null
for (MapEntry<K,V> iPair : buckets[index])
if ( ipair. getKey () . equals (key) )
return iPair.getValue() i
return null;
public Set<Map.Entry<K,V entrySet()
Set<Map.Entry<K,V set= new HashSet<Map.Entry<K,V() j
for(LinkedList<MapEntry<K, v bucket : buckets) {
if(bucket == nulll continue
for{MapEntry<K,V> mpair : bucket)
set.add{mpairl;
return set;
public static void main (String [] args) {
SimpleHashMap<String,String> m =
new SimpleHashMap<String,String>();
m.putAll (Countries.capitals (25) ) ;
System.out.println(m) ;
System.out.println(m.get("ERITREA")) ;
System.out.println(m.entrySet()) ;
1* Output :
{CAMEROON=Yaounde, CONGO=Brazzaville, CHAD=N'djamena, COTE
D!IVOIR (IVORY COAST)=Yamoussoukro, CENTRAL AFRICAN
REPUBLIC=Bangui, GUINEA=Conakry, BOTSWANA=Gaberone,
BISSAU=Bissau, EGYPT=Cairo, ANGOLA=Luanda, BURKINA
FASO=Ouagadougou, ERITREA=Asmara, THE GAMBIA=Banjul,
KENYA=Nairobi, GABON=Libreville, CAPE VERDE=Praia,
ALGERIA=Algiers, COMOROS=Moroni, EQUATORIAL GUINEA=Malabo,
BURUNDI=Bujumbura, BENIN=Porto-Novo, BULGARIA=Sofia,
GHANA=Accra, DJIBOUTI=Dijibouti, ETHIOPIA=Addis Ababa}
Asmara
[CAMEROON=Yaounde, CONGO=Brazzaville, CHAD=N!djamena, COTE
D!IVOIR (IVORY COAST)=Yamoussoukro, CENTRAL AFRlCAN
REPUBLIC=Bangui, GUINEA=Conakry, BOTSWANA=Gaberone,
BISSAU=Bissau, EGYPT=Cairo, ANGOLA=Luanda, BURKINA
FASO=Ouagadougou, ERITREA=Asmara, THE GAMBIA=Banjul,
KENYA=Nairobi, GABON=Libreville, CAPE VERDE=Praia,
ALGERIA=Algiers, COMOROS=Moroni, EQUATORIAL GUINEA=Malabo,
BURUNDI=Bujumbura, BENIN=Porto-Novo, BULGARIA=Sofia,
GHANA=Accra, DJIBOUTI=Dijibouti, ETHIOPIA=Addis Ababa]
* /1/,-
Como las "posiciones" de una tabla hash se denominan a menudo segmentos (buckels), la matriz que representa a la tabla
se denomina buckets. Para conseguir una distribucin uniforme, el nmero de segmentos es, nonnalmellte, un nmero
17 Anal isis detallado de los contenedores 553
primo.
8
Observe que se trata de una matri z de tipo LinkedList, que se encarga automt icamente de las colisiones. Cada
nuevo elemento se aade simplemente al final de la li sta correspondiente a un segmento concreto. Incluso aunque Java no
nos permite crear una matriz de genricos, si que es posible hacer una referencia a dicha matriz. Aqu resulta til hacer una
generalizacin de dicha matriz, para evi tar las proyecciones adicionales de tipos posteriormente en el cdigo.
Para el mtodo put( ), se invoca hashCode( ) utilizando la clave y el resultado se transforma en un nmero positivo. Para
insertar el nmero resultante en la matriz buckets, se emplea el operador de mdulo junto con el tamai'i.o de la matriz. Si
dicha posicin es nuJl , quiere decir que no hay ningn elemento cuyo cdigo /ash se corresponda con esa posicin, por lo
que se crea un nuevo objeto LinkedList para almacenar el objeto que acaba de ser asignado a esa posicin. Sin embargo,
el proceso nomlal consiste en examinar la lista para ver si hay duplicados y, en caso de que los haya, el va lor antiguo se
almacena en oldValue y el valor nuevo sustitllye al antiguo. El indicador found nos dice si se ha encontrado una pareja
clave-valor antigua y, en caso contrario. la nueva pareja se aade al final de la lista.
El mtodo get( ) calcula el indice para la matriz buckets de la misma fonna que put() (esto es importante con el fin de
garantizar que tenninemos en la mi sma posicin). Si existe un objeto LinkedList, se le explora en busca de una correspon-
dencia.
Observe que no pretendemos decir que esta implementacin est optimi zada para obtener el mejor rendimiento posible; slo
tratamos de ilustrar las operaciones realizadas por un mapa has/. Si examinamos el cdigo fuente de java.utiI.HashMap,
podremos ver la implementacin realmente optimizada. Asimismo, por simpl icidad, SimpleHashMap usa la misma tcni-
ca para entr.ySet( ) que ya se utiliz en Slm'\' Map, la cual es demasiado simpli sta y no funciona para los mapas de prop-
sito general.
Ejercicio 19: ( 1) Repita el Ejercicio 13 utilizando un contenedor SimplcHashMap.
Ejercicio 20: (3) Modifique SimpleHashMap para que informe de las colisiones y pruebe el sistema aadiendo el
mismo conjunto de datos dos veces, con el fin de que se produzcan colisiones.
Ejercicio 21: (2) Modifique SimpleHashMap para que informe del nmero de "consultas" necesarias cuando se produ-
cen coli siones. En otras palabras. informe del nmero de ll amadas a next( ) que hay que realizar en los
iteradores que recorren las listas enlazadas.
Ejercicio 22: (4) Implemente los mtodos e1ear() y remoye( ) para SimpleHashMap.
Ejercicio 23: (3) Impl emente el resto de la interfaz Map para SimpleHashMap.
Ejercicio 24: (5) Siguiendo el ejemplo de SimpleHashMap.jaya, cree y pruebe un contenedor Si mpleHashSet.
Ejercicio 25: (6) En lugar de usar un iterador Listlterator para cada segmento, modifique MapEntry para que sea una
nica li sta enlazada autocontenida (cada objeto MapEntry debe tener un enlace directo al siguiente obje-
to MaIIEntry). Modifique el resto del cdigo de SimpleHashMap.jaya para que func ione correctamente
esta solucin.
Sustitucin de hashCodeO
Ahora que comprendemos cmo funciona el almacenamiento hash, ti ene ms sentido escribir nuestro propio mtodo
hashCode( ).
En primer lugar, nosotros no controlamos la creacin del valor concreto que se usa para obtener el ndice de la matriz de
segmentos. Ese valor depende de la capacidad del objeto HashMap concreto y dicha capacidad varia dependiendo de lo
lleno que est el contenedor y de cul sea el/aclor de carga (describiremos este trmino ms adelante). Por tanto, el valor
generado por nuestro mtodo hasbCode( ) se procesar ulterionnente para crear el ndice de la matriz de segmentos (en
SimpleHashMap, el clculo consiste simplemente en hacer una operacin de mdulo segn el tamao de la matriz de seg-
mentos).
M En realidad, un numero primo no es en la prcca el tamao ideal para los segmentos /asll, y las implementaciones hash ms recientes en Java utilizan
un tamao igual a las potencias de dos (despus de haber realizado pruebas exhaustivas). La divisin o el clculo del resto es la operacin mas lenta en un
procesador moderno. Con una longitud de la tabla hash igual a una potencia de dos, se puede utilizar una operacin de enmascaramiento en lugar de la de
di visin. Puesto que get( ) es. con mucho, la operacin mas comn, % representa una gran parte de! coste y la solucin basada en una potencia de dos l i ~
mina este coste (aunque puede que tambin afecte a algunos mtodos hashCode( .
554 Piensa en Java
El factor ms importante a la hora de crear un mtodo hashCode( ) es que, independientemente de cundo se invoque
hashCode( ). ste debe producir el mismo valor para un objeto concreto cada vez que sea invocado. Si tuviramos un obje-
10 que produjera un valor hashCodc() al insertarlo con put() en un contenedor HashMap y otro valor distinto al extraer-
lo con get(), no podriamos nunca extraer los objetos, Por tanto, si nuestro mtodo hashCode() depende de datos del objeto
que varen, es necesario informar al usuario de que al modificar los datos se generar una clave diferente, porque se tendr
un cdigo hashCode( ) diferente,
Adems, normalmel11e 110 conviene generar un valor hashCode( ) que est basado en infonnacin de los objetos de carc-
ter distintivo, en concreto. el valor de thi s genera un cdigo hashCode() no muy bueno. porque eI1lonces es imposible gene-
rar una nueva clave idntica a la que se ha usado para insertar con put() la pareja original clave-valor. ste era el problema
que ya detectamos en SpringDetector.j ava. porque la implementacin predeterminada de hashCode( ) Ulili:a precisamen-
te la direccin del objeto. Por tanto, lo que conviene es utilizar infonnacin del objeto que le identifique de alguna manera
significativa.
Podemos ver un buen ejemplo en la clase String. Las cadenas de caracteres tienen la caracterstica especial de que si un pro-
grama dispone de varios objetos String que contienen secuencias de caracteres idnticas, entonces todos esos objetos String
se corresponden con la misma zona de memoria. Por tanto, tiene bastante sentido que el cdigo hashCode() producido por
dos instancias diferentes de la cadena de caracteres "helio" deba ser idntico. Podemos ver esto en el siguiente programa:
jj: containersjStringHashCode.java
public class StringHashCode {
public static void main{String[] args) {
String[] helIos = "HelIo Hello".split{" ")i
System.out.println(hellos(O] .hashCode ()) i
System. out. println (helIos (1] . hashCode () ) ;
1* Output: (Sample)
69609650
69609650
*/// ,-
El mtodo hashCode() para String est claramente basado en el contenido de la cadena de caracteres,
Por tanto, para que un mtodo hashCode( ) sea efect ivo, debe ser rpido y adems significativo; en otras palabras, debe
generar un valor basado en el contenido del objeto. Recuerde que este va lor no tiene por qu ser unvoco (lo que nos inte-
resa es la velocidad no la unici dad) pero enrre hashCode() y equals(), la identidad del objeto dcbe ser completamente espe-
cificada .
Puesto que el cdigo generado por hashCode( ) se procesa adicionalmente antes de generar el ndice de la matriz, el rango
de valores no es importante, basta con que el mtodo genere un valor int.
Hay otro factor ms a tener cuenta: un buen mtodo hashCode( ) debe producir una distribucin homognea de los valo-
res. Si los valores tienden a estar agrupados, entonces el contenedor HashMap o HashSet estar ms cargado en unas reas
que en otras, y no ser tan rpido como podra serl o si se di spusiera de una func in hash unifonnemente distribuida.
En el libro EJJeclive Java Programming Langllage Guide (Addison-Wesley, 2001), Joshua Bloch nos da una receta bsi -
ca para generar un mtodo hashCode() aceptable:
1. Almacene algn va lor constante di stint o de cero, como por ejempl o 17, en una variable int denomi nada result.
2, Por cada campo significati vo f del objeto (es decir, cada campo que sea tenido en cuenta por el mtodo equal s()),
calcule un cdigo Itash e de tipo int correspondiente a ese campo:
Tipo del campo Clculo
boolean c = (f? O: 1)
byte, char, short o nt e = (int)f
long e = (int)(f ' (f >32)
noat c = Float.f1oatTolntBits(f)j
17 Anlisis detallado de los contenedores 555
Tipo del campo Clculo
double
Object , donde equals( } invoca a equals( ) para este campo
Matriz
long I = Doublc.doubleToLongBits(f); e = (inl)(1 A (1 >>> 32
e = f.ha,heode( )
----
Aplicar las reglas anteriores a cada elemento
--------'
3. Combi ne el cdi go hash recin calcul ado: result = 37 * result + e;
4. Devuelva resulto
S. Examine el cdigo hashCode( ) resullanle y asegrese de que instancias igual es tengan cdigos hash iguales.
He aqu un ejemplo construido sobre estas directrices:
// : containers/ CountedString.java
JI Creacin de un mtodo hashCode () adecuado.
import java.util.*;
import static net.mindview.util . Print.*;
public class CountedString {
private static List<String> created
new ArrayList<String>();
private String Si
private int id = O;
public CountedString (String str) {
s = str;
created.add(s ) i
// id es el nmero total de instancias
// de esta cadena que CountedString est usando :
for (String s2 : created)
if (s2.equals (s))
id++i
public String toString {)
return "String: + s + It id: It + id +
It hashCode () : It + hashCode () i
public int hashC0de( )
// La tcnica simple:
// return s.hashCode ( ) * idi
// Utilizacin de la receta de Joshua Bloch:
int result '" 17 i
result '" 37 * result + s.hashCode ( ) ;
result '" 37 * result + id;
return result i
public boolean equals (Object o ) {
return o instanceof CountedString &&
s. equals ( ( (CountedString) o) . s) &&
id ='" (( CountedString) o ) . id;
public static void main(String[] args) {
Map<CountedString,Integer> map
new HashMap<CountedString,Integer>();
CountedString[] es '" new CountedString[S] i
for(int i = O; i < cs .length; i++) {
cs[iJ '" new CountedString("hi") i
map.put(cs[i] I i) i // Autobox int -> Integer
556 Piensa en Java
print (map) ;
for (CountedString cstring : es) {
print ( IlLooking up It + cstringl;
print(map.get(cstring)) ;
/* Output: (Sample)
{String: hi id: 4 hashCode(): 146450=3, String: hi id: 1
hashCode(), 146447=0, String, hi id, 3 hashCode(), 146449=2,
String: hi id: S hashCode{): 146451=4, String: hi
id, 2 hashCode(), 146448=1)
Looking up String: hi id: 1 hashCode(): 146447
O
Looking up String : hi id: 2 hashCode() , 146448
1
Looking up String: hi id: 3 hashCode() , 146449
2
Looking up String : hi id: 4 hashCode() , 146450
3
Looking up String: hi id: 5 hashCode () , 146451
4
* /// ,-
CountedString incluye un objeto String y un id que representa el nmero de objetos CountedString que conti enen un obje-
to String idntico. El recuento se realiza en el constnlctof, iterando a travs del contenedor ArrayList esttico en el que
estn almacenadas todas las cadenas de caracteres.
Tanto hashCode() como equals( ) generan resultados basados en ambos campos; si estuvieran basados simplemente en el
objeto String o en el valor id, habria correspondencias duplicadas para valores diferentes
En main(), se crean varios objetos CountedString utilizando el mismo objeto String, con el fin de demostrar que los dupli-
cados crean valores di stintos, debido al campo id que se utiliza como recuento. El ejemplo muestra el contenedor HashMap.
para que veamos cmo se almacenan los elementos internamente (no hay ningn orden discernible), y despus se busca cada
clave individualmente para demostrar que el mecanismo de bsqueda funciona correctamente.
Como segundo ejemplo, considere la clase Individual que ya usamos como clase base para la biblioteca typeinfo.pet defi-
nida en el Capirulo 14, Informacin de lipos. La clase Individual se usaba en dicho ca pirulo, pero habiamos retardado su
definicin hasta este capitulo con el fin de que el lector comprendiera la implementacin:
jj : typeinfojpetsjIndividual.java
package typeinfo.pets;
public class Individual implements Comparable<Individual>
private static long counter : O;
private final long id : counter++;
private String name;
public Individual (String name) { this. name
jj 'name' es opcional:
public Individual () ()
public String toString()
return getClass() .getSimpleName() +
(name :: null ? "" : " " + name ) ;
public long id () ( return id;
public boolean equals(Object o)
return O instanceof Individual &&
id ::::: (( Individual ) o) . id;
public int hashCode()
int result : 17;
if(name != null )
name; }
result = 37 * result + name.hashCode();
result = 37 * result + (int) id;
return result;
public int compareTo(Individual arg)
JI Comparar primero por el nombre de la clase:
String first = getClass(} . getSimpleName{);
String argFirst = arg.getClass() .getSimpleName() i
int firstCompare = first.compareTo(argFirstl;
if(firstCompare != O)
return firstCompare;
if (name ! = null && arg. Dame ! = null) {
int secondCompare = name.compareTo(arg.namel i
if(secondCompare != O)
return secondCompare;
17 Anlisis detallado de los contenedores 557
return (arg .id < id ? -1 : (arg .id id ? O 111;
El mtodo compareTo( ) tiene una jerarqua de comparaciones, de modo que producir una secuencia que estar ordenada
primero por el tipo real , y luego por Dame si es que existe y finalmente por orden de creacin. He aqu un ejemplo que
demuestra cmo funciona:
jj : containersjlndividualTest.java
import holding.MapOfList
import typeinfo.pets.*
import java.util.*
public class IndividualTest
public sta tic void main (String (] args) {
Set<Individual> pets = new TreeSet<Individual>();
for(List<? extends Pet> lp :
MapOfList.petPeople.values() )
for (Pet p : lp)
pets. add (pi;
System.out.println(petsl;
j * Output:
[Cat Elsie May, Cat Pinkola, Cat Shackleton, Cat Stanford
aka Stinky el Negro, Cymric Molly, Dog Margrett, Mutt Spot,
Pug Louie aka Louis Snorkelstein Dupree, Rat Fizzy,
Rat Freckly, Rat Fuzzy)
*jjj.-
Puesto que todas estas mascotas utilizadas en el ejemplo tienen nombres, se las ordena primero por tipo y luego, dentro de
cada tipo, segn su nombre.
Escribir sendos mtodos hashCode( ) y equals( ) para una nueva clase puede resultar complicado. Puede encontrar herra-
mientas que le ayudarn a hacerlo en el proyecto "Jakarta Commons" de Apache, en jakal'la. apache. org/commons, en el
subdirectorio Iang" (este proyecto dispone tambin de muchas otras bibliotecas potencialmente tiles, y parece ser la res-
puesta de la comunidad Java a la comunidad www.boosl.orgde C++).
Ejercicio 26: (2) Aada un campo char a CountedString que tambin se inicialice en el constructor, y modifique los
mtodos hashCode() y equals( ) para incluir el valor de este campo charo
Ejercicio 27: (3) Modifique el mtodo hashCode() en CountedString.java eliminando la combinacin con id, y
demuestre que CountedString sigue funcionando como clave. Cul es el problema con esta tcnica?
Ejercicio 28: (4) Modifique net/mindview/utiUTuple.java para convertirla en una clase de propsito general aadien-
do hashCode( ), equals(), e implementando Comparable para cada tipo de Tuple.
558 Piensa en Java
Seleccin de una implementacin
A estas alturas. el lector deberia entender. que aunque slo hay cuatro tipos de contenedores (Map. List. Se! y Queue) hay
mas de \lna implementacin de cada interfaz. Si necesitamos utilizar la funcionalidad ofrecida por una interfaz concreta.
cmo podemos decidir qu implementacin empIcar?
Cada una de las diferentes implementaciones tiene SlIS propias caractersticas, ventajas e inconvenientes. Por ejemplo, puede
\'er en la imagen incluida al principio de este captulo que la "ventaja" de I-Iashtable, Vector y Stack es que son clases here-
dadas. de modo que el cdigo antiguo 110 dejar de funcionar (aunque lo mejor es que no utilice esos contenedores en los
programas nuc\'os).
Los diferentes tipos de Queue en la biblioteca Ja\a se diferencian slo en la fonna en que accptan y devueh'cn los valores
(\'cremos la importancia de esto en el Capnllo 11. Concurrencia).
La disllncin entre unos contenedores y otros usualmente reside en el almacenamiento que se utiliza C0l110 respaldo: es decir.
en las estructuras de datos que implementan fisicamente la interfaz deseada. Por ejemplo. como List y LinkedList
implementan la interfaz List. las operacioncs bsicas de List son iguales independientemente de cul utilicemos. Sin embar-
go. ArrayList utiliza como respaldo una matriz mientras que LinkedList se implementa en la forma usual de las listas
doblcmente enlazadas. en fonna de objetos individuales cada uno de los cuales contiene tanto los datos como sendas refe-
rencias a los elementos anterior y siguiente de la lista. Debido a esto, si queremos hacer muchas inserciones y eliminacio-
nes en mitad de una lista, la eleccin apropiada ser LinkedList (LinkedList dispone tambin de funcionalidad adicional
que eSH definida en AbstractSequentiaIList). Si no es el caso, ArrayList suele ser ms rpido.
Como ejemplo adicional, podemos implementar un conjunto mediante los contenedores TreeSet. Bas hSet O
LinkedHashSet .
9
Cada uno de estos contenedores tiene diferentes comportamientos; l-IashSet es para uso normal y pro-
porciona una gran velocidad en las bsquedas. LinkedHas hSet mantiene las parejas en orden de insercin y TreeSet est
respaldado por un contenedor TreeMap y est diseiiado para disponer de un conjunto constantemente ordenado. La imple-
mentacin sc elige basndose en el comportamiento que se necesite.
En ocasiones. las diferentes implementaciones de un contenedor concreto tendrn una serie de operaciones en comn. pero
el rendimiento de esas operaciones ser diferente. En este caso, la seleccin entre unas implementaciones y otras se basa en
la frecuencia con la que se utilice una operacin concreta y lo veloz que necesitemos que sea esa operacin. Para casos como
estos, una de las maneras de evaluar las diferencias entre las distintas implementaciones de contenedores es mediante una
prueba de rendimiento.
Un marco de trabajo para pruebas de rendimiento
Para evitar la duplicacin de cdigo y para que las pmebas sean coherentes. he lcluido la funcionalidad bsica del proce-
so de pmebas en un marco de trabajo que define la parte principal del programa. El cdigo siguiente establece una clase
base a partir de la cual se crea una lista de clases internas annimas, una clase para cada una de las diferentes pruebas. Cada
una de estas clases internas se invoca como parte del proceso de pruebas. Esta solucin permite aiiadir y eliminar fcilmen-
te distintos tipos de pmebas.
Se trata de otro ejemplo del patrn de disello basado en el Mtodo de plolllillas. Aunque se sigue la solucin tipica del mto-
do de plantillas consistente en sustituir el mtodo Test. test( ) para cada prueba concreta, en este caso la parte fundamental
del cdigo (que no cambia) se encuentra en una clase separada Tester.
lo
El tipo de cont\!l1edor que estamos probando es el
parmetro genrico C:
11 : containers / Test.java
II Marco de trabako para realizar pruebas temporizadas de contenedores.
public abstract class Test<C> {
String name
public Test (String name) { this. name = name }
9 O como EnumSeI o que son casos especiales. Aunque somos conscientes dI.! que puede haber muchas implementaciones
adicionales especializadas de diversas interfaces de contenedores, en esta seccin estamos tmtando de examinar 5610 los casos ms generales.
10 Krlysztof Sobolewski me ayud a disear los genricos de este ejemplo.
17 Analisls detallado de los contenedores 559
JI Sustituir este mtodo para las diferentes pruebas.
// Devuelve el nmero real de repeticiones de la prueba.
abstraet int test(C container, TestParam tpl i
///0-
Cada objeto Test almacena el nombre de dicha prueba. Cuando se invoca el mtodo test( ). hay que pasarle el contenedor
que hay que probar junto con un "elemento de transferencia de datos" o " mensajero" que almacene los diversos parmetros
correspondientes a dicha prueba concreta. Los parmetros incluyen size, que indica el nmero de elementos del contenedor
y loops. que controla el nmero de iteraciones de la prueba. Estos parmetros pueden o no utilizarse en todas las pruebas.
Para cada contenedor se realizar una secuenci a de llamadas a test(). cada una con un objeto TestParam diferente, de modo
que TestParam tambin contiene mtodos array( ) estt icos que hacen que resulte fcil crear matrices de objetos
TestParam. La primera versIn de array() toma una lista de argumentos variabl es que contiene valores size y loops alter-
nantes, mientras que la segunda versin toma el mismo tipo de li sta. aunque con valores almacenados dentro de objetos
Stri ng. de esta forma, puede utilizarse para analizar argumentos de la linea de comandos:
/1: concainers/TestParam.java
/ / Un "objeto de transferencia de datos".
public class TestParam {
public final int size
public final int loops
public TestParam(int size, int loops)
this.size = size
this.loops = loopsi
/1 Crear una matriz de TestParam a partir de una secuencia de varargs:
public static TestParam[} array(int ... values) {
int size = values.length/2
TestPa:::.-am[} result = new TestParam[sizeJ i
int n = O
for(int i = Di i < size i++)
result[iJ = new TestParam(values[n++}, values[n++})
return result;
11 Convertir una matriz de tipo String a una matriz TestParam:
public static TestParam[] array(String[} values) {
int [J vals = new inc [values .length] i
for(int i = O i < vals.lengthi i++l
vals[i] = Integer.decode(values[i] );
return array(vals) i
Para utilizar el marco de trabajo, lo que hacemos es pasar el contenedor que hay que probar junto con una lista de objetos
Test a un mtodo Tester.run( ) (se trata de mtodos genricos de utilidad sobrecargados, que reducen la cantidad de texto
que hay que escribir para utilizarlos). Tester.run( ) invoca el constructor sobrecargado apropiado y luego llama a
timedTest(), que ejecuta para ese contenedor cada una de las pmebas de la lista. timedTest( ) repile cada prueba para cada
uno de los objetos TestParam contenidos en paramList. Puesto que pararnList se inicializa a partir de la matriz esttica
defaultParams, podemos cambiar la lista paramList para todas las pruebas resaignando defaultParams, o bien podemos
modificar la li sta paramList para una prueba determinada, pasndole para esa prueba una lista paramList personalizada:
1/ : containers/Tester.java
// Aplica objetos Test a listas de diferentes contenedores.
import java.util.*;
public class Tester<C>
public static int fieldWidth = 8;
public static TestParam[] defaultParams= TestParam.array (
10, 5000, 100, 5000, 1000, 5000, 10000, 500);
560 Piensa en Java
1/ Sustituir esto para modificar
protected C initialize(int size}
protected C container;
la inicializacin anterior a la prueba:
{ return container;
private String headline = "11;
private List<Test<C tests;
private static String stringField(}
return "%11 + fieldWidth + nsn;
private static String numberField(}
return "%" + fieldWidth + lid" i
private static int sizeWidth = 5;
private static String sizeField = "%" + sizeWidth + "s"
private TestParam[] pararnList = defaultParams;
public Tester(C container, List<Test<C tests}
this.container = container;
this.tests = tests;
if(container != null}
headline = container. getClass () . getSimpleName () i
public Tester(C container, List<Test<C tests,
TestParam[] paramList}
this(container, tests) i
this.pararnList = paramList
public void setHeadline(String newHeadline}
headline = newHeadline
II Mtodos genricos de utilidad:
public static <C> void run(C cntnr, List<Test<C tests) {
new Tester<C> (cntnr, tests} .tirnedTest(};
public static <C> void run(C cntnr,
List<Test<C tests, TestParam[] paramList}
new Tester<C> (cntnr, tests, pararnList} .timedTest();
private void displayHeader()
/1 Calcular anchura y rellenar con '-':
int width = fieldWidth * tests.size() + sizeWidth
int dashLength = width - headline .length () 1;
StringBuilder head = new StringBuilder(width)
for {int i = O; i < dashLength/2 i i+t}
head. append { , - 1 i
head. append (t t);
head. append (headline)
head.append(t t);
for(int i = D i < dashLength/2; i++)
head.append(t-t) ;
System.out.println(head)
II Imprimir cabeceras de columnas:
System.out. format (sizeField, ttsize
ll
) i
for(Test test: tests)
System.out. format (stringField{) , test . name}
System.out.println(} i
II Ejecutar las pruebas
public void timedTest()
displayHeader()
for(TestParam param :
para este contenedor:
(
paramList)
System.out . format{sizeField, param. size) i
for(TestcC> test tests} {
e kontainer initialize(param. size);
long start = System. nanoTime() i
// Invocar el mtodo sustituido:
int reps = test . test(kontainer, param);
long duratian = System. nanoTime{) - start
17 Analisi s detallado de los contenedores 561
long timePerRep = duratian I reps /1 Nanosegundos
System.out.format(numberField(), timePerRep) i
System.out.println() ;
}
///,-
Los mtodos stringField() y numberField() producen cadenas de fonnateo para imprimir los resultados. La anchura estn-
dar de format eo puede variarse modifi cando el val or estti co fieldWidth . El mtodo displayHeader() da fonnato e impri-
me la infonnacin de cabecera para cada prueba.
Si necesitamos realizar una ini ciali zacin especial, sustituimos el mtodo initialize( ). Esto produce un obj eto contenedor
ini ciali zado con el tamao apropiado; podemos modificar el objeto contenedor existente o crear uno nuevo. Como puede
ver en test() el resultado se captura en una referencia local denominada kontainer, que permite sustituir el mi embro alma-
cenado container por un contenedor ini ciali zado completamente diferente.
El valor de retorno de cada mtodo Test.test( ) debe ser el nmero de operaciones realizado por di cha prueba. 10 que se
utiliza para cal cular el nmero de nanosegundos requerido por cada operacin. Hay que tener en cuenta que
System.nanoTime() produce nonnalmente valores con una granularidad mayor que uno (y esta granularidad variar de una
mquina a otra y de un sistema operati vo a otro), lo que produce un cierto error estad sti co en los resultados.
Los resultados pueden variar de una mquina a otra, estas pruebas slo pretenden comparar el rendimi ento de los diferen-
tes contenedores.
Seleccin entre listas
He aqu una prueba de rendimiento para las operaciones de List ms esenciales. Por comparacin, tambi n se muestran las
operaciones de Queue ms importantes. Se crean dos li stas separadas de pmebas, con el fin de probar cada clase de conte-
nedor. En este caso, las operaciones de Queue slo se aplican a listas de tipo LinkedList.
jj: containersjListPerformance.java
jj Ilustra las diferencias de rendimiento en las listas.
jj {Args: 100 sao} Pequeo para que las pruebas sean cortas
import java.util. *
import net.mindview.util.*
public class ListPerformance
static Random rand = new Random()
static int reps = 1000;
static List<Test<List<Integer> tests
new ArrayList<Test<List<Integer>();
sta tic List<Test<LinkedList<Integer> qTests
new ArrayList<Test<LinkedList<Integer>();
static {
tests . add(new Test<List<Integer{"add" )
int test(List<Integer> list, Tes t Param tp)
int loops = tp.loopsi
int listSize = tp . size;
for(int i = O i < loops; i++)
list . clear () ;
for(i n t j = O; j < l istSize; j++)
list.add(j) ;
562 Piensa en Java
}
} I ;
return loops * listSize
tests. add (new TestcListclnteger ("get")
int test(Listclnteger> list, TestParam tpl
int lcops = tp.loops * reps;
}
} I ;
int listSize = list.size();
for(int i = O; i < loops; i++)
list.get(rand.nextlnt(listSize)) ;
return loops;
tests.add(new TestcListclnteger("set
"
)
int test(Listclnteger> list, TestParam tp)
int lcops = tp.loops * reps;
}
}I ;
int listSize = list . size()
for(int i = O; i e lDops; i++)
list.set(rand.nextlnt(listSize), 47);
return lDOpS;
tests. add (new TestcListclnteger (11 iteradd") {
int test(Listclnteger> list, TestParam tp) {
final int LOOPS = 1000000;
}
} I ;
int half = list.size{) / 2;
Listlteratorclnteger> it = list.listlterator(half);
for(int i = O; i < LOOPS; i++)
it.add(471;
return LOOPS
tests.add(new Test<List<Integer("insert")
int test(List<Integer> list, TestParam tp)
int loops = tp.loops
}
} I ;
for(int i = O; i < loops; i++)
list.add(5, 47}; // Minimizar el coste del acceso aleatorio
return loops;
tests. add {new Test<List<Integer (11 remove 11)
int test{List<Integer> list, TestParam tp}
int loops = tp.loops;
}
} I ;
int size = tp.size;
for(int i = O; i < loops; i++)
list. clear () ;
list.addAll{new CountinglntegerList(size));
while(list.sizeO > 5)
list.remove(5); // Minimizar el coste del acceso aleatorio
return loops * size;
// Pruebas de comportamiento de las colas:
qTests.add(new Test<LinkedList<Integer(lIaddFirst
ll
)
int test(LinkedList<Integer> list, TestParam tp) (
int loops = tp.loops;
int size = tp . size
for(int i = O; i < loops; i++)
list. clear () ;
}
} I ;
for(int j = o; j <: size; j++l
list.addFirsc(47) ;
return loops * size
17 Anlisis detallado de los contenedores 563
qTests. add (new Test<:LinkedList<:Integer ("addLast ") {
int tesc{LinkedList<Integer> list, TestParam tp) {
int loops = ep.loops
}
} I ;
int size = tp.size;
for(int i = o; i < loops; i++) {
list . clear () ;
for(int j = o; j <: size; j++)
list.addLast(47) i
return loops * size;
qTests. add (
new Testc::LinkedLisc<Integer("rmFirst")
int test (LinkedList<Inceger> list. TestParam tp) {
inc loops = tp.loops;
int size = tp.size;
for(int i = O; i <: loops; i++}
list. clear () ;
list.addAll(new CountinglntegerList(size});
while(list.size() > O)
list.removeFirst() ;
return loops * size;
}
} I ;
qTests. add (new Test<LinkedList< Integer (" rmLast I!) {
int test(LinkedList<Integer> list, TestParam tp) {
int loops = tp.loops;
}
}) ;
int size = tp.size;
for(int i = O; i < loops; i++) {
list. clear () i
list.addAll(new CountinglntegerList(size)) i
while (list.size () > Ol
list.removeLast() i
return loops * size
static class ListTester extends Tester<List<Integer
public ListTester(List<Integer> container,
List<Test<List<Integer> tests) {
super (container, tests);
II Rellenar con el tamao apropiado antes de cada prueba:
@Override protected List<Integer> initialize(int size) {
container.clear() ;
container.addAll(new CountinglntegerList{size));
return container;
II Mtodo de utilidad:
public static void run(List<Integer> list,
564 Piensa en Java
List<Test<List<Integer> tests) {
new ListTester(list, tests) .timedTest(};
public static void main{String[] argsl
if(args.length > O)
Tester.defaultParams = TestParam,array(args) j
// Slo se pueden hacer estas dos pruebas en una matriz:
Tester<List<Integer arrayTest =
new Tester<List<Integer(null, tests.suhList(l, 3}) {
1/ Esto se invocar antes de cada prueba.
JI Produce una lista de tamao fijo respaldada por una matriz:
@Override protected
List<Integer> initialize(int size) {
Integer (] ia = Generated. array (Integer. class I
new CountingGenerator.lnteger() I size);
return Arrays . asList(ia) i
}
} ;
arrayTest. setHeadline ( "Array as List 11 ) ;
arrayTest . timedTest() ;
Tester.defaultParams= TestParam.array(
10, 5000, 100, 5000, 1000, 1000, 10000, 200);
if(args . length > O)
Tester . defaultParams = TestParam. array (args) ;
ListTester.run(new ArrayList<Integer>() I tests);
ListTester.run(new LinkedList<Integer>() I tests);
ListTester.run(new Vector<Integer>() I tests) i
Tester.fieldWidth = 12;
Tester<LinkedList<Integer qTest =
new Tester<LinkedList<Integer(
new LinkedList<Integer>(), qTests);
qTest . setHeadline ( "Queue tests");
qTest.timedTest() ;
/ *
Output: (Samp1e)
- - - Array as List
size get set
10 130 183
100 130 164
1000 129 165
10000 129 165
--------------------- ArrayList ---------------------
size add get
10 121 139
100 72 141
1000 98 141
10000 122 144
- - - -- - -- - -- - - - - - - - - --
size add get
10 182 164
100 106 202
1000 133 1289
10000 172 13648
set iteradd
191 435
191 247
194
190
LinkedList
839
6880
set iteradd
198 658
230 457
1353 430
13187 435
insert
3952
3934
2202
14042
insert
366
108
136
255
remove
446
296
923
7333
remove
262
201
239
239
- - --- - - - - - - - - - - - - - - - -- -
Vector
- - - -- - - - - - - - - - - - -- - - - --
size add get set iteradd ir.sert remove
10 129 145 187 290 3635 253
100 72 144 190 263 3691 292
1000 99 145 193 846 2162 927
10000 108 145 186 6871 14730 7135
17 Anlisis detallado de los contenedores 565
-- - - - - -- - -- - ----- --- Queue tests ---- -- - - -- -- - - - - - - --
size addFirst addLast rmFirst rmLast
10 199 163 251 253
100 98 92 180 179
1000 99 93 216 212
10000 111 109 262 384
*/1/,-
Cada una de las pruebas requiere una consideracin cuidadosa para garantizar que estemos produciendo resultados signifi-
cativos. Por ejemplo, la prueba "add" borra la lista y luego la rellena de acuerdo con el tamao de lista especificado. La lla-
mada elear( ) para borrar fonua parte, por tanto, de la pmeba y puede tener un impacto sobre el tiempo de ejecucin,
especialmente para las pruebas ms pequeas. Aunque los resultados parecen bastante razonables en este caso, podramos
perfectamente pensar en reescribir el marco de trabajo de pruebas para crear una llamada a un mtodo de preparacin, (que
en este caso incluira la llamada a ele.rO) ji/era del bucle de cronometrado.
Observe que, para cada prueba, es necesario calcular con precisin el nmero de operaciones que tienen lugar y devolver
dicho valor desde test(), para que la temporizacin sea correcta.
Las pruebas "get" y "set" utilizan el generador de nmero aleatorios para realizar accesos a la lista. Analizando la salida
podemos ver que, para una lista respaldada por una matriz y para un contenedor ArrayList, estos accesos son rpidos y muy
coherentes independientemente del tamao de la li sta, mientras que para un contenedor LinkedList, el tiempo de acceso
aumenta de manera muy significati va para las listas de mayor tamao. Claramente, las listas enlazadas no representan una
buena eleccin si vamos a realizar muchos accesos aleatorios.
La prueba Hiteradd" utili za un lterador en mitad de la lista para insertar nuevos elementos. Para un contenedor ArrayList,
esta operacin resulta costosa a medida que el tamao de la li sta crece, pero para otro de tipo LinkedList es relativamen-
te barata y ese coste es constante independientemente del tamao. Esto tiene bastante sentido porque un contenedor
ArrayList debe crear espacio y copiar todas sus referencias durante una insercin. Esta operacin resulta muy costosa a
medida que crece el tamao del contenedor ArrayList. El contenedor LinkedList, por el contrario, slo necesita enlazar un
nuevo elemento, y no necesita modificar el resto de la lista, por lo que cabe esperar que el coste sea aproximadamente el
mismo independientemente del tamao de la lista.
Las pruebas "insert" y "remove" utilizan la posicin nmero 5 como punto de insercin y de eliminacin, en lugar de uti-
lizar uno de los extremos de la lista. Un contenedor LinkedList trata los extremos de la lista de manera especial, lo que per-
mite mejorar la velocidad cuando se usa el contenedor LinkedList como una cola. Sin embargo, si se aaden o eliminan en
mitad de la lista, hay que incluir el coste del acceso aleatorio, que ya hemos visto que vara para las diferentes implementa-
ciones de la li sta. Realizando las inserciones y eliminaciones en la posicin 5, el coste del acceso aleatorio debera ser des-
preciable y slo deberamos ver el coste de la insercin y la eliminacin, pero no podremos observar los resultados de las
optimizaciones especiales que se aplican a los extremos de un contenedor LinkedList. Podemos ver, analizando la salida,
que el coste de adicin y eliminacin en un contenedor LinkcdList es bastante bajo y que 110 vara con el tamailO de la lista,
pero con un contenedor ArrayList, las inserciones son especialmente caras y el coste se incrementa con el tamao de la
lista.
A partir de los datos correspondientes a Queue, podemos ver la rapidez con que LinkedList pennite insertar y eliminar ele-
mentos de los extremos de la lista, lo cual es el comportamiento ptimo para una cola.
Nonoalmente, podemos limitamos a invocar Tester.run(), pasando el contenedor y la lista tests. Aqu , si n embargo. debe-
mos sustituir el mtodo initialize() para que la lista se borre y se rellene antes de cada prueba; en caso contrario, el control
del tamao de la lista se perdera durante las diversas pruebas. ListTester hereda de Tester y realiza esta inicializacin
empleando CountinglntegerList. El mtodo de utilidad run( ) tambin se sustituye.
Tambin queremos comparar el acceso a una matriz con el acceso a un contenedor (principalmente con el acceso a
ArrayList). En la primera prueba de main(), se crea un objeto Test especial utilizando una clase interna annima. El mto-
do initialize( ) se sustituye para crear un nuevo objeto cada vez que se le invoca (ignorando el objeto container almacena-
do, de modo que null es el argumento container para este constructor Tester). El nuevo objeto se crea utilizando
Generated.array() (que se ha definido en el Captulo 16, Matrices) y Arrays.asList(). Slo dos de las pruebas pueden
realizarse en este caso, porque no se pueden insertar o eliminar elementos cuando se usa una li sta respaldada por una matriz,
as que se emplea el mtodo List.subList( ) para seleccionar las pruebas deseadas de entre la lista tests.
566 Piensa en Java
Para las operaciones get( ) y set( ) de acceso aleatorio, una lista ft'spaldada por una matriz es ligeramente ms rpida que
ArrayList. pero esas mismas operaciones son muchsimo ms caras para LinkedList porque este comenedor no est dise-
iado para operaciones de acceso aleawrio.
Es necesario evitar la utilizacin de V{'ctor: slo se ha incluido en la biblioteca para soporte del cdigo heredado (la nica
razn de que funcione en estc programa es porque fue ada piado para ser de lipa List por razones de compatibilidad Con
sucesivas versiones).
La mejor solucin consiste probablemente en elegir ArrayList como contenedor predetemlinado y cambiar a LinkedList
si hace falta su funcionalidad adicional o si descubre problemas de rendllniento debido a que se hacen mltiples inserciones
y eliminaciones en mitad de la lista. Si estamos trabajando con un grupo de elementos de tamao fijo, deberemos emplear
una li sta respaldada por una matriz (como las que produce Arrays.asList( )). en caso necesario, una verdadera matriz.
CopyOn\VritcArrayList es una implementacin especial de List que se uti li za en programacin concurrente y de la que
hablaremos en el Capinl10 21 . Concurrencia.
Ejercicio 29: (2) Modifique ListPerforrnance.java para que las listas almacenen objetos String en lugar de Integer.
Util ice el objeto Generator del Capitulo 16. Matrices, para crear valores de pmeba.
Ejercicio 30: (3) Compare el rendimiento de Collections.sort() en ArrayList y en LinkedList.
Ejercicio 31: (5) Cree un contenedor que encapsule una matriz de objetos String y que slo permita aadir y extraer
cadenas de caracteres. de modo que no sllljan problemas de proyeccin de tipos durante la utilizacin del
contenedor. Si la matriz no es lo suficientemente grande para la siguiente insercin. el contenedor debe
redimensionar automticamente la 1113tl;Z. En maine ), compare el rendimiento de este contenedor con el
de ArrayList<String>.
Ejercicio 32: (2) Repita el ejercicio ant erior para un comenedor de iut y compare el rendimient o con el de ArrayList
<Integer>. En la comparacin de rendimiento, incluya el proceso de incrementar cada objeto en el conte-
nedor.
Ejercicio 33: (5) Cree una li sta FastTraversalLinkedList que utilice internamente un contenedor LinkedList para
conseguir inserciones y eliminaciones rpidas de elementos y un contenedor ArrayList para realizar reco-
rridos rpidos de los elementos y operaciones get() rpidas. Pmebc la solucin moditic3ndo
ListPerformance.java.
Peligros asociados a las micropruebas de rendimiento
A la hora de escribir las denominadas micl'Opruebas de rendimiento, hay que tener cuidado de no dar demasiadas cosas por
supuesto y de enfocar las pruebas lo ms posible, de modo que slo se cronometren los elementos de inters. Tambin hay
que garantizar que las pruebas se ejecuten durante una cantidad de tiempo lo suficientemente grande como para producir
datos interesantes y hay que tener tambi n en cuenta que algunas de las tecnologas HotSpot de Java slo ent ran en accin
cuando un programa se ha estado ejecutando durante un determinado perodo de tiempo (tambin es impol1ante lener esto
en cuent a para los programas de corta duracin).
Los resultados sern distintos. dependiendo de la computadora y de la mquina JVM que estemos utilizando, por lo que con-
viene que ejecute estas pmebas por s mismo con el fin de verifi car que los resultados son simi lares a los que se muestran
en este libro. No deben preocuparle tanto los valores absolutos como las comparaciones de rendimiento entre un tipo de con-
tenedor y otro.
Asimismo, una herramienta de perfilado puede reali zar un mejor anlisis de rendimiento del que nosotros podemos llevar a
cabo. Java incluye un perfilador (consulte el suplemento en hllp://MindVie"wet/Books/BellerJava) y tambin hay perfil a-
dores de otros fabricantes, tanto graruitos/cdigo abierto como comerciales.
Un ejemplo relacionado es el que afecta a Math.random( ). Este mtodo produce un valor comprendido entre cero y uno,
pero eso incluye o excluye el valor" l "? En lenguaje matemtico, se trata del intervalo (O, 1), o [0.1]. o (0,1] o [0.1)0 (el
corchete indica ,oinclusin", mientras el parntesis indica 'ono inclusin"). Un programa de pmebas podra proporcionar la
respuesta:
11 : containers/RandomBounds.java
II Permite Math.random{) generar 0.0 y 1 . Q?
// {RUnByHand}
import static net.mindview.util.Print.*;
public class RandomBounds
static void usage{) {
print ("Usage : 11) ;
print("\tRandomBounds lower") i
print (" \tRandomBounds upper");
System.exit (l);
public static void main (Str ing [] args) {
if(args . length lo: 1 } usage();
if(args(O].equals("lower")) {
while(Math . random() != 0.01
; /1 Continuar intentndolo
print (11 Produced O. O! ") i
else if (args [O] . equals (lI upper") )
while(Math.random() != 1 . 0)
; // Continuar intentndolo
print ("Produced 1. O! 11);
el se
usage() ;
Para ejecutar el programa. hay que escribir la lnea de comandos:
java RandomBounds lower
o
java RandomBounds upper
17 Anlisis detallado de tos contenedores 567
En ambos casos. estamos obligados a intemlmpir la ejecucin del programa manualmente, por lo que podra parecer que
Math.random( ) nunca genera ni 0.0 ni 1.0. Pero es precisamente aqu donde este lipa de experimentos pueden resultar
engaosos. Si tenemos en cuenta que la cantidad de nmeros fraccionarios comprendidos entre O y 1 son lmas 262 fraccio-
nes diferentes. la probabilidad de obtener cualquiera de esos dos valores experimentalmente podra exceder el tiempo de
\'ida de ulla computadora, o incluso del propio experimentador. En realidad, 0.0 s que est inc:hddo en el rango de salida
de Math.raodom( ). O. dicho eo jerga matemtica. el int ervalo es [0.1 l. Por tanto, debemos tener cuidado a la hora de rea-
lizar nuestros experimentos con el fin de asegurarnos de que entendemos sus limitaciones.
Seleccin de un tipo de conjunto
Dependiendo del comportamiento que deseemos, podemos elegir entre TreeSet, HashSet o LiokcdHashSet. El siguieote
programa de pruebas proporciona una indicacin de los compromisos de rendimiento que afectan a estas implementacione;):
// : containers/SetPerformance.java
/1 Ilustra las diferencias de rendimiento entre conjuntos.
II {Args: 100 SOOO} pequeo para que las pruebas sean cortas
import java.util .*;
public class SetPerformance
sta tic List<Test<Set<Integer> tests =
new ArrayList<Test<Set<Integer>();
static {
tests. add (new Test<Set<Integer ( " add " )
int test (Set<Integer> set, TestParam tp)
int loops = tp.loops;
int size = tp.size;
568 Piensa en Java
}
} I ;
for(int i = O; i < loops; i++) {
set. clear () i
for(int j = o; j < size; j++)
set.addljl;
return loops * size;
tests.add(new Test<Set<Integer(lIcontains")
int test{Set<Integer> set, TestParam tp) {
int loops = tp.loops;
}
}I ;
int span = tp.size * 2;
for(int i = o; i < loops i++l
for(int j = O; j < span j++)
set. contains (j) ;
return loops * span;
tests .add (new '!'est<Set<Integer (" iterate") {
int test(Set<Integer> set, TestParam tp) {
int loops = tp.loops * la;
}
}I;
for(int i = O; i < loops i++) {
Iterator<Integer> it = set.iterator() j
while(it.hasNext{})
it.next() i
return loops * set.size();
public static void main (String [] args) {
if(args.length > O)
Tester.defaultParams = TestParam.array(args};
Tester.fieldWidth = 10;
Tester.run(new TreeSet<Integer>(), tests) i
Tester.run(new HashSet<Integer>(), tests);
Tester.run(new LinkedHashSet<Integer>(), tests);
1* Output: (Sample )
------------- TreeSet ------------ -
size add contains iterate
10 746 173 89
100 501 264 68
1000 714 410 69
10000 1975 552 69
------------- HashSet
-------------
size add contains iterate
10 308 91 94
100 178 75 73
1000 216 110 72
10000 711 215 100
---- ------ LinkedHashSet
----------
size add contains iterate
10 350 65 83
100 270 74 55
1000 303 111 54
10000 1615 256 58
*/// ,-
17 Anlisis detall ado de los contenedores 569
El rendimjento de Has hSet generalmente es superior al de TreeSet, pero especialmente a la hora de aadir elementos y de
buscarlos, que son las dos operaciones ms importantes. TreeSet existe porque mantiene sus elementos en orden, de modo
que slo se suele utili zar cuando hace falta un contenedor Set ordenado. Debido a la estructura interna necesaria para sopor-
tar las ordenaci ones y debido a que la iteracin suele ser una operacin muy comn, la iteracin suele resultar ms rpida
con TreeSel que con HashSet.
Observe que LinkcdHashSet es ms caro respecto a las inserciones que HashSet; eslO se debe al coste adicional de man-
tener la lista enlazada adems del contenedor hash.
Ejercicio 34: ( 1) Modifique SelPerformance.java para que los conjuntos abnacenen objetos Slring en lugar de obje.
tos Inleger. Utilice el objeto Gener ator del Capitulo 16, Matrices para crear valores de prueba.
Seleccin de un tipo de mapa
Este programa nos proporc iona una indicacin de los compromisos de rendi miento en las distintas implementaciones de
Map:
1/: containers/ MapPerformance.java
// Ilustra la diferencias de rendimiento entre mapas.
// {Args: 100 SOOO} pequeo para mantener corto el tiempo de prueba
import java.util. *
public class MapPerformance
sta tic ListcTestcMapclnteger,Integer> tests =
new ArrayListcTestcMapclnteger,Integer>{ ) ;
static {
tests.add (new TestcMapclnteger,Integer ( lIput" )
int test (Mapclnteger,Integer> map, TestParam tpl
int loops = tp.loops;
)
) 1 ;
int size = tp.size
for(int i = O; i c loops; i++)
map.clear(} ;
for{int j = O; j <: size j++)
map.put ( j. j ) ;
return loops * size
tests. add {new Test<Map<Integer, Integer ( "get 11 )
int test {Map<Integer, Integer> map, TestParam tp)
int loops = tp.loops;
)
)) ;
int span = tp . size * 2;
for ( int i = O; i < loops; i++ )
for ( int j = O; j < span; j++)
map.get ( j ) ;
return loops * span;
tests. add {new Test<Map<Integer, Integer (" iterate") {
int test{Map<Integer,Integer> map, TestParam tp) {
int loops = tp.loops * 10;
)
) ) ;
for (int i = O; i < loops; i ++)
Iterator it = map.entrySet ( ) . iterator ( )
while(it.hasNext())
it . next ()
return loops * map.size();
570 Piensa en Java
public static void main (String [] args) {
if(args.length > O)
Tester.defaultParams = TestParam , array (args) ;
Tester.run(new TreeMap<Integer,Integer>{), tests);
Tester.run(new HashMap<Integer,Integer>(}, tests);
Tester.run(new LinkedHashMap< I nteger, Integer> () ,tests);
Tester.run(
new IdentityHashMap<Integer,Integer>() I tests) i
Tester , run(new WeakHashMap<Integer,Integer>{), tests);
Tester.run(new Hashtable<Integer,Integer>(), tests) i
/ * Output: (Sample)
--- - ------ TreeMap --- -- -----
size put get iterate
10 748 168 100
100 506 264 76
1000 771 4 50 78
10000 2962 561 83
--- -- ----- HashMap --- --- ----
size put get iterate
10 281 76 93
100 179 70 73
1000 267 102 72
10000 1305 265 97
--- -- - - LinkedHashMap ----- --
size put ge t iterate
10 354 100 72
100 273 89 50
1000 385 222 56
10000 2787 341 56
IdentityHashMap
size put get iterate
10 290 144 101
100 204 287 132
1000 508 336 77
10000 767 266 56
-------- WeakHashMap - - --- - --
size put get iterate
10 48 4 146 151
100 292 126 117
1000 411 136 152
10000 2165 138 555
------ - -- Hasht able -- - - - ----
s i ze put get iterate
10 264 113 113
100 181 105 76
1 000 260 201 80
10000 1245 134 77
* /// ,-
Las inserciones en todas las implementaciones de Map excepto en IdentityHashMap van siendo significativamente ms
lentas a medida que el tamao del mapa se incrementa. Sin embargo, en general, la bsqueda es mucho menos costosa que
la insercin, lo cual resulta muy adecuado, porque lo normal es que busquemos elementos con Illucha ms frecuencia de la
que los insertamos.
El rendimiento de Haslttable es aproximadamente igual al de Ha,ltMap. Puesto que Ha,hMap pretende sust itui r a
Hashtable, y utiliza por tanto la misma estmctura subyacente de almacenamiento y el mismo mecanismo de bsqueda (de
lo que hablaremos posterionnente), no resulta demasiado sorprendente que tenga un rendimiento mejor.
17 Analisis detallado de los contenedores 571
TreeMap es general mente mas ICllI o que HashMap. Al igua l que sucede con TreeSet , TreeMap es una foml a de crear una
lista ordenada. El componamiento de un rbol es tal que sus elementos siempre estn en orden, sin que sea necesario orde-
narlos especialmente. Una vez rell enado un contenedor TreeMap. podemos invocar keySet() para obtener una vista de tipo
Set de las c1a\'cs y luego podemos ll amar a toArray( ) para generar una matriz de dichas claves. Podemos a continuacin
emplear un mtodo esttico Arrays.binarySearch() para localizar rpidamente objetos en esa matriz ordenada. Por supues-
to. esto slo tiene semido si el comportamicmo de un contenedor HashMap no es aceptable, ya que HashMap est disea-
do para poder localizar rpidamente las claves. Asimismo, podemos crear rpidamente un contenedor HashMap a partir de
otro de tipo TreeMap mediante una nica creacin de objeto o una ll amada a putAII(). En resumen, cuando estemos utili-
zando un mapa nuestra primera eleccin deber ser HashMap, y slo deberamos recurrir a TreeMap si lo que necesitamos
es un mapa que est constantemente ordenado.
LinkedHas h.Map tiende a ser ms lento que l-IashMap para las inserciones. porque mantiene la li sta enlazada (con el fin
de preservar el orden de insercin), adems de mant ener la estructura de datos hash. Sin embargo. debido a esta li sta, la ite-
racin es ms rpida.
ldentit)' l:JashMap tiene un rendimi ento di sti nto porque utiliza = en lugar de equals( ) para hacer las comparaciones.
WeakHashMap se describe ms adelante en el capitulo.
Ejercicio 35: (1) Modifique MapPerformance.java para incluir pruebas de SlowMap.
Ejercicio 36: (5) Modifique SlowMap de modo que, en lugar de dos contenedores ArrayList, almacene un nico
ArrayList de objetos MapEntry. Verifique que la versin modificada funciona correctamente. Utilizando
MapPerformance.java, compruebe la velocidad de su nuevo mapa. Ahora cambie el mtodo put( ) de
modo que realice con sort() una ordenacin despus de introducir cada parej a y modifique get() para uti-
lizar Collections.binarySearch() con el fin de buscar la clave. Compare el rendimiento de la nueva ver-
sin con el de las anteriores.
Ejercicio 37: (2) Modifique SimpleHashMap para utili zar contenedores ArrayList en lugar de LinkedList. Modifique
MapPerrormancc.ja\'3 para comparar el rendimiento de las dos impl ementaciones.
Factores de rendimiento que afectan a HashMap
Resulta posible optimizar manualmente un contenedor HashMap para incrementar su rendimiento de cara a una apli cacin
concreta. Pero para poder comprender las cuestiones de rendimiento a la hora de optimi zar un contenedor HashMap, 110S
hace falta algo de tenninologa:
Capacidad: el nmero de segmentos de la tabla.
Capacidad inicial: el nmero de segmentos en el momento de crear la tabla. HashMap y HashSet tienen cons-
tnlclores que penniten especi fi car la capacidad inicial.
TalllUlio: el nmero de entradas que hay actualmente en la tabla.
Factor de carga: Tamao/capacidad. Un faclor de carga de O representa una tabla vaca, 0.5 es una tabla medi o
llena, etc. Una tabla Ligeramente cargada tendr menos colisiones y por tanto resulta ptima de cara a las inser-
ciones y bsquedas (aunque ralentizar el proceso de recorrer el contenedor con un iterador) l-IashMap y
HashSet tienen constructores que pemliten especificar el factor de carga, lo que significa que cuando se alcanza
este factor de carga, el contenedor incrementar automticamente la capacidad (el nmero de segmentos), por el
procedimi ento de aproximadamente duplicar dicho nmero y luego redistribuir los objetos existentes en el nuevo
conjunto de segmentos (este proceso se denomina rehashing).
El factor de carga predetenninado utilizado por HashMap es 0.75 (no efecla una redi stribucin hasta que la tabla est llena
en sus tres cuartas partes). ste parece ser un buen compromiso entre los costes de tiempo y de espacio de almacenamien-
to. Un factor de carga ms alto reduce el espacio requerido por la tabla pero incrementa el coste de bsqueda, lo cual es
importante. porque la mayor parte del tiempo es bsquedas (incluyendo tanto get() como put( )).
Si sabemos de antemano que vamos a almacenar numerosas entradas en un contenedor HashMap, crearlo con una capaci-
dad inicial suficientemente grande evitar el gasto adicional del cambio de tamao automtico.
11
11 En tln mensaje privado, Joshua Bloch escribi: ..... Creo que hemos cometido un error al incluir detalles de implementacin (como el factor de carga y
el tamao de las tablas hash) en nuestras API . El cliente debera. quiz. decirnos el tamao mximo esperado de una coleccin y nosotros deberamos actuar
572 Piensa en Java
Ejercicio 38: (3) Examine la clase HashMap de la documentacin del JDK. Cree un contenedor HashMap, rell nelo
con elementos y determine el factor de carga. Pruebe la velocidad de bsqueda con este mapa, luego trate
de incrementar la velocidad creando un nuevo contenedor HasbMap con una capacidad inicial mayor y
copiando el mapa antiguo en el nuevo; despus, ejecute otra vez la prueba de velocidad de bsqueda con
el nuevo mapa.
Ejercicio 39: (6) Aada un mtodo privado rehash() a SimpleHashMap que se invoque cuando el factor de carga exce-
da de 0.75. Durante el cambio automtico de tamao, duplique el nmero de segmentos y luego busque el
primer nmero primo superior a ese nmero. con el fin de determinar el nuevo nmero de segmentos.
Utilidades
Hay diversas utilidades autnomas para contenedores, expresadas como mtodos estaUcos dentro de la clase
java.util.Collections. Ya hemos visto algunas de ellas, como addAII( ), reverseOrder( ) y binarySearch( ). He aqu las
otras (las utilidades de tipo synchronized y unmodifiable se cubrirn en las secciones siguientes). En esta tabla, se usan
genricos all donde son relevantes:
checkedCollection(Collection<T>, Produce una vista dinmicamente segura con respecto 1 tipos de Collection,
Class<T> type) o de un subtipo especfico de Collection. Utilice este mtodo cuando no sea
checkedList(List<r>, Class<T> type) posible emplear la versin con comprobacin esuitica. Ya hemos visto estos
checkedMap(Map<K, V>, mtodos en el Capitulo 15, Genricos, en la seccin "Seguridad dinmica de
Class<K> keyType, Class<V> valucTypc) tipos".
checkedSet(Set<T>, Class<T> I)'pe)
checkedSortcdMap(Sorted Map<K, V>,
Class<K> keyType, Class<V>
valueType)checkedSortedSet(SorledSet<T>,
Class<T> type)
max(Collection) Devuelve el elemento mximo o mnimo contenido en el argumento utilizan-
rnin(Col1eetion) do el mtodo de comparacin natural de los objetos de la coleccin.
max(Collection, Comparator) Devuelve el elemento mximo o mnimo del objeto Colleerion utilizando el
min(Collection, Comparator) objeto Comparator.
indexOrsubList(List SOllree, List (arget) Devuelve el ndice de inicio del primer lugar donde target aparece dentro de
so u ree, o 21 si no aparece.
lastlndexOfSubLi sl(List source, List targct) Devuel ve el ndice de inicio del/timo lugar donde target aparece dentro de
source, o 21 si no aparece.
replaceAII(List<T>, Reemplaza todos los valores oldVal por newVal .
T oldVal, T ncwVal)
re\'erse(List) Invierte el orden de IOdos los elementos en la li sta.
reverseOrder( ) Devuelve un objeto Comparator que invierte la ordenacin natural de una
re\'crseOrder(Comparator<T coleccin de objetos que implemente Comparable<T>. La segunda versin
invierte el orden del objeto Comparator suministrado.
rotate(List, inl distanee) Mueve todos los elementos hacia adelante una distancia distance, extrayn-
dolos por el extremo y volviendo a colocarlos al principi o.
II(cOlllimwcilI) a panir de ahi. Los clientes pueden, fcilmente, generar ms problemas que beneficios al seleccionar los valores de estos parmetros.
Como ejemplo exagerado, considera el valor eapacitylDcrement de V<,ctor. Nadie debera configurar este valor y no deberamos haberlo incluido. Si se
le asigna un valor distinto de cero. el coste asinttico de una secuencia de adiciones pasa de lineal a cuadrtico. En Olras palabras, el rendimiento se viene
abajo. Con el paso del tiempo, carla vez tenemos ms experiencia con este tipo de cosa. Si examinas IdentityHashMap, vers que DO dispone de ningn
parmetro de optimizacin de bajo nivel".
17 Anlisis detall ado de los contenedores 573
shuffle(List) Pennuta de manera aleatoria la lista especificada. La primera forma propor-
shuffle(Lisl, Random) ciona su propio mecanismo de aleatorizacin, o bien podemos proporcionar
nuestro propio mecanismo con la segunda fonna.
sort (List<T sort(List<T>, Ordena la lista List<T> utilizando una ordenacin natural.
La segunda forma
Comparator<? super T> e) permite proporcionar un objeto Comparator para la ordenacin.
copy(Li st<? super T> desl, Copia elementos desde src a des t.
List<? ext ends T> src)
swap(Li st, inl i , inl j) Intercambios los elementos en las posiciones i y j dentro de List .
Probablemente ms rpido que los mtodos que pudiramos escribir no-
sotros.
fill (List<? super T>, T x) Sustituye todos los elementos de la lista por x.
nCopies(int n, T x) Devuelve una lista inmutable List<T> de tamaiio n cuyas referencias p u n ~
tan todas a x.
disj oint(Coll ection, Col1ecti on) Devuelve true si las dos colecciones no tienen elementos en comn.
frequency(Coll ection, Object x) Devuelve el nmero de elementos de Coll ection que sean iguales a x.
emptyList ( ) Devuelve un objeto List. Map o Set vaco inmutable. Son objetos genricos,
emlltyMap( ) emptySetO por lo que el objeto Collection resultante estar parametrizado con el tipo
deseado.
singleton(T x) Produce un objeto Sct<T>, List<T>, o Map<K,V> inmutable que contiene
si ngletonList(T x) una nica entrada basada en los argumentos proporcionados.
singletonMap( K kcy, V value)
list(Enumeration<T> e) Produce Wl objeto ArrayList<T> que contiene los elementos en el orden en
el que son devueltos por el (antiguo) objeto Enumeration (predecesor de
Iter at or). Para la conversin de cdigo heredado.
enumerati on(Coll ection<T Genera un objeto Enumeration<T> de estilo antiguo para el argumento.
Observe que min() y max( ) funcionan con objetos Collection no con listas, por lo que no es necesario preocuparse acer-
ca de si la coleccin ya est ordenada o no (como ya hemos mencionado anteriormente, s que es necesario ordenar con
sort() una lista o una matriz antes de realizar una bsqueda binaria con binarySea rch( .
He aqu un ejemplo que muestra el uso bsico de la mayora de las utilidades de la tabla anterior:
JJ : containers / Utilities.java
JJ Ejemplo simple de las utilidades para colecciones.
import java.util.*i
import static net.mindview.util.Print.*
public class Utilities {
static List<String> list = Arrays.asList {
Ilone Two three Four five six one".split ( 1I " i
public static void main (String(] args ) {
print (list ) i
print (" I list I disjoint (Four)?: 11 +
Collections.disjoint {list,
Collections.singletonList("Four" ) ) i
print ( "max: " + Collections. max (list ) } i
print("min: " + Collections.min(list i
print(" max w/ comparator: 11 + CollectionS.max (list,
574 Piensa en Java
String.CASE_"NSENS"TIVE_ORDER ;
print ("min w/ comparator: " + Collections. min (list,
String.CASE_INSENSITIVE_ORDER ;
sublist =
Arrays,asList("Four five six".split{" ";
print (I! indexOfSubList: lO +
Collections.indexOfSubList(list, sublist;
print (" lastlndexOfSubList: 11 +
Collections.lastlndexOfSubList(list, sublist;
Collections.replaceAll(list, "ane", "Yo");
print(UreplaceAll: " + list);
Collections.reverse{list) ;
print(ttreverse: " + list);
Collections.rotate(list, 3);
print ("rotate: " + list);
List<String> source =
Arrays.asList{lIin the matrix",split(U 11;
Collections.copy(list, source);
print{"copy: " + list);
Collections. swap (list, O, list.size() - 1);
print {"swap: 11 + list )
Collections.shuffle(list, new Random(47;
print("shuffled: 11 + list)
Collections. fill (list, "pOp");
print{lifill: 11 + list)
print{Ufrequency of 'pop': " +
Collections.frequency(list, "pOp";
List<String> dups = Collections.nCopies(3, IIsnapU);
print("dups: n + dups)
print (11 'list' disjoint 'dups'?: 11 +
Collections.disjoint(list, dups;
11 Obtencin de un objeto Enumeration al estile antiguo:
Enumeration<String> e = Collections.enumeration(dups);
Vector<String> v = new Vector<String>()
while{e.hasMoreElements(
v.addElement(e.nextElement( ;
1/ Conversin de un vector de estilo antiguo
11 en una lista a travs de un objeto Enumeration:
ArrayList<String> arrayList =
Collections .list (v. elements () ) ;
print ( " arrayList: 11 + arrayList);
/ * Output:
[one, Two, three, Four, five, six, one)
t list t disjoint (Four)?: false
max: three
min: Four
max wl comparator : Two
min wl comparator: five
indexOfSubList: 3
lastlndexOfSubList : 3
replaceAll: [Yo, Two, three, Four, five, six, Yo]
reverse: [Yo, six, five, Four, three, Two, Yo]
rotate: [three, Two, Yo, Yo, six, five, Four]
copy: (in, the, matrix, Yo, six, five, Four)
swap: [Four, the, matrix, Yo, six, five, in]
shuffled: [six, matrix, the, Four, Yo, five, in]
till, (pop, pop, pop, pop, pop, pop, pop)
frequency of 'pop': 7
dups: [snap, snap, snapl
'list' disjoint 'dups'?: true
arrayList: (snap, snap, snap]
* jjj ,-
17 Anlisis detallado de los contenedores 575
La salida explica el comportamiento de cada mtodo de utilidad. Observe la diferencia en min( ) y max( ) con el objeto
String.CASE_INSENSITIVE_ORDER Compar.tor debido a la utilizacin de maysculas y minsculas.
Ordenaciones y bsquedas en las listas
Las utilidades para realizar ordenaciones y bsquedas en las li stas tienen los mismos nombres y signaturas que se emplean
para ordenar matrices de objetos, pero se trata de mtodos estticos de Collections en lugar de ser mtodos de Arrays. He
aqu un ejemplo que emplea la lista de datos list de Utilities.java:
JI: containers/ ListSortSearch . java
JI Ordenacin y bsqueda en listas con las utilidades de Co!lections.
import java.util.*
import static net.mindview.util.Print .*
public class ListSortSearch {
public static void main(String[] args)
List<String> list =
new ArrayList<String> (Utilities.list) i
list.addAll(Utilities.list) i
print(list) i
Callections.shuffle(list, new Random(47 i
print("Shuffled: " + list)
II Utilizar Listlterator para eliminar los ltimos elementos:
Listlterator<String> it = list.listlterator {lO);
while (i t. hasNext ()) (
it. next () ;
it . remove()
print ("Trimmed : " + list) i
Colleccions.sort(listl
print{"Sorted: " + list) i
String key = list . get(7)
int index = Collections.binarySearch(list, key)
print ("Location of " + key + " is " + index +
", list.get(tI + index + ti ) = " + list.get{index
Collections.sort(list, String.CASE_INSENSITIVE_ORDERl
print ("Case-insensitive sorted: " + list) i
key = list.get(7);
index = Collections . binarySearch{list, key,
String.CASE_INSENSITIVE_ORDER) ;
print ("Location of " + key + is It + index +
", list.get(II + index + ") = " + list.get(index
1* Output:
[one, Two, three, Four, five, six, one, ane, Two, three,
Four, five, six, one]
Shuffled: [Four, five, one, one, Two, six, six, three,
three, five, Four, Two, ane, ane]
Trimmed : (Four, five, one, one, Two, six, six, three,
three, five]
Sorted: [Four, Two, five, five, one, one, six, six, three,
three]
Location of six is 7, list.get(7) = six
Case-insensitive sorted: [five, five, Faur, ane, ane, six,
576 Piensa en Java
six, three, three, Two]
Location of three is 7, lisc.get ( 7) = three
*// / , -
Al igual que cuando se reali zan bsquedas y ordenaciones con matri ces, si ordenamos utili zando un obj eto Comparator,
debemos efectuar la bsqueda con binarySearch( ) usando el mi smo objeto Comparalor.
Este programa tambi n ilustra el mtodo shuftle( ) de Coll eclions, que aleatoriza el orden de una li sta. Se crea un obj eto
Listlterator en una posicin concreta de la li sta al eatori zada y se utili za para eliminar los elementos comprendidos entre
dicha posicin y el final de la li sta.
Ejercicio 40: (5) Cree una clase que contenga dos objetos Slring y haga que sea de tipo Comparable de modo que la
comparacin slo tenga en cuenta el primer obj eto St ring. Rellene una matriz y un contenedor ArrayList
con obj etos de esa clase, utilizando el generador RandomGenerator. Demuestre que la ordenaci n fun-
ciona apropiadamente. Ahora defina un objeto Comparator que slo tenga en cuenta el segundo objeto
String y demuestre que la ordenacin funciona correctamente. Asimi smo, realice una bsqueda binaria
utitizando ese objeto Comparator.
Ejercicio 41: (3) Modifique la clase del ejerci cio anterior para que funcione con contenedores HashSel y como clave
en contenedores HashMap.
Ejercicio 42: (2) Modifique el Ej ercicio 40 para que se utili ce una ordenacin alfabtica.
Creacin de colecciones o mapas no modificables
A menudo, resulta conveniente crear una versin de slo lectura de una coleccin o mapa. La clase Collections pernlite
hacer esto, pasando el contenedor original a un mtodo que devuelve una versin de slo lectura. Hay diversas variantes de
este mtodo, para col ecciones (si no se puede tratar un objeto Collection como si fuera de tipo ms especfi co), li stas, con-
juntos y mapas. El siguiente ejemplo muestra la fonna de construir versiones de slo lectura de cada uno de estos contene-
dores:
// : containers/ReadOnly . java
// Utilizacin de los mtodos Collections . unmodifiable.
import java . util. * ;
import net.mindview.util .*
import static net.mindview. util.Print. *
public class ReadOnly {
static Collection<String> data =
new ArrayList<String> (Countries.names (6 )) i
public s t atic void main (String[] args ) {
COllection<String> c =
Collections.unmodifiableCollection (
new ArrayList<String> (data ))
print (c ) i // Se puede leer
// ! c. add ( "one" ) i // No se puede modificar
List <String> a = Collections.unmodifiableList {
new ArrayLi st<String> (data) ) ;
Listlterator<String> lit = a.listlterator {)
print(lit . next{) ) ; // Se puede leer
j i! lit.add ( "one" ) i // No se puede modificar
Set<St ring> s = Col l ections.unmodifiableSet (
new HashSet<String>{data));
print(s ) ; // Se puede leer
ji ! s . add {"one " ) ; // No se puede modificar
// Para un contenedor SortedSet :
Set<String> ss = Collections.unmodifiableSortedSet (
17 Anl isis detallado de los contenedores 577
new TreeSet<String> (data) );
Map<String,String> ro = Collections . unmodifiableMap(
new HashMap<String,String>(Countries . capitals(6))) i
print(m); // Se puede leer
JI! m.put("Ralph", "Howdy!") i
JI Para un contenedor SortedMap:
Map<String,String> sm =
Collections.unmodifiableSort edMap(
new TreeMap<String,String>(Countries.capitals{6)));
/ * Output:
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASOI
ALGERIA
[BULGARIA, BURKINA FASO, BOTSWANA, BENIN, ANGOLA, ALGERIAI
{BULGARIA=Sofia, BURKINA FASO=Ouagadougou, BOTSWANA=Gaberone,
BENIN=Porto-Novo, ANGOLA=Luanda, ALGERIA=Algiers}
* ///,-
La invocacin del mtodo "unmodifiable" (no modifi cable) para un tipo concreto no hace que se generen advertencias en
tiempo de compil acin, pero una vez que la transfonnacin se ha producido, cualquier llamada a un mtodo que modifique
el contenido de un contenedor concreto generar una excepcin U nsupportedOperationException.
En cada caso, debe rell enar el contenedor con datos significati vos antes de hacerlo de slo lectura. Una vez cargado, lo mej or
es susti tuir la referencia existente por la referencia generada por la llamada al mtodo "unmodifiable". De esa fonna, no
corremos el riesgo de tratar de modificar accidentalmente el contenido una vez que hemos dicho que no es modifi cable. Por
otro lado, esta herrami enta tambin pennite mantener el contenedor modificable como pri vado dentro de una clase y devol-
ver una referencia de slo lectura a di cho contenedor desde una llamada a mtodo. De este modo, podemos cambiar el con-
tenedor dentro de la clase, pero desde cualquier otro lugar slo podr leerse.
Sincronizacin de una coleccin o un mapa
La palabra clave synchronized es una parte importante del tema de la programacin multihebra, un tema ms compli cado
del que no habl aremos hasta el Capnilo 21, Concurrencia. Aqu. nos limaremos a resaltar que la clase Collections contie-
ne una fonna de sincronizar aut omticamente un contenedor completo. La sintaxis es similar a la de los mtodos ' \mmodi-
fi able":
jj: containersjSynchronization.java
jj Utilizacin de los mtodos Collections.synchronized.
import java . util.*
public class Synchronization {
public static void main(String[] args) {
Collection<String> c =
Collections . synchronizedCollection{
new ArrayList<String>{)) i
List<String> list = Collections . synchronizedList{
new ArrayList<String>())
Set<String> s = Collect i ons.synchronizedSet(
new HashSet<String>()) i
Set<String> ss = Collections . synchronizedSortedSet(
new TreeSet<String>{)) i
Map<String,String> m = Collections.synchronizedMap(
new HashMap<String,String>{)) i
Map<String,String> sm =
Collections . synchronizedSortedMap{
new TreeMap<String , String>()) i
578 Piensa en Java
Lo mejor es pasar inmediatamente el nuevo contenedor a travs del apropiado mtodo synchronized'o. como se muestra en
el ejemplo. De esa forma. no existe ninguna posibilidad de que la versin no sincronizada quede accidentalmente expuesta.
Fallo rpido
Los contenedores de Java tambin tienen un mecanismo para evitar que ms de un proceso modifique el contenido de un
contenedor. El problema se presenta si estamos en mitad de un proceso de iteracin a travs de un contenedor y entonces
algn otro proceso interviene e inserta, modifica o elimina un objeto de dicho contenedor. Puede que ya hayamos pasado
dicho elemento del contenedor, O puede que todava no hayamos llegado a ese elemento, puede que el tamao del contene-
dor se reduzca despus de que hayamos invocado size( ) ... existen muchas posibilidades de que se produzca un desastre. La
biblioteca de contenedores de Java utiliza un mecanismo de fallo rpido que exami na si se ha producido en el contenedor
algn cambio distinto de aquellos de los que nuestro proceso es personalmente responsable. Si detecta que alguien ms est
modificando el contenedor. genera inmediatamente una excepcin ConcurrentJ\llodificationException. A eso se refiere el
trmino de fallo rpido: no trata de detectar un probl ema ms adelante utilizando un algoritmo ms complejo.
Resulta bastant e fcil ver el mecani smo de fallo rpido en accin: lo nico que hace falta es crear un iterador y luego aa-
dir algo a la coleccin a la que el iterador est apuntando, como el ejemplo siguient e:
jI : containers/ FailFast.java
II Ilustracin del comportamiento del 11 fallo rpida".
import java.util.*
public class FailFast
public static void main (String [J args} {
Collection<String> e = new ArrayList<String> () ;
Iterator<String> it = c . iterator () ;
c.add ( "An object" }
try {
String s it . next();
catch(ConcurrentModificationException e} {
System.out.println (e } ;
j* Output:
java.util.ConcurrentModificationException
*jjj,-
La excepcin se produce porque se ha incl uido algo en el contenedor despus de que se haya adquirido el iterador para ese
contenedor. La posibilidad de que dos partes del programa puedan modificar el mi smo contenedor hace que acabemos en
un estado incierto, por lo que la excepcin nos notifica que es necesario modificar el cdigo; en este caso, habr que adqui-
rir el terador despus de haber aadido todos los elementos al contenedor.
Los contenedores COllcurrentHashMap, CopyOnWriteArrayList y CopyOnWriteArraySet utilizan tcni cas que impi-
den que se genere la excepcin ConcurrentModficationException.
Almacenamiento de referencias
La biblioteca java.lang.ref contiene un conjunto de clases que aumentan la flexibilidad del mecani smo de depuracin de
memoria. Estas clases son especialmente tiles cuando tenemos objetos de gran tamao que puedan hacer que la memoria
se agote. Existen tres clases heredadas de la clase abstracta Reference: SoftReference, WeakReference y
PhantomReference. Cada una de ellas proporciona un nivel distinto de indireccin para el depurador de memoria si el obje-
to en cuestin slo es alcanzable a travs de uno de estos objetos Reference.
Si un objeto es alcanoable, quiere decir que en algn lugar del programa puede encontrarse el objeto. Esto puede querer
decir que disponemos de una referencia nonual en la pila que apunta directamente al objeto, pero tambin podemos tener
una referencia a un objeto que tenga a su vez una referencia al objeto en cuestin; puede incluso haber muchos enlaces inter-
medios. Si un objeto es alcanzable, el depurador de memoria no puede ignorarlo, porque sigue siendo utilizado por el pro
grama. Si el objeto no es alcanzable, no hay ninguna fonna de que nuestTo programa lo use, as que resulta seguro depurar
dicho objeto de la memoria.
17 Anlisis detallado de los contenedores 579
Utilizamos objetos Reference cuando queremos continuar almacenando una referencia al objeto (es decir, queremos poder
alcanzar el objeto), pero tambin querernos pennitir que el depurador de memoria libere el objeto. De este modo, tenemos
una fanTIa de utilizar el objeto. pero si est a punto de agotarse la memoria, dejamos que ese objeto sea eliminado.
Para hacer esto, utilizamos un objeto Reference como intemlediario (pro.\y) entre nosotros y la referencia normal. Adems,
no debe haber referencias nomlales al objeto (es decir. referencias que no estn envueltas dentro de objetos de tipo
Reference). Si el depurador de memoria descubre que un objeto es alcanzable a travs de una referencia normal. no podr
liberar dicho objeto.
Ordenando los distintos tipos de referencias SoftReference, WeakReference y PhantomReference, cada uno de ellos es
"ms dbil " que el anterior y se corresponde con un nivel distinto de alcanzabilidad. Las referencias blandas (50ft referen-
ces) son para implementar cachs sensibles a la memoria. Las referencias dbiles (weak references) sirven para implemen-
tar "mapas cannicos" (con los que pueden usarse instancias de objetos simultneamente en mltiples lugares de un
programa, con el fin de ahorrar espacio de almacenamiento) que no impiden que sus claves o valores sean eliminados. Las
referencias fantasma (phanlom relerences) sirven para planificar acciones de limpieza pre-mortem de una manera ms fle-
xible de lo que puede conseguirse con el mecanismo de flllalizacin de Java.
Con SoftReference y \VeakReference, tenemos la opcin de situar esas referencias en una cola de referencias de tipo
ReferenceQucue (que se utiliza para acciones de limpieza pre-mortem). pero una referencia PhantornReference tiene obli-
gatoriamente que construirse dentro de una cola ReferenceQueue. He aqu un ejemplo simple:
11: containers/References.java
II Ejemplo de objetos Reference
import java. lang.ref.*i
import java.util.*;
class VeryBig {
private static final int SIZE = 10000;
private long [] la = new long [SIZE] ;
private String ident;
public VeryBig (String id) { ident
public String toString () { return
protected void finalize() {
id; )
ident; }
System.out.println(!!Finalizing !! + ident);
public class References {
private static ReferenceQueue<VeryBig> rq
new ReferenceQueue<VeryBig>()
public static void checkQueue () {
Reference<? extends VeryBig> inq = rq.poll()
if(inq !::; null)
System.out.println(ltln queue: " + inq.get());
public static void main(String[] args)
int size = 10;
II O bien elegir el tamao a travs de la lnea de comandos:
if(args.length > O)
size = new Integer(args[Ol};
LinkedList<SoftReference<VeryBig sa =
new LinkedList<SoftReference<VeryBig() i
for{int i = Di i < size; i++) {
sa.add(new SoftReference<VeryBig>{
new VeryBig(ItSoft 11 + i) ,rq))
System.out.println("Just created: 11 + sa.getLast())
checkQueue{) ;
LinkedList<WeakReference<VeryBig wa =
new LinkedList<WeakReference<VeryBig();
580 Piensa en Java
far(int i = o; i < size i++} {
wa.add(new WeakReference<VeryBig>(
new VeryBig ("Weak " + i), rq));
System. out. println ("Just created: 11 + wa. getLast () ) i
checkQueue() ;
SoftReference<VeryBig> s =
new SoftReference<VeryBig> (new VeryBig ( IISoft ji) ) ;
WeakReference<VeryBig> w =
new WeakReference<VeryBig> (new VeryBig ( "Weak
H
) ;
System.gcll;
LinkedLisc<PhantomReference<VeryBig pa =
new LinkedLisc<PhantomReference<VeryBig() i
far(int i = o; i <: size i++) {
pa.add(new PhantomReference<VeryBig>(
new VeryBig ( " Phantom " + i), rq});
System. out.println(IIJust created: " + pa.getLast());
checkQueue() ;
/ * (Execute to see output) * /// :-
Cuando se ejecuta este programa (conviene redirigir la salida a un archivo de texto para poder ver la salida pgina a
pgina), podemos ver que los objetos se depuran de la memoria, an cuando seguimos teniendo acceso a ellos a travs del
objeto Reference (para obtener la referencia real al objeto se utili za get( . Tambin podemos ver que el objeto
RefercnccQueuc siempre genera un objeto Reference que contiene un objeto nuLl . Para usar ste, herede de una clase
Referencc concreta y aliada otros mtodos ms ti les a la nueva clase.
WeakHashMap
La biblioteca de contenedores di spone de un tipo especial de mapa para almacenar referencias dbiles WeakHashMap. Esta
clase est di scliada para facilitar la creacin de mapas cannicos. En dicho tipo de mapas se ahorra espacio de almacena-
miento creando una instancia de cada valor concreto. Cuando el programa necesita dicho valor, busca el objeto existente en
el mapa y lo utiliza (en lugar de crear uno parti endo de cero). El mapa puede construir los valores como parte de su proce-
so de inicializacin, pero lo ms probable es que los va lores se constmyan a medida que son necesarios.
Puesto que se trata de una tcni ca de ahorro de espacio de almacenamiento, resulta bastante til que WeakHashMap per-
mita al depurador de memoria limpiar automticamente las claves y los valores. No hace fa lta hacer nada especial con las
claves y valores que se incluyan en el contenedor Wea kHas hMap; dichas claves y valores son envueltos automticamente
por el mapa en referencias de tipo WeakReference. Lo que hace que quede permitida la tarea de limpi eza del depurador de
memoria es que la clave ya no est siendo utili zada, como se ilustra en el siguiente ejemplo:
11: containers/CanonicalMapping.java
II Ilustra el contenedor WeakHashMap .
import java.util.*
class Element {
private String ident
public Element (String id) { ident = id; }
public String toString() { return ident; }
public int hashCode () { return ident .hashCode ();
public boolean equals (Obj ect r) {
return r instanceof Element &&
ident . equals ( ( (Element ) r) . ident)
protected void f inalize () {
System. out. println ( " Finalizing
getClass() .getSimpleName {) +
+
+ ident)
class Key extends Element
public Key (String id) { super (id) i
class Value extends Element
public Value (String id) super (id) i
public class CanonicalMapping {
public static void main(String[] args)
int size = 1000;
17 Anlisis detallado de los contenedores 581
// O bien elegir tamao a travs de la lnea de comandos:
if(args . length > O)
size = new Integer(args[O]) i
Key(] keys = new Key[size);
WeakHashMap<Key,Value> map =
new WeakHashMap<Key,Value> () i
for {int i = O; i < size i++} {
Key k = new Key(Integer.toString(i)} i
Value v = new Value {Integer.toString ( i i
if (i % 3 == O)
keys [i ) = k; /1 Guardar como referencias "reales"
map.put(k, v);
System.gc() i
/ * (Execute to see outputl */ // :-
La clase Key debe tener sendos mtodos hashCode( ) y equals(), puesto que est siendo usada como clave en una estruc-
tura de datos hash. Ya hemos hablado anterionnente en el captulo del mtodo ha,hCode().
Cuando se ejecuta el programa, vemos que el depurador de memoria se saltar una de cada tres claves, porque en la matriz
keys se ha incluido tambin una referencia nOffi1al a dichas claves, por lo que esos objetos no pueden ser depurados de la
memoria.
Contenedores Java 1.0/1.1
Lamentablemente, hay una gran cantidad de cdigo que se escribi util izando los contenedores de Java 1.011.1 , incluso hoy
da se sigue describiendo algo de cdigo nuevo empleando dichas clases. De modo que aunque nunca vayamos a uti lizar los
contenedores antiguos a la hora de escribir nuevo cdigo, s que tenemos que ser conscientes de su existencia. De todos
modos, los contenedores antiguos eran bastante limitados, as que es mucho lo que se puede contar acerca de ell os. Y, como
son anacrnicos, trataremos de evitar poner demasiado nfasis en alguno de los detalles relati vos a las decisiones de diseo
que con esos contenedores se tomaron.
Vector y Enumeration
El nico tipo de secuencia auto-expansiva en Java 1.0/ 1.1 era Vector, as que dicho contenedor se utilizaba con gran fre-
cuencia. Sus defectos son demasiado numerosos como para describirlos aqu (vase la primera edicin de este libro, dispo-
nible en ingls para descarga gratuita en www.MindView.nel). Bsicamente, podemos considerar este contenedor como un
tipo ArrayList con nombres de mtodos muy largos y compli cados. En la biblioteca revisada de contenedores Java, se adap-
t Vector para que pudiera funcionar como un contenedor de tipo a Collection y de tipo List. Esta solucin no es excesi-
vamente buena, ya que podra inducir a algunas personas a pensar que Vector ha mejorado, cuando en realidad slo se ha
incluido para poder soportar el cdigo Java ms anti guo.
Para la versin Java 1.0/ 1.1 del iterador se decidi inventar un nuevo nombre. "enumeration", en lugar de usar el trmino
con el que todo el mundo estaba familiarizado ("iterator"). La interfaz Enumeration es ms simple que la de Iterator, con
582 Piensa en Java
slo dos mtodos y utiliza nombres de mtodo ms largos: boolean hasMoreElements( ) devuelve true si esta enumera-
cin contiene ms elementos y Object nextElement( ) devuelve el siguiente elemento de esta enumeracin si es que exis-
te (en caso contrario. genera una excepcin).
Enumeration es slo una interfaz, no una implementacin, e incluso las nuevas bibliotecas utilizan todava en ocasiones la
interfaz Enumeration, lo que no resulta muy afortunado, aunque tampoco es que genere grandes problemas. En general,
debemos usar Iterator siempre que podamos en nuestro propio cdigo, pero an as debemos estar preparados para encon-
tramos con bibliotecas en las que nos veamos forzados a manejar contenedores de tipo Enumeration.
Adems, podemos generar un contenedor de tipo Enumeration para cualquier contenedor de tipo Collection utili zando el
mtodo Collcctions.enumeration(). corno se puede ver en el siguiente ejemplo:
//: containers/Enumerations.java
// Vector y Enumeration de Java 1.0/1.1.
import java.util.*;
import net.mindview.util.*
public class Enumerations {
public static void main(String[] args) {
v =
new i
Enumeration<String> e = v.elements{);
while(e.hasMoreElements())
System.out.print (e.nextElement () + ", It);
/1 Generar un objeto Enumeration a partir de otro Collection:
e = Collections.enumeration(new ArrayList<String>()) i
/ * Output:
ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO, BURUNDI, CAMEROON, CAPE VERDE,
CENTRAL AFRICAN REPUBLIC,
*///,-
Para generar un objeto Enumcration, ll amamos al mtodo elements(), despus de lo cual podemos usarlo para realizar una
iteracin en sentido di recto.
La ltima linea crea un objeto ArrayList y utiliza enumeration( ) para adaptar un obj eto Enumcration a partir del itera-
dar de ArrayList Iterator. As, si di sponernos de cdigo antiguo que necesite manejar un objeto Enumeration, podemos
seguir usando los nuevos contenedores.
Hashtable
Como hemos visto en la comparativa de rendimiento contenida en este ejemplo, el contenedor bsico Hashtable es muy
similar a HashMap, incluso en lo que respecta a nombres de mtodos. No hay ninguna razn para utilizar Hashtable en
lugar de HashMap en el cdigo nuevo que escribamos.
Stack
El concepto de pila ya ha sido expl icado anterionnente al hablar de LinkcdList. Lo que resul ta ms confuso acerca del con-
tenedor Stack de Java 1.0/ 1.1 es que en lugar de utilizar un Vector empleando el mecanismo de composicin, Stack here-
da de Vector. Por tant o, tiene todas las caracter sticas y comportamientos de Vector ms alguna funcionalidad propia de
Stack. Resulta dificil saber si los diseadores decidieron conscientemente que sta era una forma especialmente til de hacer
las cosas, o si se trata simplemente de un error absurdo; en cualquier caso. lo que est claro es que nadie revis el diseo
antes de lanzarlo a di stribuci n, de modo que este diseo incorrecto contina hacindose notar todava hoy en muchos pro-
gramas (pero no debera utilizarse en ningn programa nuevo).
He aqu una ilustracin simple de Stack en la que se inserta cada representacin de tipo String de una enumeracin enum.
El ejemplo muestra tambin cmo resulta igual de fci l de utilizar un elemento LinkedList como pila, o bien la clase Stack
creada en el Captul o 11 , Almacenamiento de objetos:
JI: containers/Stacks . java
// Ilustracin de la clase Stack.
import java.util.*;
import static net.mindview.util.Print. *;
17 Anlisis detallado de los contenedores 583
enum Month ( JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,
JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER )
public class Stacks {
public static void main (String [J args) {
Stack<String> stack = new Stack<String>();
for(Month ID : Month.values())
stack.push(m.toString()) ;
print("stack = ,. + stack);
// Tratar una pila como un Vector:
stack. addElement ( "The last line
n
);
priot ( " element S = 11 + stack. elementAt (5) ) i
priot ("popping elements: 11 ) ;
while(!stack.empty())
printnb(stack.pop() + " " ) i
/1 Utilizar un objeto LinkedList como una pila:
LinkedList<String> lstack = new LinkedList<String> ();
for(Month ro : Month.values())
Istack.addFirst (m .toString ()) ;
print("lstack = " + lstack);
while (!lstack .isEmpty () )
printnb (lstack. removeFirst () + " " );
II Uso de la clase Stack del Captulo 11,
II Almacenamiento de objetos:
net.mindview.util . Stack<String> stack2
new net.mindview.util.Stack<String> ();
for (Month m : Month.values())
stack2.push(m.toString()) ;
print ( n stack2 = " + stack2);
while(!stack2.empty())
printnb (stack2 . pop () + It 11);
1* Output:
stack = [JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,
JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER]
element 5 = JUNE
popping elements:
The last line NOVEMBER OCTOBER SEPTEMBER AUGUST JULY
JUNE MAY APRIL MARCH FEBRUARY JANUARY lstack = [NOVEMBER,
OCTOBER, SEPTEMBER, AUGUST, JULY, JUNE, MAY, APRIL, MARCH,
FEBRUARY, JANUARY]
NOVEMBER OCTOBER SEPTEMBER AUGUST JULY JUNE MAY APRIL MARCH
FEBRUARY JANUARY stack2 = [NOVEMBER, OCTOBER, SEPTEMBER,
AUGUST, JULY, JUNE, MAY, APRIL, MARCH, FEBRUARY, JANUARY]
NOVEMBER OCTOBER SEPTEMBER AUGUST JULY JUNE MAY APRIL MARCH
FEBRUARY JANUARY
* /// , -
A partir de las constantes enum Month se genera una representacin de String, que se inserta en el contenedor Stack
mediante push( ), y que luego se extrae de l a pila mediante pope ). Para resaltar uno de los puntos que hemos comentado
anterionnente, tambi n se realizan operaciones de tipo, Vector sobre el objeto Stack. Esto es posible porque, debido a la
584 Piensa en Java
herencia, un objeto Stack es un Vector. Por tanto, todas las operaciones que puedan reali zarse sobre un objeto Vector tam-
bin podrn realizarse sobre un obj eto Stack, como por ejemplo la operacin elementAt().
Como hemos mencionado anteriorment e. debemos ut ili zar un contenedor LinkedList cuando queramos un contenedor con
comportamiento de pila; o bien, la clase net.mindview.util.Stack creada a partir de la clase LinkedList.
BitSet
BitSet se utili za si se quiere almacenar de manera eficiente una gran cantidad de infonnacin de tipo binario. Slo resulta
eficiente desde el punto de vista del tamao; si lo que estamos buscando es eficienci a de acceso, este contenedor resulta lige-
ramente ms lento que emplear una matri z nati va.
Adems, el tamao mnimo de un contenedor BitSet es el de los valores a long: 64 bits. Esto implica que si estamos alma-
cenando algn conjunto de bits menor, como por ej emplo, 8 bits, con BitSet estaremos desperdi ciando buena parte del espa-
cio; en este caso, sera simplement e mejor crear una clase, o una matriz, para almacenar los indicadores binarios, en caso
de que el tamao sea un problema (esto slo ser un probl ema si estamos creando una gran cantidad de objetos que conten-
gan li stas de informacin binaria; y sl o debera tomarse esta decisin despus de reali zar un perfilado del programa O de
reali zar algn otro tipo de mtrica. Si tomamos la deci sin basndonos simplemente en nuestra propia creencia de que algo
es demasiado grande. temlinaremos creando una compl ejidad innecesari a y desperdiciando una gran cantidad de ti empo).
Un contenedor nomlal se expande a medida que aadimos ms el ementos y BitSet tambi n acta de esta mi sma manera. El
siguiente ejemplo muestra cmo funciona BitSet:
1/ : containers/Bi ts . java
/1 I lustracin de Bi tSet .
import java.util. ;
import static net.mindview.util.Print.
publ ic class Bits {
public sta tic void printBitSet (BitSet b )
print ( "bits: " + b ) ;
StringBuilder bbits = new StringBuilder () ;
for (int j = O; j < b.size () ; j++ )
bbits . append(b .get(j) ? " 1 " : "O");
print (" bit pattern : " + bbit s ) i
public static void main (String[] args )
Random rand = new Random (47 )
II Tomar bit de menos peso de nextInt () :
byte bt = (byte)ra nd.nextInt();
BitSet bb = new BitSet () ;
for (int i = 7 i
if ((( l i ) &
bb.set ( i ) ;
el se
bb.clear (i ) ;
>= O; i-- )
bt ) != O)
print ( "byte value : " + bt);
printBitSet (bb) ;
short st = (short)rand.nextInt () ;
BitSet bs = new BitSet();
for ( i nt i = 15; i >= O; i-- )
if ((( l i ) & st ) != O)
bs. set (i ) ;
else
bs . clear(i) ;
print ("short value : " + st);
printBitSet (bs ) ;
int it = rand.nextlnt() i
BitSet bi = new BitSet ();
for (int i = 31; i >= O; i-- )
if (({l i) & it ) != O)
bi. set (i) ;
else
bi.clear{i) ;
print("int value: 11 + it};
printBitSet (bi) i
/1 Probar conjunto de bits >= 64 bits:
BitSet b127 = new BitSet () ;
b127.set(127} ;
print{"set bit 127: " + b127) i
BitSet b255 = new BitSet(65) i
b255 . set(255} ;
print("set bit 255: 11 + b255);
BitSet bl023 = new BitSet (S 12 ) ;
bl023.set (1023 } ;
bl023.set(1024} ;
print("set bit 1023: " + bl023 ) ;
/ * Output:
byte value: -107
bits, {O, 2, 4, 7}
17 Analisis detallado de los contenedores 585
bit pattern: 1010100100000000000000000000000000000000000000000000000000000000
short value: 1302
bit., {l, 2, 4, 8, lO}
bit pattern: 0110100010100000000000000000000000000000000000000000000000000000
int value: -2014573909
bits, {O, 1, 3, 5, 7, 9, 11, 18, 19, 21, 22, 23, 24, 25, 26, 3l}
bit pattern: 1101010101010000001101111110000100000000000000000000000000000000
set bit 127, {127}
set bit 255, {255}
set bit 1023, {1023, l024}
* /// ,-
Utilizamos el generador de nmeros aleatorios para crear nmeros aleatorios byte, short e int, transfonnando cada uno en
su correspondiente patrn de bits que se almacena en un contenedor BitSet. Esto no causa ningn problema porque BitSet
tiene 64 bits como mnimo, as que ninguno de estos valores hace que se tenga que incrementar el tamaiio. A continuacin,
se crea contenedores BitSet de mayor tamao. Como podemos ver en el ejemplo, cada contenedor BitSet se expande segn
es necesario.
Nonnalmente, resulta ms conveniente utilizar un contenedor EnumSet (vase el Captulo 19, Tipos enumerados) en lugar
de BitSet cuando disponemos de un conjunto fijo de indicadores binarios a los que podemos asignar nombre, porque
EnumSet nos pennite manipular los nombres en lugar de las posiciones numricas de cada bit, con lo que se reduce la posi-
bilidad de cometer errores en el programa. EnumSet tambin nos impide aadir accidentalmente nuevas posiciones bina-
rias, que es algo que podra causar algunos errores graves y dificiles de localizar. Las nicas razones por las que deberamos
utilizar BitSet en lugar de EnumSet son: que no sepamos hasta el momento de la ejecucin cuntos indicadores binarios
vamos a uitlizar, o que resulte poco razonable asignar nombres a los indicadores, o que necesitemos algunas de las opera-
ciones especiales incluidas en BitSet (consulte la documentacin del JDK relativa a BitSet y EnumSet).
Resumen
La biblioteca de contenedores es, probablemente, la ms importante de cualquier lenguaje orientado a objetos. En la mayo-
ra de los programas se utilizarn contenedores ms que cualquier otro componente de la biblioteca. Algunos lenguajes
(Python, por ejemplo) incluyen incluso los componentes contenedores fundamentales (listas, mapas y conjuntos) como parte
del propio lenguaje.
586 Piensa en Java
Como hemos visto en el Captulo 11. Almacenamienlo de objetos, podemos hacer varias cosas cnonnemente interesantes
utilizando contenedores sin necesidad de mucho esfuerzo de programacin. Sin embargo. llegados a un cierto punto. nos
vemos obligados a conocer ms detalles acerca de los contenedores para poder utilizarlos adecuadamente: en particular.
debemos conocer los suficientes detalles acerca de las operaciones /IOSI1 como para escribir nuestro propio mtodo
hashCode() (y debemos saber tambin cundo es necesario hacer esto), y debemos conocer lo suficiente acerca de las dis-
tintas implementaciones de contenedores como para poder decidir cul es la apropiada para nuestras necesidades. En este
captulo hemos cubierto estos conceptos y hemos proporcionado detalles tiles adicionales acerca de la biblioteca de con-
tenedores. Llegados a este punto. el lector debera estar razonablemente bien preparado para utilizar los contenedores de
Java como parte de sus tareas cotidianas de programacin.
El diseo de una biblioteca de contenedores resulta complicado (como sucede con la mayora de los problemas de di seilo
de bibliotecas). En C++, las clases de contenedores cubren todos los aspectos fundamentales empleando muchas clases dife-
rentes. Evidentemente. esta solucin era mejor que la que haba disponible antes de que aparecieran las clases de contene-
dores de C++ (es decir, nada), pero no era una solucin que pudiera traducirse demasiado bien a Java. En el otro extremo,
yo he visto una biblioteca de contenedores que est compuesta por una nica clase. "container" que acta al mismo tiempo
como secuencia lineal y como matriz asociativa. La biblioteca de contenedores de Java trala de conseguir un cierto equili-
brio. la funcionalidad completa que esperaramos obtener de una biblioteca de contenedores madura, pero con una facilidad
de aprendizaje de uso superior a la de las clases contenedoras de C++ y a otras bibliotecas de contenedores similares. El
resultado puede parecer algo extrao en algunos aspectos, pero, a diferencia de algunas de las deci siones tomadas en las pri-
meras bibliotecas Java, esos aspectos extraos no son simples accidentes, sino decisiones de diselio cuidadosamente adop-
tadas y basadas en una serie de compromisos relativos a la complejidad de la solucin adoptada.
Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico rhe Thinking in Jm'tI Alllloftlled SOllllioll Guid/!, disponible para
la venta en lI'Ww.MimJView.nel.
Entrada/salida
La creacin de un buen sistema de entrada/salida (E/S) es una de las tareas ms dificiles para el
diseador de lenguajes, cosa que queda de manifiesto sin ms que ver la gran cantidad de
tcnicas distintas que se han utili zado.
Parece ser que el desafio radica en tratar de cubrir todas las posibilidades. No slo hay diferentes fuentes y consumidores
de datos de E/S con los que es necesario comunicarse: archivos, la consola, conexiones de red, etc., sino que tambin hay
que hablar con esos distintos interlocutores de diferentes formas (secuencial, acceso aleatorio, con buffer, binari a, de carac-
teres, por lneas, por palabras. etc.).
Los diseadores de la biblioteca Java abordaron el problema creando una gran cantidad de clases. De hecho, hay tantas cIa-
ses para el sistema de E/S de Java que a primera vista uno se siente intimidado (irnicamente, el diseo del sistema de E/S
en Java evita, de hecho. que se produzca una autntica explosin de clases). Asimismo. hubo un significativo cambio de
diseo en la biblioteca de E/S despus de la versin Java 1.0, cuando la biblioteca original orientada a bytes fue suplemen-
tada con una serie de clases de E/S orientadas a caracteres y basadas en el conjunto de caracteres Unicode. Las clases Dio
(que quiere decir "new l/O", nueva E/S, las cuales fueron introducidas en el IDK 1.4 Y ya son. por tanto, bastante "anti-
guas") fueron aadidas para mejorar el rendimiento y la funcionalidad. Como resultado, hay un gran nmero de clases que
es necesario aprender antes de comprender los suficientes detalles del sistema de E/S de Java como para poderlo utilizar
apropiadamente. Adems, es importante entender la evolucin de la bibli oteca de E/S, an cuando nuestra primera reaccin
sea decir: ''No me des la lata con cuestiones hi stricas, limtate a enseanne cmo utilizar las clases". El problema es que,
sin la perspectiva hi strica, podemos llegar rpidamente a sentin10s confundidos por algunas de las clases yana tener claro
cundo deberan usarse y cundo no.
En este captulo proporcionaremos una introduccin a las diversas clases de E/S existentes en la biblioteca estndar de Java
y veremos tambin cmo utilizarlas.
La clase File
Antes de presentar las clases que se encargan propiamente de leer y de escribir flujos de datos, vamos a examinar una uti-
lidad de la biblioteca que nos sirve de ayuda a la bora de tratar con aspectos relativos a los directorios de archivos.
La clase Fil e tiene un nombre que se presta a confusin, podramos pensar que bace referencia a un archivo, pero en reali-
dad no es as. De hecho, "FilePath" (ruta de archivo) hubiera sido un nombre mucho mejor para esa clase. Puede represen-
tar o bien el nombre de un archivo concreto o los nombres de un conjunto de archivos en un directorio. Si se trata de un
conjunto de archivos, podemos pedir que se nos devuelva dicho conj unto util izando el mtodo list( ), que devuelve una
matriz de objetos St ri ng. Tiene bastante sentido devolver una matriz en lugar de una de las clases contenedoras ms flexi-
bles, porque el nmero de elementos es fijo y, si queremos un listado de directorio distinto, basta con crear un objeto Fil e
diferente. Esta seccin muestra un ejemplo de utilizacin de esta clase, incluyendo la interfaz asociada Fil enameFilter .
Una utilidad para listados de directorio
Suponga que deseamos obtener un listado de directorio. El objeto Fil e puede uti lizarse de dos maneras distintas. Si invoca-
mos list() sin ningn argumento, obtendremos la lista completa contenida en el objeto File. Sin embargo, si queremos una
588 Piensa en Java
li sta restringida, por ejemplo, todos los archivos que tengan la extensin .java, entonces debemos utilizar un "filtro de direc-
tori o", que es una clase que especifi ca cmo seleccionar los objetos File que queramos visualizar.
He aqu el ejemplo. Observe que el resultado se ha ordenado muy fcilmente (de manera alfabtica) mediante el mtodo
java.util.Arrays.sort() y el comparador String.CASE_INSENSITlVE_ORDER:
JI : io/ DirList.java
JI Mostrar un listado de directorio utilizando expresiones regulares.
jj {Args: "D .* \ .java"}
import java . util . regex .* ;
import java . io .*
import java.util. * ;
public class DirList
public static void main (String[] args ) {
File path = new
St r ing [] list;
if ( args.length == O)
list path . list () ;
else
list path . list (new DirFilter (args[O] ))
Arrays . sort(list, String . CASE_INSENSITIVE_ORDER) ;
for (String dirItem : list )
System.out . println (dirItem)
class DirFi l ter implements Fi l enameFilter
private Pattern pattern
public DirFilter(String regex) {
pattern = Pattern . compile ( regex)
public boolean accept {File dir, String name )
return pattern.matcher (name ) . matches {)
/ * Output :
DirectoryDemo.java
DirList . java
DirList 2 . java
DirList3.java
* jjj ,-
La clase DirFilter impl ement a la interfaz FilenameFilter. Observe 10 simpl e que es esta interfaz:
public interface FilenameFilter {
boolean accept ( File dir, String name) i
La nica razn de existir de DirFilter es proporcionar el mtodo accept() al mtodo list(), de modo que ste pueda "retro-
ll amar" a accept() con el fin de determinar qu nombres de archi vos deben incluirse en la lista. Debido a esto, esta estmc-
tura se suele califi car como l'erollamada. Ms especficamente. ste es un ejemplo del patrn de diseo de Estrategia,
porque list() implementa la funcionalidad bsica y proporciona la Estrategia en la forma de un obj eto FilenameFilter, con
el fin de completar el algoritmo necesario para que list() proporcione su servicio. Puesto que list() toma un objeto
FilenameFilter como argumento, quiere decir que podemos pasar a ese mtodo un objeto de cualquier clase que implemen-
te FilenameFilter con el fin de el egir (incluso en tiempo de ejecucin) cmo debe comportarse el mtodo list(). El prop-
sito del patrn de di seo de ESll'alegia es proporcionar una dosis de nexibilidad en el comportamiento del cdigo.
El mtodo accept( ) debe aceptar un objeto File que represente el directorio en el que se encuentre un archi vo concreto y
un obj eto String que contenga el nombre de di cho archivo. Recuerde que el mtodo list( ) llama a accept( ) para cada uno
18 Entrada/salida 589
de los nombres de archivo del objeto directorio, para ver cules hay que incluir; esto se indica mediante el resultado de tipo
boolean devuelto por accept( l.
accept( ) utiliza un objeto matcher de expresiones regulares con el fin de ver si la expresin regular rcgex se corresponde
con el nombre del archivo. Utilizando accept( l , el mtodo list( l devuelve una matriz.
Clases internas annimas
Este ejemplo es ideal para reescribirlo empleando una clase ntema annima (concepto descrito en el Capt ulo 10, Clases
internas). Como primera aproximacin se crea un mtodo filter( ) que devuelve una referencia a un objeto filenameFiJter :
jI : io/ DirList2 .java
// Utilizac in de clases internas annimas.
// {Args, "D.* \ .java" }
import j ava.util.regex.*
import java.io .*;
import java.util.*
public class DirList2
public static FilenameFilter filter (final String regex) {
II Creacin de la clase interna:
return new FilenameFilter {) {
private Pattern pattern = Pattern.cempile(regex)
public boolean accept (File dir, String name) {
return pattern . matcher(name) .matches()
} I I Fin de la clase interna annima
public static void main (String {] args ) {
File path = new File ( " ." )
String [] list
if (args.length == O)
list path.list () ;
else
list path .list (f ilter (args {O] ) ) ;
Arrays.sort (list, String.CASE_ INSENSITIVE_ORDER) ;
for (String dirItem : list )
System.out.println {dirltem)
1* Output:
DirecteryDemo.java
DirList. java
DirList2. java
DirList3. java
* /// ,-
Observe que el argumento de filter( ) debe ser de tipo final. Esto es requerimiento de la clase intema annima, para que
sta pueda emplear un objeto que est fuera de su mbito.
Este diseo representa una mejora porque la clase FilenameFilter est ahora estrechamente acoplada a DirList2. Sin
embargo, podemos llevar este enfoque un paso ms all y definir la clase interna annima como un argumento de Ust( l, en
cuyo caso el ejemplo es todava ms sucinto:
11 : io/ DirList3.java
II Creacin de la clase interna annima "sobre el terreno".
II {Args: "D.*\.java
ll
}
import java.util.regex.*
import java.io.*
impert java.util.*;
public class DirList3
590 Piensa en Java
publi c sta tic vo id main (f inal String [1 args ) {
File path = new File ( "." } ;
String [J list;
if(args.length == O)
list path.list () ;
else
list path .list (new FilenameFilter () {
private Pattern pattern = Pattern.compile (args [Ol ) ;
public boolean accept (File dir I String name ) {
return pattern.matcher (name) .matches ( );
}
} ) ;
Arrays.sort (list, String.CASE_INSENSITIVE_ORDER) ;
for(String dirltem ; list )
System.out.println (dirltem) ;
1* Output:
DirectoryDemo.java
DirList. java
DirList2. java
DirList3. java
,///,-
El argumento de main() es ahora de tipo final , puesto que la clase interna annima utiliza argsOI directamente.
Este ejemplo nos muestra cmo las clases internas annimas permiten la creacin de clases especficas de un solo uso para
resolver cienos problemas. Una de las ventajas de esta tcnica es que mantiene aislado en un nico lugar el cdigo que
resuelve un problema concreto. Por otro lado, no siempre resulta tan fci l de leer y entender dicho cdigo, as que hay
que utilizar juiciosamente esta tcnica.
Ejercicio 1:
Ejercicio 2:
Ejercicio 3:
(3) Modifique DirList.java (o una de sus variantes) para que el objeto FilenameFilter abra y lea cada
archivo (utilizando la utilidad net.mindview.utiI.TextFile) y acepte el archivo basndose en si alguno de
los argumentos finales de la lnea de comandos existe en dicho archivo.
(2) Cree una clase denominada SortedDirList con un constructor que tome un objeto File y construya una
li sta de directorio ordenada a partir de los archivos contenidos en dicho objeto File. Aada a esta clase dos
mtodos list() sobrecargados: el primero produce la lista completa y el segundo produce el subconjunto
de la lista que se corresponda con su argumento (que ser una expresin regular).
(3) Modifique DirList.java (o una de sus variantes) para que calcule la suma de los tamallOS de los archi-
vos seleccionados.
Utilidades de directorio
Una tarea comn en programacin consiste en realizar operaciones sobre conjuntos de archivos, bien en el directorio local
o bien recorriendo todo el rbol de directori os. Resulta til disponer de una herramienta que produzca el conjunto de archi-
vos para nosotros. La siguiente clase de util idad produce una matri z de objetos File en el directorio local utili zando el mto-
do local( ) o un objeto List<File> que representa todo el rbol de directorios a partir del directorio indicado. empleando
walk() (los objetos File son ms tiles que los nombres de archivo porque dichos objetos contienen ms infonnacin). Los
archi vos se eligen basndose en la expresin regular que proporcionemos:
11: net/mindview/util/Directory.java
I I Generar una secuencia de objetos File que se correspondan
II con una expresin regular bien en el directorio local,
II o bien recorriendo un rbol de directorios.
package net.mindview.util
import java.util.regex.*
import java.io.*;
import java.util.*
public final class Oirectory {
public static File[]
local (File dir, final String regex) {
return dir.liscFiles(new FilenameFilter()
private Pattern pattern = Pattern.compile(regex);
public boolean accept (File dir, String name) {
return paccern.maccher(
}
} ) ;
new File (name) .gecName( .matches{) i
public static File(]
local (String path, final String regex) { II Sobrecargado
return local(new File (pach) , regex);
II Una tupla de dos elementos para devolver una pareja de objetos:
public sta tic class Treelnfo implements Iterable<File>
public List<File> files = new ArrayList<File>();
public List<File> dirs = new ArrayList<File>();
II El elemento iterable predeterminado es la lista de archivos:
public Iterator<File> iterator () {
return files.iterator();
void addAll(Treelnfo other)
files.addAll(other.files) ;
dirs.addAll(other.dirs) ;
public Stri ng toString () {
return "dirs :
lI\n\nfiles :
+ PPrint . pformat(dirs) +
11 + PPrint . pformat (files);
public static Treelnfo
walk (String start, String regex) { /1 Comenzar recursin
return recurseDirs{new File{start), regex);
public static Treelnfo
walk (File start, String regex) { II Sobrecargado
return recurseOirs(start, regex);
public static Treelnfo walk (File start) { lITado
return recurseDirs (start, ". * ti) i
public static Treelnfo walk(String start)
return recurseOirs(new File(start), ". * ");
static Treelnfo recurseDirs(File startDir, String regexl {
Treelnfo result = new Treelnfo(l i
for(File item startOir.listFiles()
if{item.isDirectory()) (
result.dirs.add(item) ;
result . addAll (recurseDirs (item, regex;
else II Archivo normal
i f (item.getName() .matches(regex
result.fi l es.add(item) ;
return resul t;
II Prueba simple de validacin :
18 Entrada/salida 591
592 Piensa en Java
public static void main(String[] args)
if{args.length == O)
System.out.println(walk(u.,,}) ;
el se
for(String arg : args)
System out.println{walk(arg));
El mtodo local() utiliza una variante de File.list() denominada listFiles() que genera una matriz de objetos File. Podemos
ver tambin que utiliza un objeto FilenarneFilter. Si hace falta una lista en lugar de LUla matriz, podemos convertir nosotros
mismos el resultado utilizando Arrays.asList().
Et mtodo walk( ) convierte el nombre del directorio de inicio en un objeto File e invoca recurseDirs( ), que realiza un
recorrido recursivo del directorio, recopilando ms infonnacin con cada recursin. Para distinguir los archivos normales
de los directorios, el valor de retorno es, de hecho, una "tupla" de objetos: un contenedor List que almacena archivos nor-
males y otro que almacena los directorios. Los archivos estn definidos aqu como public a propsito, porque el objeto
TreeInfo es si mpl emente para recopilar los objetos; si estuviramos simplemente devolviendo una li sta, no lo definiramos
como priva te, as que el hecho de que estemos devolviendo un par de objetos no quiere decir que los tengamos que definir
como priva te. Observe que Treelnfo implementa lterable<File>, que genera los archivos, de modo que disponemos de una
"iteracin predetenninada" a travs de la li sta de archi vos, mientras que para especificar directorios tenemos que escribir
".dirs".
El mtodo Treelnfo.toString() utili za una clase de "impresin avanzada", para que la salida sea ms fcil de visualizar. Los
mtodos predeterminados toString( ) de los contenedores imprimen todos los elementos de un contenedor en una mi sma
lnea. Para colecciones de gran tamao, esto puede hacerse dificil de leer, as que podemos tratar de emplear un format o
alternativo. He aqu la herramjenta que aade avances de lnea y sangrados a cada elemento:
JI : netJmindviewJutil/PPrint.java
JI Impresin avanzada de colecciones
package net.mindview.uti1i
import java.util.*
publie elass PPrint {
public statie String pformat (Collection<?> e) {
if(e . size() == O) return 11 [] "i
StringBuilder result new StringBuilder (11 [11) i
for {Objeet elem : el {
if(c . size() != 1)
result . append ( " \n 11) i
result.append(elem) i
if(e.size{) != 1}
result.append("\n") i
result. append ("] 11 l ;
return result.toString();
publie static void pprint (Colleetion<?> e) {
System.out.println{pformat(c) i
public statie void pprint {Object [] el {
System.out.println(pformat(Arrays.asList(c) ;
El mtodo pformat( ) produce un objeto String fonnateado a partir de un objeto Collection, y el mtodo pprint() utili za
pformat() para llevar a cabo esa tarea. Observe que los casos especiales en los que no existe ningn elemento o slo exis-
ta uno se gestionan de manera di stinta. Tambin hay una versin de pprint() para matrices.
La utilidad Directory est incluida en el paquete net.mndvew.util , para que est fcilmente disponible. He aqu un ejem-
plo de utilizacin:
JI: iofDirectoryDemo.java
JI Ejemplo de uso de las utilidades de directorio.
import java.io.;
import net.mindview.util.*
import static net.mindview.util.Print.*;
public class DirectoryDemo {
public statie void main (String [] args) {
1/ Todos los directorios:
PPrint. pprint (Directory. walk ( " . ") . dirs) ;
1/ Todos los archivos que comiencen con 'T'
for(File file: Directory.local(II.", "T.*"))
print (file) ;
print(II----------------------l') ;
JI Todos los archivos Java que comienzan con 'T':
for (File file: Directory.walk(".", "T.* \\.j ava" ))
print(file) ;
print("======================") ;
/ / Todos los archivos de clase que contengan "Z" o "z":
for (File file: Directory.walk{".",".*[Zz] .* \\ .class"
print (file) i
/ * Output: (ej emplo)
l. \xfilesl
. \TestEOF.class
. \ TestEOF. java
.\TransferTo.class
.\TransferTo.java
. \ TestEOF . java
. \TransferTo.java
. \ xfiles \ThawAlien. java
.\FreezeAlien.class
.\GZIPcompress.class
. \ ZipCompress. class
. ///, -
18 Entrada/salida 593
Puede que necesite refrescar sus conocimi entos sobre expresiones regulares en el Captulo 13, Cadenas de caracteres, para
comprender los argumentos situados en segunda posicin en local( ) y walk( ).
Podemos llevar esta idea un paso ms all y crear una herramienta que recorra directorios y procese los archivos contenidos
en ellos de acuerdo con un objeto Strategy (se trata de otro ejemplo del patrn de di seo Estrategia):
// : net/mindview/util/ProcessFiles.java
package net.mindview.util;
import java.io.*;
public class ProcessFiles (
public interface Strategy
void process(File file )
private Strategy strategy
private String ext;
public ProcessFiles (St rategy strategy, String ext ) {
this.strategy = strategy
this. ext = ext i
public void start (String [] args) {
try {
if(args.length == O)
594 Piensa en Java
processDirectoryTree(new File{".!!));
else
far (String arg args) {
File fileArg new File(arg);
if{fileArg.isDirectory() )
processDirectoryTree(fileArg) ;
else (
/1 Permitir que el usuario no incluya la extensin:
if(larg.endsWith("." + exc))
arg += "," + ext;
strategy.process(
new File (arg) .getCanonicalFile ());
catch(IOException el
throw new RuntimeException(e);
public void
processDirectoryTree(File rooe) throws IOException
for(File file: Directory .walk(
root .getAbsolutePath () I ". * \ \ ." + ext))
strategy.process(file.getCanonicalFile()) ;
}
JI Ejemplo de utilizacin :
public static void main(String [] args) {
new ProcessFiles(new ProcessFiles.Strategy()
public void process (File fi l e ) {
System.out.println(file) ;
}, "java!! ) .start(args) i
/ * (Execute to see output) * /// :-
La interfaz Strategy est anidada dentro de ProcessFiles, de modo que si queremos implementarla debemos implementar
ProcessFiles.Strategy, que proporciona ms infonnacin al lector. ProcessFiles realiza todo el trabajo de localizar los
archivos que tengan una extensin concreta (el argumento ext del constructor), y cuando localiza un archivo que cumpla
con el criterio simplemente se lo entrega al objeto Strategy (que tambin es un argumento del constructor).
Si no le damos ningn argumento, ProcessFiles asume que queremos recorrer todos los directorios a partir del direclOrio
actual. Tambin podemos especificar un archivo concreto, con o sin la extensin (el programa aadir la extensin en caso
necesario) o UIlO o ms directorios.
En maine ) podemos ver un ejemplo bsico de utili zacin de la herramienta; en el que se imprimen los nombres de todos
los archivos fuente Java de acuerdo con la linea de comandos que proporcionemos.
Ejercicio 4:
Ejerci cio 5:
(2) Utilice Directory.walk( ) para sumar los tamalios de todos los archivos de un rbol de directorios
cuyos nombres se correspondan con una expresin regular concreta.
(1) Modifique ProcessFiles.java para que busque correspondencias con una expresin regular en lugar de
con una extensin fija.
Bsqueda y creacin de directorios
La clase File es algo ms que una mera representacin de un directorio o un archivo existente. Tambin podemos utilizar
un objeto FUe para crear un nuevo directorio o una ruta completa de directorios, si es que no existe. Tambin podemos exa-
minar las caractersticas de los archivos (tamao, fecha de la ltima modificacin, penni sos de lectura/escritura), para ver
si un objeto File representa a un archivo o a un directorio, y borrar un archivo. El ejemplo siguiente muestra algunos de los
otros mtodos di sponibles en la clase Fil e (vase la documentacin del JDK disponible en http://jovo.sun. com para conocer
el conjunto completo de mtodos):
JI: io/MakeDirectories.java
/1 Ejemplo de uso de la clase File para crear
/1 directorios y manipular archivos.
// {Args: MakeDirectoriesTest}
import java.io.*;
public class MakeDirectories
private static void usage () {
System.err.println(
UUsage:MakeDirectories pathl ... \n" +
"Creates each path\n" +
"Usage:MakeDirectories -d pathl ... \n'! +
"Deletes each path\n" +
"Usage:MakeDirectories -r pathl path2\n" +
"Renames fram pathl to path2
11
);
System.exit(l) i
private static void fileData(File fl {
System.out.println(
"Absolute path: " + f,getAbsolutePath() +
"\n Can read: 11 + f .canRead () +
"\n Can write: + f.canWrite() +
"\n getName: 11 + f. getName () +
" \n getParent : + f. getParent () +
lI\n getPath: 11 + f.getPath( ) +
" \n length: " + f.length() +
" \n lastModified: u + f .lastModified () ) ;
if I f . isFile (1 1
System.out.println(IIIt's a file");
else if(f.isDirectory())
System.out . println(IIIt's a directory");
public static void main(String[] args) {
if (args.length < 1) usage();
if largs [O) . equals 1" -rOO 11 {
if(args .length != 3) usage()
File
old = new File (args[1] ),
rname = new File (args(2) )
old.renameTo(rname)
fileData (old)
fileData(rname) ;
return II Salir de main
int count = O;
boolean del = false
iflargs[O) . equalsl"-d"ll
count++;
del = true;
count--
while ( ++count < args .length) {
File f = new File(args(count)
if lf .exists (ll {
System.out.println(f + " exi sts " );
ifldell {
System. Qut . println ("deleting .. . " + f ) ;
f. delete I 1 ;
18 Entrada/salida 595
596 Piensa en Java
el se { II No existe
if(!dell {
f . mkdirs (1 ;
System.out.println(lIcreated 11 + f);
fileData (f) i
1* Output: (80% match)
created MakeDirectoriesTest
Absolute path: d:\aaa-TIJ4\code\io\MakeDirectoriesTest
Can read: true
Can write: true
getName: MakeDirectoriesTest
getParent: null
getPath : MakeDirectoriesTest
length: O
lastModified: 1101690308831
It's a directory
*/// ;-
En fileData() podemos ver diversos mtodos de consulta de archivos utilizados para mostrar informacin acerca del archi-
vo o de la ruta de directorios.
El primer mtodo utilizado por main() es renameTo(), que pennite renombrar (o desplazar) un archivo a una ruta de direc-
torios nueva, representada por el argumento, que es otro objeto File. Esto funciona tambin con directorios de cualquier lon-
gitud.
Si experimenta con el programa anterior, ver que puede construir una ruta de directorios todo lo compleja que quiera, por-
que mkdirs() se encarga de hacer el trabajo por nosotros.
Ejercicio 6: (5) Utilice PrneessFiles para encontrar todos los archivos de cdigo fuente Java en un subrbol de direc-
torios concreto que hayan sido modificados despus de una fecha concreta.
Entrada y salida
Las bibliotecas de E/S de los lenguajes de programacin utili zan a menudo la abstraccin deljlujo de dalas (slream), para
representar cualquier origen o destino de datos como un objeto capaz de producir o recibir elementos de datos. El flujo de
datos oculta los detalles de lo que ocurre con los datos dentro del dispositivo real de E/S.
Las clases de la biblioteca de Java para E/S estn divididas en entrada y salida, como se puede ver en la jerarqua de clases
al examinar la documentacin del JDK. A travs del mecani smo de herencia, todas las clases derivadas de las clases
InputStream o Reader disponen de mtodos bsicos denominados read( ) para leer un nico byte o una matriz de bytes.
De la mi sma fonna, todas las clases derivadas de las clases OutputStream o Writer disponen de mtodos bsicos denomi-
nados write() para escribir un nico byte o una matriz de bytes. Sin embargo, generalmente no utilizaremos estos mtodos;
esos mtodos existen para que otras clases puedan emplearlos, pero estas otras clases nos proporcionan una interfaz ms
til. Por tanto, raramente crearemos nuestro objeto flujo de datos utili zando una nica clase, sino que en su lugar apil are-
mos mltiples objetos para obtener la funcionalidad deseada (se trata del patrn de diseo Decorador, como veremos en
esta seccin). El hecho de que creemos ms de un objeto para producir un nico flujo de datos es la razn principal de que
la bibli oteca de E/S de Java sea tan confusa.
Resulta til clasificar las clases segn su funcionalidad. En Java 1.0, los di seadores de bibliotecas comenzaron decidiendo
que todas las clases que tuvieran algo que ver con la entrada de datos heredaran de InputStream, mientras que todas las
clases que estuvieran asociadas con la salida heredaran de OutputStream.
Corno suele ser habitual en este libro, tratar de proporcionar una panormica de las clases, pero asumiendo que el lector va
a emplear la documentacin del JDK para conocer el resto de los detalles, como por ejemplo la lista exhaustiva de mtodos
de una clase concreta.
18 Entrada/salida 597
Tipos de InputStream
La tarea de I nputStrea m consiste en representar clases que produzcan datos de entrada procedentes de diferentes fuentes.
Estos orgenes de datos pueden ser:
1. Una matriz de bytes.
2. Un objeto St ring.
3. Un archivo.
4. Una "canalizacin (pipe)" que funciona como una canalizacin fsica: se introducen las cosas por un extremo y
esas cosas salen por el otro.
5. Una secuencia de otros flujos de datos, de modo que se los pueda recopilar en un nico flujo.
6. Otros orgenes de datos, como por ejemplo una conexin a lntcmct (este tema se cubre en Thinking in EnleJ]Jrise
Java, disponible en lVlVw.MindView.net).
Cada lino de estos orgenes tiene una subclase asociada de Input8tream. Adems, Filter[nputStream tambin es un tipo
de InputSt ream, para proporcionar una clase base para las clases "decoradoras" que asocian atributos o interfaces fci les a
los flujos de datos de entrada. Este tema se analiza ms adelante.
Tabla E/S.l. npos de InputStream.
Clase Funcin
Argumentos del constructor
- -
Cmo utilizarlos
ByteArrayl nputStream Pennile utilizar un bllffer de memoria El buffer del cual hay que extraer los bytes.
como un InputSt ream.
Como origen de datos: conctelo a un objeto
FilterInputStream para proporcionar una interfaz til.
St ri ngBu ffer I nputStream Conviene un String en un InputStream. Un objeto String. La implementacin subyacente utiliza
en realidad un objeto StringBuffer.
Como origen de datos: conctelo a un objeto
Filter l nputSt rearn para proporcionar una interfaz til.
File) nputStream Para leer informacin de un archivo. Un objeto St ri ng que representa el nombre del archivo o
un objeto Fil e o FileDeseriptor.
Como origen de datos: conetelo a un objeto
FilterlnputStream para proporcionar una interfaz til.
PipedLnputStream Genera los datos que estn siendo escritos PipedOutputStrearn
en el objeto PipedOutput-Strearn asocia-
do. Implementa el concepto de "canaliza- Como origen de datos en programacin multihebra:
cin " de flujos de dalas. conctelo a un objeto Filterl nputStream para propor-
cionar una interfaz til.
Seq ueneeI nputStrearn Convierte dos o ms objetos lnputStream Dos objetos InputStream o un objeto Enumeration
en un nico Input Stream. para un contenedor de objetos InputSt ream.
Como origen de datos: conctelo a un objeto
Fil terlnputStream para proporcionar una interfaz til.
FilterlnputStream Clase abstracta que acta como interfaz Vase la Tabla E/S.3
para las clases decoradoras que proporcio-
nen funcionalidad til a las otras clases
InputStream. Vase la Tabla E/S.3.
Vase la Tabla E/S.3
598 Piensa en Java
Tipos de OutputStream
Esta categora incluye las clases que deciden a dnde debe ir la salida: a una matriz de bytes (pero no a un objeto String,
aunque presumiblemente podemos crear uno usando la matriz de bytes), a un archivo o a una canali zacin.
Adems, el objeto FilterOutputStream proporciona una clase base para las clases "decoradoras" que asocien atributos o
interfaces tiles a los flujos de datos de salida. Este tema se analiza ms adelante.
Tabla E/S.2. Tipos de OutputStream.
[
Clase Funcin
Argumentos del constructor
..
-e
Cmo utilizarlos
ByteArrayOutputStream Crea un en memoria. Todos los Tamao inicial opcional del bl!Uer.
datos que se enven al flujo de datos se
I
colocan en este buffer.
Para designar el destino de los datos: conctelo a un
I objeto F'ilterOutputStream para proporcionar una
interfaz til.
FileOutputStream Para enviar infornlacin a un arch ivo. Un objeto String que representa el nombre del archi-
vo o un objeto File o FileDescriptor.
Para designar el destino de los datos: conctelo a un
objeto FilterOutputStream para proporcionar una
interfaz til.
PipedOutputStrcam Cualqui er infonnacin que escribamos en PipcdlnputStream
este flujo de datos tennina siendo autom-
ticamente la entrada para el objeto
Para designar el destino de los datos en programacin
PipcdlnputStream asociado. Impl ementa
el concepto de "canalizacin" de fluj os de
multihebra: conctelo a un objeto
datos.
FilterOutputSt.-eam para proporcionar una interfaz
til.
-
FilterOutputSt.-eam Clase abstracta que acta como interfaz Vase la Tabla EISA.
para las clases decoradoras que proporcio-
nan funcionalidad til a las otras clases
OutputStream. Vase la Tabla EISA.
Vase la Tabla E/S.4.
Adicin de atributos e interfaces tiles
Los decoradores ya han si do introducidos en el Captulo 15, Genricos. La biblioteca de E/S de Java requiere muchas com-
binaciones diferentes de caract ersti cas, y sta es la justificacin de utilizar el patrn de di sefi.o Decorador.
1
La razn de la
exislencia de las clases "fi ltro" de la biblioteca de E/S de Java es que la clase abstracta de " filtro" es la clase base para todos
los decoradores. Un decorador debe lener la misma interfaz que el objeto al que decore. pero el decorador tambin puede
ampliar la interfaz, que es algo que ocurre en varias de las clases de " filtro".
Existe, sin embargo, un problema con la estrategia Decorador. Los decoradores nos dan mucha ms flexibilidad a la hora de
escribir un programa. (ya que se pueden mezclar y ajuslar fcilmente los atributos). pero aumentan la complejidad del cdi-
go. La razn de que la biblioteca de E/S de Java sea tan complicada de utilizar es que es necesario crear muchas clases (la
clase de E/S "bsica" ms todos los decoradores) para poder di sponer de ese nico objeto de E/S que deseamos.
Las clases que proporcionan la interfaz de decoracin para controlar un flujo de datos lnputStream o OutputStream
concreto son FilterlnputStrcam y FilterOutputStream, que no tienen nombres muy intuiti vos. FilterInputStream
1 No esta claro que sta haya sido una buena decisin de diseo. especialmente si la comparamos con la simplicidad de las bibliotecas de E/S en otros len-
guajes. Pero es, sin ninglma duda. la justificacin de esa decisin.
18 Entrada/salida 599
filterOutputStream derivan de las clases base de la biblioteca de E/S InputStream y OutputStream, lo que es un requi-
sito clave de los decoradores (para que puedan proporcionar la interfaz comn para todos los objetos que estn siendo deco-
rados).
Lectura de un flujo InputStream con FilterlnputStream
Las clases FilterlnputStream realizan dos tareas significativamente distintas. DatalnputStream pennite leer difcrcmcs
tipos de datos primitivos, as como objetos String (todos los mtodos empiezan con "rcad", como por ejemplo readByte(),
readFloat( ), etc. Esta clase, junto con su compaera DataOutputStream, pennitc desplazar datos primitivos de un lugar
a otro a travs de un flujo de datos. Esos "l ugares" estn detemlillados por las clases de la Tabla E/S.I.
Las clases FilterlnputStream restantes modifican la forma en que se comporta illtemamente un flujo InputStream: indi-
can si ste dispone de huffer o no, si llevan la cuenta de las lneas que estn leyendo (10 que nos permite preguntar por los
nmeros de lnea o asignar nmeros de lnea), y si se puede retroceder un nico carcter. Las dos ltimas clases parecen
pensadas para permitir la escritura de un compilador (probablemente se aadieron para soportar el experimento de "cons-
truir un compilador de Java en Java"), por lo que probablemente no los use nunca en tareas de programacin general.
La mayor parte de las veces ser necesario almacenar la entrada en un buffer, independientemente del dispositi vo de E/S
con el que nos estemos conectando, as que habra tenido ms sentido que la biblioteca de E/S dispusiera de un caso espe-
cial (o simplemente una llamada de mtodo) para la entrada sin buffer en lugar de para la entrada con bllffe/:
Tabla E/S.3. Tipos de FilterlnpulStream
Clase
DatalnputStream
B ufferedl nputStream
I "'"", ",m
PushbacklnputStream
Funcin
Se utiliza conjulltamente con
DataOutputStream, para poder leer pri-
mitivas (int, char, long, etc.) de un flujo
de datos de una manera portable.
Utilice sta para evitar una lectura fsica
cada vez que desee ms datos. Lo que
estamos diciendo es: "Utiliza un buffer".
Lleva la cuenta de los de lnea
en el flujo de dalos de entrada: podemos
invocar a getLincNumber() y
set LincNumber(int).
Dispone de un buffer de retroceso de un
nico byte para poder retroceder al ltimo
carcter ledo.
Argumentos del const ructor
Cmo utilizarlos
InputStream
.-.
Contiene una interfaz completa con la que leer
tipos primilivos.
InputStream, con un tamailo de blljJer opcio-
nal.
No proporciona una interfaz per se. Slo aiiade
la capacidad de hujIer al proceso. Asocie un
objeto interfaz.
Input8tream I
Slo aade la I1l11nerac16n de
probablemente la utIlicemos aSOCIando un obJe-
10 interfaz.
--
InputStrcam
Generalmente, se utiliza en el anlisis sintctico
realizado por un compilndor. Probablemente no
utilice nunca esta clase.
Escritura de un flujo OutputStream con FilterOutputStream
El complemento a Data IllputStream es DataOut putStream, que fonnatea cada uno de los tipos primitivos y objetos
String en un flujo de daws de tal manera que cualquier objeto Da talnputStrea m, en cualquier mquina, pueda leerlos.
Todos los mtodos comienzan con "write" , como por ejemplo writeByte(). writeFloat(), etc.
600 Piensa en Java
La intencin original de PrintStream era impri mir todos los tipos de datos primitivos y objetos St ring en una forma legi-
ble. Esto difiere de DataOutputStream, cuyo objeti vo es insertar los elementos de datos en un flujo de datos de tal mane-
ra que DatalnputStream pueda reconstnlirlos de manera portable.
Los dos mtodos ms importantes de PrintStream son print( ) y println( ), que estn sobrecargados para imprimir todos
los diversos tipos de datos. La diferencia entre print( ) y println( ) es que este ltimo aade un avance de linea cuando ha
acabado.
PrintStream puede ser problemtico porque act iva todas las excepciones de IOException (hay que comprobar explcita-
mente el estado de error con checkError(), que devuelve true si se ha producido un error). Asimismo, PrintStream no se
internacionaliza adecuadamente y no gesti ona los saltos de lnea de manera independiente de la plataforma. Estos proble-
mas estn resueltos en PrintWriter, que se describe ms adelante.
BufferedOutputStre.m es un modificador que le di ce al flujo de datos que utilice un buffer, para que no se realicen escri-
turas fisicas cada vez que escribamos en el flujo de datos. Nonnalmente, siempre conviene utili zar este modificador a la
hora de llevar a cabo una salida de datos.
Tabla E/S.4. Tipos de Fil terOutpulStream.
Clase Funcin
Argumentos del constructor
- --
- -
-
-
-
-
Cmo utilizarlos
DataOutputStrcam Se uti li za en conjuncin con OutputStream
DatalnputStream para poder escri bir
mitivas (int, char, long, etc.) en un flujo
Contiene una interfaz completa que permite escribir
de datos en una fonna ponable.
tipos primitivos.
PrintStream Para generar salida fonnateada. Mientras OutputStream, con un valor boolean opcional que
que DataOutputStream se encarga del indica que el bufler debe vaciarse con cada nueva
almacenamiel1to de los datos, PrintStream lnea.
gestiona la visualizacin.
Debe ser el envoltorio "final " para el objeto
OutputStream. Probablemente utilice esta clase
muy a menudo.
BufferedOutputStream Utilice esto para evitar que se realice una OutputStream, con un tamao de buffer opcional.
escrinlra fisica cada vez que se enve un
elemento de datos. Estamos diciendo: No proporciona una interfaz per se. Simplemente
Utiliza un buffer". Puede utilizar flush( ) aade un buffer al proceso. Ascielo a un objeto
para vaciar el buffer. interfaz.
Lectores y escritores
Java 1.1 realiz significativas modificaciones en la biblioteca fundamental de flujos de E/S. Cuando examine las clases
Re.der (lector) y Writer (escritor), su primer pensamiento (al igual que me pas a mi ) puede ser que esas clases intenta-
ban sustituir a InputStream ya OutputStream, pero en realidad no es as. Aunque algunos aspectos de la biblioteca
nal de la biblioteca de flujos de datos estn obsoletos y ahora se desaconsejan (si los emplea, el compilador generar una
advertencia), las clases InputStream y OutputStream siguen proporcionando una valiosa fu ncionalidad en la forma de
mecanismos de E/S orientados a bytes, mientras que las clases Reader y Writer proporcionan mecanismos de E/S
dos a caracteres y compatibles con Unicode. Adems:
1. Java 1.1 aadi nuevas clases a las jerarquas InputStream y OutputStream, as que resulta obvio que la inten-
cin no era susti tuir esas jerarquas.
2. Existen ocasiones en las que es necesario utilizar clases de la jerarqua orientada a "bytes" en combinacin con
las clases de la jerarqua orientada a "caracteres". Para hacer esto, existen clases "adaptadoras": InputStream-
Reader convierte un objeto InputStream en un objeto Reader, y OutputStreamWriter convierte un objeto
OutputStre.m en un objeto Writer.
18 Entrada/satida 601
La razn ms importante para la existencia de las jerarquas Reader y Writer es la intemalizacin. La antigua jerarqua de
flujos de datos de E/S slo soportaba flujos de datos con bytes de 8 bits y no gestionaba adecuadamente los caracteres
Unicade de 16 bits. Dado que Unicode se utiliza para la intemalizacin (y el tipo char nativo de Java es Unicode de 16-
bits), las jerarquas Reader y Writer se aadieron para soportar Unicode en todas las operaciones de E/S. Adems, las nue-
vas bibliotecas estn diseadas para que sus operaciones sean ms rpidas que las de las bibliotecas antiguas.
Orgenes y destinos de los datos
Casi todas las clase originales para flujos de datos de E/S de Java disponen de clases Reader y Writer correspondientes
con el fin de pennitir la manipulacin nativa de datos Unicode. Sin embargo, existen algunas ocasiones en las que los flu-
jos InputStream y OutputStream orientados a bytes constituyen la solucin correcta; en particular, las bibliotecas
java.util.zip estn orientadas a bytes, en lugar de a caracteres. Por tanto, el enfoque ms apropiado consiste en fratar de uti-
lizar las clases Reader y Writer siempre que se pueda y descubrir aquellas situaciones en las que haya que utilizar las
bibliotecas orientadas a bytes; resulta fcil descubrir esas situaciones, porque los programas no podrn compilarse en caso
contrario.
He aqu una tabla que muestra la correspondencia entre los orgenes y los destinos de infonnacin (es decir, de dnde vie-
nen y a dnde van fisicamente los datos) en las dos jerarquas.
Origenes y destinos: clase Java 1.0 Clase Java 1.1 correspondiente
JnputStream Reader
adaptador: JnputStreamReader
Output5trcam Writer
adaptador: OutputStream\Vriter
Filelnput5tream FileReader
FileOutput5tream FileWriter
StringBu fferl n putSt rea m (desaconsejado) StringReader
(no hay clase correspondiente) String\Vriter
ByteArrayl nputStrearn CharArrayReader
ByteArrayOutputStream CharArrayWriter
Pipedl nputStream PipedReader
PipedOutputStream Piped\Vriter
En general, encontrar que las interfaces para las dos jerarquas son similares, sino idnticas.
Modificacin del comportamiento de los flujos de datos
Para los flujos de tipo InputStrearn y OutputStream, los flujos de datos se adaptaban para cada necesidad particular utili-
zando subclases "decoradoras" de FilterlnputStream y FilterOutputStream. Las jerarquas de clases de Reader y Writer
continan utilizando esta idea, pero no exactamente.
En la siguiente tabla, la correspondencia es algo menos precisa que en la tabla anterior. La diferencia se debe a la organiza-
cin de las clases; aunque HufferedOutputStream es una subclase de FilterOutputStrearn, BufferedWriter no es una
subclase de FilterWriter (la cual, aunque es abstracta no tiene subclases y parece, por tanto, que se ha incluido simplemen-
te para poder utilizarla en el futuro o para que no nos rompamos la cabeza preguntndonos dnde est). Sin embargo, las
interfaces de las clases s que se parecen bastante.
602 Piensa en Java

Filtros: clase Java 1.0 Clase Java 1.1 correspodiente
filterlnputStream

FilterReader
--- - - - - -
FilterOutputStrcam Filtcr\Yriter (clase abstracta sin subclases)
f------
Bu ffcred 11l1)utStrea nI BuffercdReadcr (tambin tiene readLine(
BufferedOutputStream Buffered\Vriter
DatalnputStream
Utilice DatalnputStream (excepto cuando necesite usar readLine(), en
cuyo caso debe emplear BufferedRcadcr)
PrintStrcam Print\\' riter
LineNumberlnputStream (desaconsejado) LineNumberReader
St rcamTokenizer StreamTokenizer (utili ce el constructor que admite un objeto Reader)
PushbackInputStream PushbackReader
Existe una directriz bastante clara: cuando quiera utilizar readLine(). no debe hacerlo con un flujo DatalnputStream (esto
origina un mensaje de advertencia en tiempo de compil acin donde se infonna de que esa tcnica est desaconsejada), sino
que debe utilizar BufferedReader. Por lo dems, DatafnputStream contina siendo uno de los miembros "aconsejados"
de la bibl ioteca de E/S.
Para fac ilitar la transicin a soluciones que empleen Print\Vriter, esta clase ti ene constTIlctores que admiten cualquier obje-
to OutputStream as como objetos Writer. La interfaz de [0n11ateo de PrintWriter es casi idntica a la de PrintStream.
En Java SES. se han aadido constructores a PrintWriter para simplifi car la creacin de arcbjvos a la hora de escribir la
salida, como veremos enseguida.
Un constructor PrintWriter tambin dispone de una opcin para reali zar un vaciado de buffer automt ico, lo que tiene lugar
despus de cada ll amada a println() si se activa el correspondi ente indicador en el constructor.
Clases no modificadas
Algunas clases no sufrieron modifi caciones entre las versiones Java 1.0 y Java 1. 1:
Clases Java 1.0 sin clases Java 1.1 correspondientes
DataOutputStream
File
RandomAccessFile
Sequencel nputStream
DataOutputStream, en concreto, se utili za sin modificacin, por lo que para almacenar y extraer datos en un fonnato trans-
portable, se utilizan las jerarquas InputStream y OutputStream.
RandomAccessFile
RandomAccessFile se utili za para archivos que contengan registros de tamao conocido, de modo que podamos desplazar-
nos de un registro a otro usando seek(), y luego leer o modificar los registros. Los registros no ti enen que tener el mismo
tamao; simplemente tenemos que detenninar el tamao que tienen y el lugar del archivo donde se encuentran.
18 Entrada/salida 603
En principio, resulta bastante dificil de entender que RandomAccessFile no fonne parte de la jerarqua InputStream o
OutputStrcam. Sin embargo, no tiene ninguna asociacin con dichas jerarquas. 5al\'0 el hecho de que implementa las inter-
faces DataInput y DataOutput (que tambin son implementadas por DataInputStroam y DataOutputStroal11). Ni siquie-
ra utiliza ninguna de las funcionalidades de las clases InputStream o OutputStream existentes: se trata de una clase
completamente separada. que se ha escrito partiendo de cero, con sus propios mtodos (en sllll1ayor parte nativos). La razn
puede ser que RandomAccessFile tiene un compol13micnto esencialmente distinto del de los otros tipos de E/S, ya que nos
podemos mover hacia adelante y hacia atrs dentro de un archivo. En cualqui er caso, se trata de una clase aislada. descen-
diente directa de Object.
Esencialmente, un objeto RandomAcccssFile funciona como un flujo DatalnputStrealll que se hubiera conectado con un
flujo DataOutputStrcam, junto con los mtodos de getFilePointer( ) para averiguar en qu lugar del archivo nos encon-
tramos, seek( ) para desplazarse a un lluevo punto en el archi\'o y length( ) para detenninar el tamalio mximo del archivo.
Adems. los constmctores requieren un segundo argumento (idntico a fopen( ) en e) que indica si estamos simplememe
leyendo de manera aleatoria ("r") o leyendo y escribiendo ("rw"). No existe soporte para archivos de slo escritura, lo que
podra sugerir que RandomAccessFile podra tambin haberse diseado como clase heredada de DataInputStream.
Los mtodos de bsqueda slo estn disponibles en RandomAccessFiJe. que solamente puede aplicarse a archivos.
Buffcrcdl nput Stream pennite marcar con mark() una posicin (cuyo valor se almacena en una nica \'ariable interna) y
efectuar un reposicionamiento con reset( ) a dicha posicin, pero esta funcionalidad es muy limitada y no resulta muy til.
La mayor parte de la funcionalidad de Ra ndomAccessFile, si es que no toda ella. ha sido sust ituida en el JDK lA por los
archh'os mapeados en memoria nio, que describiremos ms adelante en el captulo.
Utilizacin tpica de los flujos de E/S
Aunque podemos combinar las clases de nujos de E/ S de muchas maneras distintas, lo ms probable es que en nuestros pro-
gramas utilicemos slo unas cuantas combinaciones. Los siguientes ejemplos pueden usarse como referencia bsica de lo
que constituye una utilizacin tpica de los mecanismos de E/S.
En estos ejemplos, simplificaremos el tratamiento de excepciones pasando las excepciones a la consola, pero esta forma de
proceder slo resulta apropiada en utilidades y ejemplos de pequeo tamao. En el cdigo de los programas reales, convie-
ne utilizar tcnicas de tratamiento de errores ms sofisticadas.
Archivo de entrada con buffer
Para abrir un archivo con entrada orientada a caracteres, utilizamos un objeto Fil elnputReader con un objeto String o File
como nombre de archivo. Para aumentar la velocidad, conviene asociar con el archivo un buffer, para lo cual se proporcio-
na al constructor la referencia a un objeto BuffercdReader. Puesto que BuffcredRcader tambin proporciona el mtodo
readLine( ), ste ser nuestro objeto final y la interfaz a travs de la cual efectuaremos las lecturas. Cuando readLine( )
devuelva null , habremos alcanzado el final del archivo.
11 : io/BufferedlnputFile.java
import java.io.*;
public class BufferedlnputFile
II Pasar excepciones a la consola:
public static String
read(String filename) throws IOException
1I Leer la entrada lnea a lnea:
BufferedReader in : new BufferedReader (
new FileReader(filename) ) ;
String s;
StringBuilder sb = new StringBuilder();
while{ (s : in.readLine()) 1= null)
sb.append(s + to\n
to
);
in. close () ;
return sb.toString();
604 Piensa en Java
public static void main(String[] args )
throws IOException {
System. out. print (read ( "BufferedlnputFile. java") )
1* (Ejecutar para ver la salida) *11/ :-
El objeto StringBuilder sb se utiliza para acumular el contenido completo del archivo (incluyendo los avances de lnea que
haya que aadir, ya que readLine( ) los elimina). Finalmente, se invoca c1ose( ) para cerrar el archiv0
2
Ejerci c io 7: (2) Abra un archivo de texto para poder leer el contenido de lnea en lnea. Lea cada lnea en forma de una
cadena de caracteres y site dicho objeto String dentro de un contenedor Li nkedLi st. Imprima todas las
lineas de LinkedList en orden inverso.
Ejercicio 8: ( 1) Modifique el Ejercicio 7 para proporcionar como argumento de la lnea de comandos el nombre del
archivo que haya que leer.
Ejercicio 9: (1) Modifique el Ejercicio 8 para pasar a maysculas todas las lneas del contenedor Li nkedList y enve
los resultados a System.out.
Ejercicio 10: (2) Modifique el Ejercicio 8 para admitir argumentos adicionales en la lnea de comandos que especifi-
quen palabras que haya que encontrar en el archivo. Imprima todas las lneas que contengan alguna de las
palabras.
Ejercicio 11: (2) En el ejemplo inner classes/GreenhouseCont r oUer.java, GreenhouseCont roller contiene un conjun-
to precodificado de sucesos. Modifique el programa para que lea dos sucesos y sus instantes relativos de
un archivo de texto (nivel de dificultad 8): utilice un patrn de diseo basado en el Mtodo defoctoria para
construir los sucesos; consulte Thinking in Palterns (wi/h Java) en \Vw\V.MindView.l1el.
Entrada desde memoria
Aqu, el objeto String resultante de Bufferedlnput Fil e.read() se uti liza para crear un objeto StringReader . A continua-
cin, se utiliza read() para leer de carcter en carcter y enviar los datos a la consola:
/1 : io/Memorylnput.java
import java.io . *;
public class Memorylnput
public static void main(String[] args)
throws IOException {
StringReader in = new StringReader(
BufferedlnputFile. read (uMemorylnput. java") )
int c
while ((c = in.read()) != -1 )
System.out.print( (char le);
/ * (Ejecutar para ver la salida) *11/ :-
Observe que read( ) devuelve el sigui ente carcter como un valor int y debe, por tanto, proyectarse el resultado sobre un
valor cha r para imprimirlo adecuadamente.
Entrada de memoria formateada
Para leer datos "fonnateados", ut ili zamos un flujo DataInputStrearn, que es una clase de E/S orientada a bytes (en lugar
de a caracteres). Por tanto, debemos uti lizar todas las clases I nputSt rea m en lugar de clases Reader. Por supuesto, pode-
2 En el diseo original, se supona que close( ) deba ser invocado cuando se ejecutara finali ze( ), y tendr ocasin de ver en algunos textos que fin alizc{)
se define de esta fomla para las clases de E/S. Sin embargo, como hemos comentado anteriomlentc, la funcionalidad fi nalize( ) no ha llegado a funcionar
de la fonna en que los disenadores de Java haban previsto originalmente (en otras palabras, no va a llegar a funcionar nunca), por lo que la tcnica segu-
ra consiste en invocar close( ) explcitamente para los archivos.
18 Entrada/salida 605
mos leer cualquier cosa (por ejemplo un archivo) de byte en byte utilizando clases InputStream. pero lo que aqu se utili-
za es una cadena de caracteres:
JI : io/FormattedMemorylnput.java
import java.io.*;
public class FormattedMemorylnput
public static void main(String[] args)
throws IOException {
try (
DatalnputStream in = new DatalnputStream{
new ByteArraylnputStream(
BufferedlnputFile.read{
"Format tedMemorylnput. java") . getBytes () ) ) ;
while(true)
System.out.print((char)in.readByte{)) ;
catch (EOFException el {
System.err.println{"End of stream");
/ * (Execute to see output) * /// :-
A un flujo ByteArraylnputStream hay que proporcionarl e una matriz de bytes. Para generarla, String dispone de un mto-
do getBytes(). El flujo ByteArraylnputStream resultante es un objeto InputStream apropiado para entregrselo a un flujo
DatalnputStream.
Si leemos los caracteres de un flujo DatalnputStream de byte eo byte usando readByte( ), todo valor de tipo byte es un
resultado legtimo, por lo que el valor de retorno no puede utilizarse para detectar el final de la eotrada. En lugar de ello,
puede emplearse el mtodo available( ) para detenninar cuntos caracteres ms hay di sponibles. He aqu un ejemplo que
muestra cmo leer un archivo de byte en byte:
11: io/TestEOF.java
II Comprobacin del final del archivo mientras se lee de byte en byte .
import java . io.*
public class TestEOF
public static void main(String[] args)
throws IOException {
DatalnputStream in = new DatalnputStream(
new BufferedlnputStream(
new FilelnputStream (tlTestEOF . java U) ) ) i
while (in. available () ! = O)
System.out . print((char)in.readByte()) ;
1* (Ejecutar para ver la salida) * 111:-
Observe que avalable( ) funci ona de manera distinta dependiendo del tipo de medio del que estemos leyendo; literalmen-
te, ese mtodo nos da "el nmero de bytes que pueden leerse sin que se produzca un bloqueo". Con un archivo, esto signi-
fica todo el archivo. pero con otro flujo de datos distinto podramos obtener alguna otra cosa, as que utilice el mtodo con
cuidado.
Tambin podemos detectar el final de la entrada en casos como ste tratando de capturar una excepcin. Sin embargo. el uso
de excepciones para control de flujo se considera una tcnica poco recomendable.
Salida bsica a archivo
Un objeto FileWriter escribe datos en un archivo. Prcticamente en todas las ocasiones nos convendr aadir un bujfer a
la salida, envolviendo el objeto en otro objeto BufferedWriter (trate de eliminar este objeto envoltorio para ver el impac-
to sobre el rendimiento: la utili zacin de buffel's ayuda a incrementar enormemente el rendimiento de las operaciones de
E/S). En este ejemplo, se utilizaPrintWriter como decorador para que se encargue de las tareas de fomlateo. El archivo de
datos creado de esta fonna se puede leer como un archivo de texto nonna!.
606 Piensa en Java
JI: io/BasicFileOutput.java
import java.io.*;
public class BasicFileOutput
static String file = "BasicFileOutput.out"i
public static void main(String[] args)
throws IOException {
BufferedReader in = new BufferedReader(
new StringReader(
BufferedlnputFile. read ("BasicFileOutput. java") ) ) i
PrintWriter out = new PrintWriter(
new BufferedWriter{new FileWriter(file)));
int lineCount = 1;
String Si
while((s = in . readLi ne()) != null}
out .println (lineCount++ + ": " + sl i
out. clase () ;
/1 Mostrar el archivo almacenado:
System, out . println(BufferedlnputFile , read(file)} ;
/ * (Ejecutar para ver las salida) * ///:-
A medida que se escriben li neas en el archivo, se aaden los nmeros de l nea. Observe que no se utiliza
LineNumberReader, porque es una clase muy simple y no resulta necesaria. Como podemos ver en este ejemplo, resulta
trivial ll evar la cuenta de nuestros propios nmeros de lnea.
Una vez que se han terminado los datos del flujo de entrada, readLine( ) devuelve null . El ejemplo muestra una llamada
explcita a close( ) para out, porque si no invocamos a close( ) para todos los archivos de salida, podramos encontrarnos
con que los buffers no se vaciarn, con lo que el archivo estara incompleto.
Un atajo para realizar la salida correspondiente a un archivo de texto
Java SES ha aadido W1 constructor a PrintWriter para que no tengamos que encargamos de reali zar a mano todas las
tareas de decoracin cada vez que queramos crear un archivo texto y escribir en l. El ejemplo siguiente muestra el archivo
BasicFil eOutput.java reescrito con esta nueva tcnica:
//: io/FileOutputShortcut . java
import java.io .*
public class FileOutputShortcut
static String file = ti FileOutputShortcut .out" i
public static void main(String[] args)
throws IOException {
BufferedReader in = new BufferedReader{
new StringReader(
Buf f eredInputFile . read{ tl Fil eOutputShort cut . java
lt
))) ;
// He aqu la tcnica ms simple:
PrintWriter out = new PrintWriter{file)
int lineCount = 1;
String s;
whi l e{{s = in . readLine{)) != nul l }
out . println {lineCount++ + ": " + s};
out . close{} ;
// Mostrar el archivo almacenado :
System.out .pri ntln{Buffer ed Input File . rea d(fil e }} ;
/* (Ejecutar par a ver la salida) */// : -
Seguimos disponiendo de un buffer. pero no tenemos que encargamos nosotros del mecanismo del mismo.
Lamentablemente, no existen atajos similares para otras tareas muy comunes, por lo que un programa tpico de E/S sigue
18 Entrada/salida 607
requi riendo una gran cantidad de texto redundante. Si n embargo. la utilidad TextFile que se usa en este li bro, que definire-
mos ms adelante en este caprulo, permite si mplificar estas tareas comunes.
Ejercicio 12: (3) Modifique el Ejercicio 8 para abrir tambin un archivo de texto de modo que podamos escribir texto
en l. Escriba en el archivo las lneas del contenedor LinkedList, j unto con sus correspondientes nme-
ros de l nea (no trate de utili zar las clases "LineNumber" ').
Ejercicio 13: (3) Modifique BasicFileOutput.java para que ut ilice LineNumberReader con el fin de controlar el
nmero de lneas. Observe que resulta mucho ms sencillo ll evar la cuenta medi ante programa.
Ejercicio 14: (2) Comenzando con BasicFileOutput.java, escriba un programa que compare la velocidad de escritura
en un archivo al utili zar mecanismos de E/S con buffer y sin bulfa
Almacenamiento y recuperacin de datos
Un obj eto PrintWriter format ea los datos para que resulten legibles. Sin embargo, para sacar los datos de manera que pue-
dan ser recuperados por otro /lujo de datos, se l l t i ~ z DataOutputStream para escribir los datos y DatalnputStream para
recuperarlos. Por supuesto, estos flujos de datos pueden ser cualquier cosa, pero en el sigui ente ejempl o se utiliza un archi-
vo, usndose buffers tanto para lectura como para escritura. DataOutputStream y DataInputStream estn orientados a
bytes y requi eren por tanto, fluj os de datos InputStream y OutputStream:
11: io/StoringAndRecoveringData.java
import java.io. * ;
public class StoringAndRecoveringData
publ ic static void main(String[] args)
throws IOEx ception {
DataOutputStream out = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(uData . txt
ll
})) j
out.writeDouble(3 .141S9) j
out . writeUTF("That was pi"} j
out.writeDouble(1 . 41413} i
out . wri t eUTF ( " Square root of 2") j
out.close() ;
DatalnputStream in = new DatalnputStream(
new BufferedlnputStream(
new FilelnputStream(IIData.txt"))};
System. out.println(in.readDouble()} i
II Slo readUTF() permite recuperar la cadena
II de caracteres Java-UTF apropiadamente:
System. out . println(in.readUTF()) j
System.out . println(in.readDouble{)) i
System.out.println(in.readUTF()} i
1* Output:
3.14159
That was pi
1.41413
Square root of 2
*///,-
Si utili zamos DataOutputStream para escribir los datos, Java garantiza que podemos recuperar perfectamente los datos con
DatalnputStream, independientemente de las platafonnas que se empl een para leer y escribir los datos. Esto resulta enor-
memente til , como puede atestiguar cualquiera que haya dedicado algo de tiempo a resolver problemas relacionados con
el tratamiento especfico de los datos en cada plataforma. Dichos probl emas desaparecen si disponemos de Java en ambas
plataformas
3
J XML es otra fonna de resolver el problema de trasmitir datos de una platafonna a otra, y no depende de si se di spone de Java cn todas las plataformas.
Hablaremos de XML ms adelame en este captulo.
608 Piensa en Java
Cuando estamos usando DataOutputStream, la nica fom1a fiable de escribir una cadena de caracteres para que pueda
recuperase mediante un flujo DatalnputStream consiste en utilizar codificacin UTF-8, la cual se consigue en este ejem-
plo mediante writeUTF() y readUTF( ). UTF-8 es un fonnato multibyte, y la longitud de codificacin varia de acuerdo
con el conjunto de caracteres que se est empleando. Si estarnos trabajando con ASCII o caracteres que sean ASCII en su
mayor parte (los cuales slo ocupan siete bits), Unicode representa un tremendo desperdicio de espacio y/o espacio de
banda, por lo que se emplea UTF-8 para codificar los caracteres ASCII en un nico byte, y los caracteres no-ASCII en dos
o tres bytes. Adems, la longitud de la cadena de caracteres se almacena en los dos primeros bytes de la cadena UTF-8. Sin
embargo, writeUTF( ) y readUTF() uti lizan una variante de UTF-S especial para Java (que est completamente descrita
en la documentacin del JDK cOlTespondiente a estos mtodos), por lo que si leemos una cadena escrita con writeUTF( )
utilizando un programa no-Java, deberemos escribir un cdigo especial para poder leer esa cadena apropiadamente.
Con writeUTF( ) y re.dUTF( ), podemos mezclar cadenas de caracteres con otros tipos de datos empl eando un flujo de
datos DataOutputStream. en la seguridad de que las cadenas de caracteres sern apropiadamente almacenadas como datos
Unicode y podrn recuperarse fcilmente mediante DatalnputStrcam.
El mtodo writeDouble() almacena el nmero double en el flujo de datos, y el mtodo complementario readDouble() per-
mite recuperarlo (existen mtodos similares para poder leer y escribir los otros tipos de datos). Pero para que cualquiera de
los mtodos de lectura funcionen correctamente, es necesario conocer la posicin exacta de los elementos de datos dentro
del flujo de dalOs, ya que sera igualmente posible el valor double al macenado como una si mpl e secuencia de bytes, o como
un valor char, etc. Por tal1to, tenemos que tener UI1 fonnato fijo para los datos en el archivo o, alternativamente. deberemos
almacenar en el archivo infonnac in adiciona l que ser necesario analizar para detenninar dnde estn ubicados los datos.
Observe que otras tcnicas. como la de serializacin de los objetos o XML (describiremos ambas ms adelante en el cap-
tulo), pueden ser ms senci ll as a la hora de almacenar y recuperar estructuras de datos ms complejas.
Ejercicio 15: (4) Consulte DataOutputStream y DatalnputStream en la documentacin del JDK, Comenzando con
StoringAndRecoveringData.java, cree un programa que almacene y luego extraiga todos los diferentes
tipos posibles proporcionados por las clases DataOutputStream y DatalnputStream, Verifique que los
valores se almacenan y extraen adecuadamente.
Lectura y escritura de archivos de acceso aleatorio
Utili zar RandomAecessFile es como emplear sendos flujos DatalnputStream y DataOutputStream compilados (porque
implementa las mismas interfaces: Datalnput y DataOutput). Adems, podemos utilizar seek() para desplazamos por el
archivo y cambiar los va lores.
Para poder usar RandomAccessFile, debemos conocer la disposicin del archivo para poder manipularlo adecuadamente.
RandomAccessFiJe tiene mtodos especficos para leer y escribir primitivas y cadenas de caracteres UTF-8. He aq u un
ejemplo:
JI : io/UsingRandomAccessFile.java
import java.io.*
public class UsingRandomAccessFile
static String file = "rtest .dat";
static void display() throws IOException
RandomAccessFile rf = new RandomAccessFile (file, "r")
for(int i = O; i < 7; i++)
System.out.println(
"Value " + i + ": " + rf. readDouble () ) ;
System.out.println(rf . readUTF()) ;
rf . close () ;
public static void main(String[] argsl
throws IOException {
RandomAccessFile rf = new RandomAccessFile ( file, "rw" ) ;
for ( int i = O; i < 7; i++}
rf.writeDouble(i*1.414l;
rf.writeUTF ( "The end of the file" ) ;
rf . clase () ;
display () ;
rf = new RandomAccessFile ( file, "rw" ) ;
rf.seek {5 *8 ) ;
rf.writeDouble{47.0001) i
rE. clase () ;
display () ;
/ * Output:
Value o: 0.0
Value 1, 1.414
Value 2, 2.828
Value ),
4.242
Value 4, 5.656
Value 5, 7 . 069999999999999
Value 6 , 8.484
The end of the file
Value
o,
0.0
Value 1, 1. 414
Value 2, 2.828
Value 3, 4.242
Value 4, 5.656
Value 5, 47.0001
Value 6, 8.484
The end of the file
* jjj ,-
18 Entrada/salida 609
El mtodo display( ) abre un archivo y muestra siete elementos contenidos en l en forma de valores double. En main( l ,
se crea el archivo y luego se abre y modifica. Puesto que, un valor double siempre tiene ocho bytes de longitud, para des-
plazarlos con seek( l al nmero situado en la posicin cinco basta con multiplicar 5*8 con el fin de obtener el valor de bs-
queda.
Como hemos indicado anterionnente, RandomAccessFile est aislado, de hecho, del resto de la jerarqua de E/S, salvo por
la circunstancia de que implementa las interfaces Datalnput y DataOutput. Esta clase no soporta el mecanismo de deco-
racin, asi que no se la puede combinar con ninguno de los aspectos de las subclases InputStream y OutputStream.
Tenemos que asumir. por tanto, que RandomAccessFile di spondr de los mecanismos de buffer apropiados, ya que no tene-
mos manera de especi ticar que se emplee.
La nica opcin de la que disponemos se encuentra en el segundo argumento del constructor: podemos abrir un archivo
RaodomAcccssFile para lectura (Ur") O lectura y escritura ("rw").
Tambin merece la pena considerar la utilizacin de archivos mapeados en memoria oio en lugar de RandomAccessFile.
Ejercicio 16: (2) Consulte RandomAccessFile en la documentacin del JDK. Tomando como punto de partida
Usi ngRandomAccessFile.java, cree un programa que almacene y luego extraiga todos los diferentes tipos
posibles soportados por la clase RandomAccessFile. Verifique que los valores se almacenan y se extraen
adecuadamente.
Flujos de datos canalizados
En este capitulo slo hemos mencionado brevemente las clases PipedOutputStream, PipedReader y PipedWriter. Esto
no quiere decir que dichas clases no sean tiles, pero la ventaja que proporcionan no se comprende adecuadamente hasta
que no se comienza a analizar el tema de la concurrencia, ya que los flujos de datos canali zados se emplean para la comu-
nicacin entre tareas. Este tema se tratar junto con un ejemplo en el Capitulo 21, Concurrencia.
Utilidades de lectura y escritura de archivos
Una tarea de programacin muy comn consiste en leer un archivo de memoria, modificarlo y luego volverl o a escribir. Uno
de los problemas con la biblioteca de E/S de Java es que nos obli ga a escribir bastante cdigo para poder realizar estas ope-
610 Piensa en Java
raciones comunes: no existen funciones bsicas de utilidad que se encarguen de realizar el trabajo por nosotros. Todavia
peor: los decoradores hacen que resuhe relati\'amente dificil acordarse de qu hay que hacer para abrir archivos. Por tanto,
resulla bastante conveniente aiiadir clases de utilidad a nuestra biblioteca que se encarguen de realizar estas tareas por no-
sotros. Java SE5 ha afiadido un constructor muy til a Print\Vriter para poder abrir fcilmente un archivo de texto con el
fin de escribir en l. Sin embargo. existen muchas otras tareas comunes que tendremos que realizar una y otra vez y convie-
ne tratar de eliminar el cdigo redundante asociado con dichas tareas.
He aqu la clase TextFilc que hemos utilizado en ejemplos anteriores de este libro para simplificar la lectura y escritura en
archivos. Contiene mtodos estticos para leer y escribir archivos de texto en una nica cadena de caracteres y tambin
podemos crear un objeto TextFiJe que almacene las lneas del archivo en un contenedor ArrayList (con 10 que tendremos
toda la funcionalidad de ArrayList a la hora de manipular el contenido del archivo):
// : net / mindview/ utiI / TexcFile.java
/1 Funciones estticas para leer y escribir archivos de texto en forma de
// una nica cadena de caracteres y para tratar el archivo como un
// contenedor ArrayList.
package net.mindview.util;
import java.io.*;
import java.util. *;
public class TextFile extends ArrayList<String> {
/1 Leer un archivo como una nica cadena de caracteres:
public static String read (String fileName) {
StringBuilder sb = new StringBuilder();
try {
BufferedReader in= new BufferedReader(new FileReader{
new File (fileName) .getAbsoluteFile ()) ;
try {
String S;
while ( (s = in. readLine ( ! = null) {
sb.append(sl;
sb.append("\n") ;
}
finally {
in. close () ;
catch(IOException e) {
throw new RuntimeException(e);
return sb.toString() i
1/ Escribir un archivo en una llamada a mtodo:
public static void write(String fileName, String text)
try {
}
PrintWriter out = new PrintWriter{
new File (fileName) .getAbsoluteFile();
try {
out.print(text) ;
finally {
out.close() ;
catch(IOException el {
throw new RuntimeException(e);
/1 Leer un archivo, dividir segn cualquier expresin regular:
pUblic TextFile(String fileName, String splitter)
super (Arrays. asList (read (fileName) . spli t (splitt er ) ) ) ;
1/ El mtodo split() con expresiones regulares suele dejar una
1/ cadena de caracteres vaca en la primera posicin:
if(get(O) .equals ('''')) remove(O);
JI Leer normalmente lnea a lnea:
public TextFile (String fileName) {
this (fileName, "\n
ll
);
public void write (String fileName ) {
try {
PrintWriter out new PrintWriter{
new File (fi leName ) . getAbsoluteFile () ) ;
try {
for(String item : this)
out.println{item) i
finally {
out.close() i
catch(IOException el
throw new RuntimeException(e);
JI Prueba simple:
public static void main{String[) args)
String file = read {"TextFile.java" );
write("test.txt", file);
TextFile text = new TextFile{"test.txt");
text.write{"test2.txt") ;
JI Descomponer en una lista ordenada de palabras distintas:
TreeSet<String> words = new TreeSet<String>{
new TextFile("TextFile.java", " \\W+ ")) i
II Mostrar las palabras en maysculas:
System.out.println{words.headSet{"a")) ;
1* Output:
[O, ArrayList, Arrays, Break, BufferedReader,
BufferedWriter, Clean, Display, File, FileReader,
FileWriter, I OException, Normally, Output, PrintWriter,
Read, Regular, RuntimeException, Simple, Static, String,
StringBuilder, System, TextFile, Tools, TreeSet , W, Write]
* ///,-
18 Entrada/salida 611
read() aade cada lnea a un objeto StringBuilder, seguida de un avance de lnea, ya que los caracteres de avance de lnea
se eliminan durante la lectura. A conlinuacin, devuelve un objeto String que contiene el archivo completo. write() abre el
archivo y escribe en l el texto contenido en String.
Observe que todos los elementos de cdigo en los que se abre un archivo protegen la ll amada a c1ose() dentro de una clu-
sula finally para garantizar que el archivo se cierre correctamente.
El constructor utiliza el mtodo read() para transfonnar el archivo en un objeto String, y luego emplea String.split() para
dividir el resultado en lneas. segn estn dispuestos los avances de lnea (si utiliza esta clase muy a menudo, trate de rees-
cribir este constructor para mejorar el rendimiento). Como no existe ningn mtodo para combinar las lineas es necesario
utilizar el mtodo write() no estlico para escribi r las lneas de fonna manual.
Puesto que esta clase pretende simplificar el proceso de lectura y escritura de archivos, todas las excepciones IOException
se convierten en excepciones RuntimeException, para que el usuario no tenga que emplear bloques try-catch. Sin embar-
go, en los programas reales puede que sea necesario crear otra versin que pase las excepciones IOException al Hamante.
En main(), se realiza una prueba sencilla para verificar que el mtodo funciona.
Aunque esta utilidad no requiere de una cantidad excesiva de cdigo, si que pennite ahorrar una gran cantidad de tiempo de
programacin, como veremos posterionnente en algunos de los ejemplos de este captulo.
612 Piensa en Java
Otra fonna de resolver el problema de leer archivos de texto consiste en utilizar la clase java.utilScanner introducida en
Java SES. Sin embargo. esa clase slo sirve para leer archivos, no para escribirlos, y di cha herramienta (que 110 se encuen-
tra en java.io) est diseada principalmente para crear ana li zadores de lenguajes de programacin o "pequeos lenguajes" .
Ejercicio 17: (4) Utilizando TextFile y un contenedor Map<Charaeter,lnteger>, cree un programa que cuente el
nmero de apariciones de los diferentes caracteres dentro de un archivo (en otras palabras, si la letra 'a'
aparece 12 veces en el archivo, el valor Integer asociado con el va lor Character que contenga 'a' en el
mapa ser' 12').
Ejercicio 18: (1) Modifique TextFile.java para que pase las excepciones IOException al lIamante.
Lectura de archivos binarios
Esta utilidad es si milar a TextFile.java, en el sentido de que pemlite si mplificar el proceso de lectura de archivos binarios:
11: net/mindview/util/BinaryFile.java
I I Utilidad para leer archivos en forma binaria.
package net.mindview.util;
import java.lo.*;
public class BinaryFile
public static byte(] read (File bFile) throws IOException{
BufferedlnputStream bf = new BufferedlnputStream(
new FilelnputStream(bFile));
try (
byte[1 data = new byte[bf.available () ];
bf.read (data ) ;
return data;
finally (
bf .elose () ;
public static byte(]
read (String bFile ) throws IOException {
return read (new File (bFile ) .getAbsoluteFile ()) ;
)
/// ,-
Un mtodo sobrecargado admite un argumento File; el segundo admite un argumento String, que representa el nombre del
archivo. Ambos devuelven la matriz de tipo byte resultante.
Se utiliza el mtodo available( ) para obtener el tamao apropiado de la matriz y esta versin concreta del mtodo read( )
sobrecargado se encarga de rellenar la matri z.
Ejercicio 19: (2) Utilizando BinaryFilc y un contenedor Map<Byte,Integer>. cree un programa que cuente el nmero
de apariciones de los diferentes bytes dentro de un archivo.
Ejercicio 20: (4) Utilizando Directory.walk( ) y BinaryFile, verifique que todos los archivos .e1ass en un rbol de
directorios comienzan con los caracteres hexadecimales CAFEBABE'.
E/S estndar
El tnnino E/S estndar hace referencia al concepto Unix de un nico flujo de nfonnacin que es utilizado por un progra-
ma (esta idea se reproduce en cierta manera en Windows y muchos otros sistemas operativos). Toda la entrada de un pro-
grama puede provenir de la entrada estill7dar, toda su salida puede ir a la salida estndar y todos sus mensajes de error
pueden enviarse a la salida de error esttndm: El valor de la E/S estndar es que los programas se pueden encadenar fcil-
mente entre s, y la salida de un programa puede ser la entrada estndar de otro programa. Se trata de una herramienta de
gran potencia.
18 Entrada/salida 613
Lectura de la entrada estndar
Siguiendo el modelo de la E/S estndar, Java dispone de Systcm.in, Systcm.out y Systcm.cr r. A lo largo de este libro hemos
visto cmo escribir en la salida estndar utilizando System.out, que est ya pre-envuelta en un objeto PrintStream.
System. er r es tambin un objeto PrintStream, pero System.in es un objeto lnput Stream simple sin ningn tipo de envol-
torio. Esto significa que aunque podemos uti lizar Syst cm.out y Systcm.err de manera directa, es necesario envolver
System.in antes de leer el mismo.
Nomlalmente, leeremos la entrada de lnea en lnea empleando readLine(). Para hacer esto, envuelva Systcrn.in en un obje-
to Buffered Reader , lo que requiere que convierta System.in en un objeto Reader mediante InputStreamReader . He aqu
un ejemplo que se limita a devolver como un eco cada lnea que escribamos:
jj , iojEcho.java
JJ Cmo leer de la entrada estndar.
jj {RunByHand}
import java.io . *
public class Echo
public static void main(String[] args)
throws IOException {
}
BufferedReader stdin = new BufferedReader(
new InputStreamReader (Syst em.in )) ;
String Si
while( (s = stdin.readLine{)) 1= null && s.length{)!= O)
System.out . println(sl;
JJ Una lnea vaca o Ctrl-Z hace que se termine el programa
// j,-
La razn de la especificacin de excepciones es que readLine() puede generar una excepcin IOException. Observe que
System.in debera normalmente emplearse con un buffer, al igual que la mayora de los flujos de datos.
Ejercicio 21 : (1) Escriba un programa que tome datos de la entrada estndar y pase a maysculas todos los caracteres,
y que luego inserte los resultados en la salida estndar. Redirija los cOlllcnidos de un archivo hacia este
programa (el proceso de redireccin variar dependiendo de su sistema operativo).
Cambio de System.out a un objeto PrintWriter
System.out es un objeto PrintStream, que a su vez es de tipo OutputStream. PrintWriter tienen un constructor que toma
un objeto OutputStream como argumento. Por tanto, si queremos convertir System. out en un objeto PrintWriter utilizan-
do dicho constructor:
JJ: ioJChangeSystemOut.java
/J Transformacin de System.out en un objeto PrintWriter.
import java.io.*;
public class ChangeSystemOut {
public static void main (String [] args ) {
PrintWriter out = new PrintWriter (System.out, truel i
out .println ( "Hello, world");
J* Output:
HelIo, world
* //j ,-
Es importante uti lizar la versin de dos argumentos del constructor de PrintWriter y asignar al segundo argumento el valor
true, para pennitir el vaciado automtico de buffer; en caso contrario, podramos no llegar a ver la sa lida.
Redireccionamiento de la E/S estndar
La clase Systcm de Java pernlite redirigi r los flujos de E/S estndar de entrada, de salida y de error ut ilizando simples ll a-
madas a los mtodos estticos:
614 Piensa en Java
setIn(JnputStream)
setOut(Pri ntStream)
setErr(printStream)
La redireccin de la salida resulta especialmente til si comenzamos de repente a generar una gran cantidad de salida en la
pantalla y sta empieza a desplazarse ms rpido de lo que la podamos leer.
4
El redireccionamiento de la entrada resulta
muy ti I para un programa de lnea de comandos en el que queramos probar repetidamente una secuencia concreta de entra-
da de datos de usuario. He aqu un ejemplo simple que muestra el uso de estos mtodos:
/1: io/Redirecting . java
// Ilustra la redireccin de la E/S estndar.
import java.io.*;
public class Redirecting
public static void main(String[] args)
throws IOException {
PrintStream console = System.out;
BufferedlnputStream in = new BufferedlnputStrearn (
new FilelnputStream ( "Redirecting . java") ) ;
PrintStream out = new PrintStream{
new BufferedOutputStream(
new FileOutputStream (" tes t. out It) ) ) i
System. setln(in) i
System. setOut(out) i
System.setErr(out) i
BufferedReader br = new BufferedReader(
new InputStrearnReader{System. in);
String s
while s = br.readLine ()) != null}
System. out . println(sl;
out. close () II Remember this!
System.setOut(console)
}
/// ,-
Este programa asocia la entrada estndar con un archivo y redirige la salida estndar y la salida de error estndar a otro archi-
vo. Observe que almacena al principio del programa una referencia al objeto System.out original y que restaura la salida
del sistema hacia dicho objeto al final del programa.
El redireccionamiento de E/S manipula flujos de bytes, no flujos de caracteres; es por eso que se utili zan objetos
InputStream y OutputStream en lugar de Reader y Writer.
Control de procesos
A menudo es necesario ejecutar otros programas del sistema operativo desde dentro de Java y controlar la entrada y la sali-
da de tales programas. La biblioteca Java proporciona clases para reali zar tales operaciones.
Una tarea comn consiste en ejecutar un programa y enviar la salida resultante a la consola. En esta seccin vamos a pre-
sentar una utilidad que permite simplificar dicha tarea.
Con esta utilidad pueden producirse dos tipos de errores: los errores nonnales que generan excepciones (para los que nos
limitaremos a regenerar una excepcin de tiempo de ejecucin) y los errores debidos a la ejecucin del propio proceso.
lnfonnaremos de dichos errores mediante una excepcin diferente:
11 : net/mindview/util/OSExecuteException . java
package net.mindview. util
4 En el Capitulo 22, Interfaces grficas de l/sI/ario, se muestra una solucin todava mas cmoda para este problema: un programa GUI con un area de
texto desplazable.
public class OSExecuteException extends RuntimeException
public OSExecuteException(String why) { super(why); }
} //1,-
18 Entradaisalida 615
Para ejecutar un programa, hay que pasar a OSExecute.command() una cadena de caracteres command, que es el mi smo
comando que esc ribiramos para ejecutar el programa en la consola. Este comando se pasa al constructor java.lang.
ProcessBuilder (que requiere que se le suministre en fa nna de una secuencia de objetos String), despus de lo cual se ini-
cia un obj eto ProcessBuilder:
JI: net/mindview/util/OSExecute.java
/1 Ejecutar un comando del sistema operativo
// y enviar la salida a la consola.
package net.mindview.util;
import java.io. *;
public class OSExecute
public static void command(String command) {
boolean err false;
}
try (
Process process
new ProcessBuilder{command.split{1I " )) .start();
BufferedReader results new BufferedReader(
new InputStreamReader(process.getInputStream())) i
String Si
while ((s ::o resul ts . readLine () ) ! = nulll
System. out .println(sl i
BufferedReader errors = new BufferedReader(
new InputStrearnReader(process.getErrorStream())) i
// Informar de los errores y devolver un valor distinto de
// cero al proceso llamante si existen p r oblemas:
while((s = errors . readLine())!= null) {
System.err.println(s) i
err = true;
catch(Exception el {
// Correccin para Windows 2000, que genera una
// excepcin para la lnea de comandos predeterminada:
if(!comrnand.startsWith("CMD /C"))
command ("CMD /C " + cornmand) i
el se
throw new RuntirneException(e) i
if (err)
throw new OSExecuteException ("Errors executing ti +
cornmand) ;
// /,-
Para capturar el flujo de salida estndar del programa a medida que ste se ejecuta, hay que invocar getInputStream( ). La
razn es que un objeto InputStream es algo de lo cual podemos leer.
Los resultados del programa llegan lnea a lnea, as que los leemos mediante readLine( ). Aqu nos limit amos a imprimir
las lneas, pero tambi n podramos capturarlas y devolverlas desde cornmand() .
Los errores de programa se envan al flujo estndar de error y se capturan invocando getErrorStream( ). Si existen erro-
res, se imprimen y se genera una excepci n OSExecuteException, de modo que el programa llamante pueda gesti onar el
problema.
He aqu un ejemplo que muestra cmo utili zar OSExccute:
//: io/OSExecuteDemo.java
// Ilustra el redireccionamiento de la E/S estndar.
616 Piensa en Java
import net.mindview.util.*
public class OSExecuteDemo {
public static void main (String (] args ) (
OSExecute.command ( "javap OSExecuteDemo");
/* Output:
Compiled fram "OSExecuteDemo.java"
public class OSExecuteDemo extends java.lang.Object{
public OSExecuteDemo () ;
public static void main ( java.lang.String[) ) ;
}
* /// ,-
Este ejemplo utiliza el descompilador javap (incluido en el JDK) para descompilar el programa.
Ejercicio 22: (5) Modifique OSExecute.java para que, en lugar de imprimir el flujo estndar de salida, devuelva los
resultados de la ejecucin del programa como una lista de cadenas de caracteres. Jlustre con un ejemplo
el empleo de la nueva versin de esta utilidad.
Los paquetes new
La "nueva" biblioteca de E/S de Java introducida en los paquetes java. nio.* el1 el JDK lA tiene como principal objetivo
aumentar la velocidad. De hecho, los "antiguos" paquetes de E/S se han reimplementado uti lizando Di o para poder aprove-
char este incremento de la velocidad, as que nos podremos beneficiar de esa mayor velocidad incluso aunque no escriba-
mos cdigo con nio. El incremento de velocidad se hace patente tanto en la E/S de archivos, que es la que vamos a explorar
aqu, como en la E/S de red. de la que se trata en T/inking in Enlelprise Java.
La mayor velocidad se obtiene utilizando estructuras que estn ms prximas a la fonna en que se realiza la E/S en el sis-
tema operativo: canales y bu./Jers. Podramos utilizar el smil de una mina de carbn: el canal sera la mina que contiene la
veta del carbn (los datos) y el buffer sera la vagoneta que introducimos en la mina. La vagoneta sale de la mina llena de
carbn y nosotros extraemos el carbn de la vagoneta. En otras palabras, nosotros no interactuamos directamente con
el canal sino que lo hacemos con el buffer, enviando el bujIer al canal. El canal extrae datos del buffer o pone datos en el
buffel:
El nico tipo de buffer que se comunica directamente con el canal es ByteBuffer, un bujIer que almacena bytes sin ningn
tipo de fonnato. Si examinamos la documentacin del JDK para java.nio.ByteBuffer, podremos ver que se trata de una
clase muy bsica: creamos uno de estos buffer:;; dicindole cunto espacio de almacenamiento hay que asignar y existen
mtodos para insertar y extraer datos, bien como bytes sin fonnato o como tipos de datos primitivos. Pero no existe mane-
ra de insertar o de extraer un objeto, ni siquiera de lipa String. Es una clase de nivel bastante bajo, precisamente porque esto
hace que se pueda implementar de una forma ms eficiente la mayora de los sistemas operativos.
Tres de las clases del "antiguo" esquema de E/S han sido modificadas para poder generar un objeto FileChannel:
FileInputStream, FileOutputStream y, tanto en lectura como en escritura, RandomAccessFile. Observe que se trata de
los flujos de datos para manipulacin de bytes, en consonancia con la naturaleza de bajo ni vel de ni o. Las clases Reador y
\Vriter en modo carcter no generan canales, aunque la clase java.nio.channels.Channels dispone de mtodos de utilidad
para generar objetos Reader y Writer a partir de canales.
He aqu un ejemplo simple donde se pmeban los tres tipos de flujo de datos con el fin de generar canales de escritura, de
lectura/escritura y de lectura:
11 : io/ GetChannel.java
II Obtencin de canales a partir de flujos de datos
import java.nio.*;
import java.nio.channels.*
import java.io.*;
public class GetChannel
private static final int BSIZE 1024;
public static void main{String[] args) throws Exception {
// Escribir un archivo:
FileChannel fe =
new FileOutputStream ("data. txt") . getChannel () i
fc,write{ByteBuffer.wrap("Some text ".getBytes ())};
fe. clase () ;
JI Aadir al final del archivo:
fe =
new RandomAccessFile ( "data. txt", "rw" ) .getChannel () ;
fc.position {fc .size( )}; JI Desplazarse al final
fe. wri te {ByteBuffer. wrap (" Sorne more". getBytes () ) ) i
fe. clase () ;
JI Leer el archivo:
fe = new Fi lelnputStream ("data. txt 11) getChannel () ;
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
fc.read(buffl;
buff .flip l) ;
while (buff.hasRemaining ())
System.out.print( (char)buff.get ());
/ * Output:
Sorne text Sorne more
*///;-
18 Entrada/salida 617
Para cualquiera de las clases de flujos de datos mostradas aqu, getChannel( J generar un objelO F'ileChannel. Los cana-
les son bastante bsicos. Podemos entregarlos un objeto ByteBuffer para lectura o escritura, y podemos bloquear regiones
del archivo con el fin de obtener acceso exclusivo (hablaremos ms sobre esto posterionnente).
Una forma de insertar bytes en un objelO ByteBuffcr consiste en introducirlos directamente utilizando uno de los mtodos
put, con el fm de insertar uno o ms bytes, o valores de tipos primitivos. Sin embargo. como puede verse en el ejemplo,
tambin podemos envolver una matriz de tipo byte en un objeto ByteBuffer utilizando el mtodo wrap( J. Cuando hace-
mos esto, no se copia la matriz subyacente, sino que se utili za como almacenamiento para el objeto ByteBuffer generado.
En este caso, decimos que el objeto ByteBuffer est "respaldado" por la matriz.
El archivo data.txt se vuelve a abrir utilizando un objclO RandomAccessF'i1e. Observe que debemos desplazar el objelO
FileChannel por el archivo; en el ejemplo, se le desplaza hasta al final para poder aadir nueva informacin mediante escri
ruras adicionales.
Para acceso de slo lectura. es necesario asignar explcitamente un objeto ByteBuffer utili zando el mtodo esttico alloca-
te( J. El objetivo de nio consiste en transferir rpidamente grandes cantidades de datos, por lo que el tamao del objeto
ByteBuffer tiene su importancia: de hecho, el valor de 1 K utilizado aqu resulta, probablemente, ms pequeo de lo que
normalmente conviene utilizar (tendr que experimentar con cada aplicacin para encontrar el tamao adecuado).
Resulta posible obtener una velocidad an mayor usando allocateDirect( J en lugar de allocate( J, con el fin de generar un
buffer "directo" que pueda estar acoplado de forma an ms estrecha con el sistema operativo. Sin embargo, el gasto adi-
cional de procesamiento de dicho tipo de asignacin es mayor, y la implementacin vara de un sistema operativo a otro, as
que, de nuevo, ser necesario experimentar con cada aplicacin para determinar si un buffer directo permite obtener una
ventaja de velocidad.
Despus de invocar read( J para deci rl e al objeto F'ileChannel que almacene bytes en el objeto ByteBuffer, es necesario
invocar flip() en el buffer con el fin de que ste se prepare para la extraccin de los bytes que contiene (como puede ver, el
mecanismo parece un poco mdimentario, pero recuerde que es un mecanismo de muy bajo nivel y que est pensado para
obtener la mxima velocidad). Y si furamos a utilizar el buffer para operaciones read( ) adicionales, tendramos tambin
que invocar clear() para preparar el b,![(er para cada read(). Podemos ilustrar esto mediante un sencillo programa de copia
de archivos:
JJ : ioJChannelCopy.java
II Copia en un archivo utilizando canales y buffers
II {Args: ChannelCopy . java test.txt}
import java.nio.*i
618 Piensa en Java
import java.nio.channels. *i
import java. lo.;
public class ChannelCopy {
private static final int BSIZE = 1024;
public static void main(String[] args) throws Exception {
if (args .length ! = 2 ) {
System. out. println ( lIarguments: sourcef ile destfile!l) i
System.exit (1);
FileChannel
in = new FilelnputStream(args{O]) .getChannel(),
out = new FileOutputStream(args[l]) .getChannel();
ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
while(in.read(buffer) != -1) {
buffer.flip(); 1/ Preparacin para la escritura
out.write(buffer) ;
buffer. clear () ; / / Preparacin para la lectura
Como puede ver, se abre un objeto FileCha nnel para lectura y otro para escri tura. Se asigna un objeto ByteBurrer , y cuan-
do FileCha nnel.read( ) devuelve - ( (una remini scencia, sin lugar a duda, de Unix y C), querr decir que hemos alcanza-
do el fina l del flujo de datos de entrada. Despus de cada read(), que inserta datos en el buffer, tlip() prepara el buffer para
poder extraer la infornlacin con una ll amada a wri te(). Despus de la ejecucin writ e( ), la infonnacin continuar estan-
do en el buffer, y clear() permi tir reinicializar todos los punteros internos para que el buffer quede li sto para aceptar datos
durante otras llamadas a read( ).
Sin embargo. el programa anterior no es la forma ideal de gestionar este tipo de operacin. Dos mtodos especiales
t r a nsrerTo( ) y transrerFrom( ) permiten conectar directamente un canal con otro:
11: io/TransferTo.java
II Utilizacin de transferTo() entre canales
II {Args: TransferTo.java TransferTo.txt}
import java.nio.channels.*
import java.io.*;
public c lass TransferTo
public static void main(String[) args) throws Exception {
if(args.length != 2) {
System. out .println ("arguments: sourcefile destfile")
System.exit(l) ;
FileChannel
in = new FilelnputStream(args [O]) . getChannel (),
out = new FileOutputStream(args[l] ) .getChannel{)
in.transferTo(O, in.size(), out);
II O bien,
II out.transferFrom(in, O, in.size())
No vamos a tener que hacer este tipo de cosas muy a menudo en nuestras tareas de programacin. pero resulta convenien-
te conocerlas.
Conversin de datos
Si volvemos a examinar GetChannel.j ava, observaremos que para imprimir la informacin del archivo, estamos extrayen-
do los datos de byte en byte y proyectando cada byte sobre un valor charo Esto parece un tanto primitivo: si examinamos
la clase java.nio.CharBufrer, veremos que di spone de un mtodo toString() que dice: "Quiero que me devuel vas una cade-
18 Entrada/sal ida 619
na de caracteres que contenga los caracteres de este buffer". Puesto que un objeto ByteBuffer puede manipularse como un
buffer de tipo CharBuffer mediante el mtodo asCharBuffer ( ), por qu no usar dicho mtodo? Como puede ver anali-
zando la primera lnea de la salida del siguiente ej empl o, dicha solucin no funciona:
JI: io/SufferToText . java
JI Conversin de texto para objetos ByteBuffer
import java . nio. * ;
import java . nio.channel s .* ;
import java.nio.charset .*
import java.io. * ;
public class BufferToText
prvate static final int BSIZE 1024;
public static void main(String[] args) throws Exception
FileChannel fe =
new FileOutputStream ( "data2. txt") .getChannel () ;
fe. write (ByteBuffer. wrap ( "Sorne text 11 .getBytes () ) ) i
fe. clase () ;
fe = new FilelnputStream("data2.txt") .getChannel();
ByteBuffer buff = ByteBuffer.allocate(BSIZE) i
fc.read{buff) ;
buff. flip () ;
II No funciona :
System.out.println(buff. asCharBuffer( ;
II Decodificar usando el conjunto de caracteres
II predeterminado de este sistema:
buff.rewind() i
String eneoding = System. getProperty ("file. encoding") ;
System.out.println(tlDecoded using " T encoding + ": "
+ Charset. forName (encoding) .decode(buff i
II O bien, podemos codificar con algo que permita imprimir:
fe = new FileOutputStream( tldata2. txt") . getChannel () i
fc.write(ByteBuffer.wrap(
"Sorne text" .getBytes("UTF-16BE" );
fe. elose () ;
II Ahora intentamos leer de nuevo:
fc = new FilelnputStream("data2.txt") .getChannel();
buff . clear () ;
fC.read{buff) ;
buff.flip{) ;
System.out . println(buf f . asCharBuffer( ;
II Utilizar un objeto CharBuffer a travs del cual escribir:
fc = new FileOutputStream ("data2 . txt " ) . getChannel () ;
buff = ByteBuffer . allocate(24); II Ms que suficiente
buff.asCharBuffer{) .put{"Some text");
fc.write(buff) i
fe. elose () ;
II Leer y visualizar :
fe = new FilelnputStrearn ("data2 . txt") . getChannel () i
buff . clear () ;
fC.read{buff) ;
buff.flip{) ;
Systern.out.println(buff .asCharBuffer( ;
1* Output :
????
Decoded using Cp1252 : Sorne text
Sorne text
Sorne text
* ///,-
620 Piensa en Java
El buffer contiene bytes sin fonnato, y para transfonnarlos en caracteres debemos codificarlos a medida que los introduci-
mos (para que tengan significado cuando se los extraiga) o decodificar/os a medida que salen del bllffer. Esto se puede con-
seguir utilizando la clase java.nio.charset.Charset, que proporciona herramientas para la codificacin en muchos tipos
distintos de conjuntos de caracteres:
JJ : ioJAvailableCharSets.java
JJ Muestra los conjuntos de caracteres y sus alias
import java.nio.charset.*
import java.util.*
import static net . mindview util.Print.*
public class AvailableCharSets {
public static void main(String[] args)
SortedMap<String,Charset> charSets =
Charset.availableCharsets() i
Iterator<String> it = charSets.keySet() .i terator() i
while(it.hasNext()) {
String csName = it.next() i
printnb(csName)
Iterator aliases =
charSets. get (csName) . aliases () . i terator () i
if(aliases.hasNext(})
printnb(": ");
while{aliases.hasNext())
printnb(aliases.next()) i
if(aliases.hasNext())
printnb{H, 11 ) i
print() ;
/ * Output:
Big5 : csBig5
Big5-HKSCS: big5-hkscs, big5hk, big5-hkscs:unicode3 . 0,
big5hkscs, Big5_HKSCS
EUC-JP: eucjis, x-eucjp, csEUCPkdFmtjapanese, eucjp,
Extended_UNIX_Code_Packed_Format_for_Japanese, x-euc-jp,
euc_jp
EUC-KR: ksc5601, 5601, ksc5601_1987, ksc_5601, ksc5601-
1987, euc_kr, ks_c_5601-1987, euckr, csEUCKR
GB1B030, gblB030-2000
GB2312: gb2312-1980, gb2312, EUC_CN, gb2312-80, eue-cn, euccn, x-EUC-CN
GBK: windows-936, CP936
* /// ,-
Por tanto, volviendo a BufferToText.java, si rebobinamos el bllffer con rewiod() (para retroceder al principio de los datos)
ya continuacin utili zamos el conjunto de caracteres predeterminado de esa plataforma para decodificar los datos con deco-
de( ), el objeto de bllffer resultante eharBurrer podr imprimirse sin problemas en la consola. Para averiguar el conjunto
de caracteres predeterminado, use System.getPropcrty("file.encoding"), que genera la cadena de caracteres que da nom-
bre al conjunto de caracteres. Pasando este nombre a Charset.forName() se genera el objeto Charset (conjunto de caracw
teres) que puede utili zarse para decodificar la cadena.
Otra alternativa consiste en codificar (con encode( utilizando un conjunto de caracteres que pemlita obtener algo que
pueda imprimirse en el momento de leer el archivo, como podemos ver en la tercera parte del archivo BufferToTcxt.java.
Aqu, se utiliza UTF-16BE para escribir el texto en el archivo, y en el momento de leerlo, todo lo que hay que hacer es con-
vertirlo a un objeto CharBuffer, el cual generar el texto esperado.
Por ltimo, podemos ver lo que sucede si escribimos en el objeto ByteBuffer a travs de un objeto CharBuffer (analizare-
mos este tema con ms detalle posterionnente). Observe que asignamos 24 bytes para el objeto ByteBuffer. Puesto que cada
18 Entrada/salida 621
valor char requiere dos bytes, esto es suficiente para 12 caracteres, pero "Some text" slo tiene 9. Los restantes bytes. con
valor cero, continuarn apareciendo en la representacin del objeto CharBuffer generada por su mtodo toStrillg(), como
puede verse en la salida.
Ej ercicio 23: (6) Cree y pruebe un mtodo de utilidad para imprimir el contenido de un objeto CharBuffer basta la posi-
cin en que los caracteres dejen de ser imprimibles.
Extraccin de primitivas
Aunque un objeto ByteBuffer slo almacena bytes, contiene mtodos para generar cada uno de los diferentes tipos de valo-
res primit ivos a parti r de los bytes que contiene. Este ejemplo ilustra la insercin y la extraccin de varios valores em-
pleando dichos mtodos:
/1 : io/GetData.java
/1 Obtencin de diferentes representaciones
II a partir de un objeto ByteBuffer
import java.nio.*
import static net.mindview.util.Print.*;
public class GetData {
private static final int BSIZE = 1024;
public static void main(String(] args}
ByteBuffer bb = ByteBuffer.allocate(BSIZE}
II El proceso de asignacin pone a cero
II automticamente el objeto ByteBuffer:
int i = O
while(i++ < bb.limit()}
iflbb.get() [o 01
print ("nonzero" ) ;
print("i = " + i }
bb.rewind(} ;
II Almacenar y leer una matriz char:
bb. asCharBuffer () . put ("Howdy! ")
char C;
while(c = bb.getChar() != O)
printnb(c + " "J
print() ;
bb.rewind() ;
II Almacenar y leer un valor short:
bb. asShortBuffer 11 . put Ilshort 14 711421 ;
printIbb.getShort11 1;
bb.rewind() ;
II Almacenar y leer un valor int:
bb.aslntBufferll .putI994711421;
print Ibb.getlnt 11 1 ;
bb.rewind()
II Almacenar y leer un valor long:
bb.asLongBuffer() .put (99471142)
printlbb . getLonglll;
bb.rewind()
II Almacenar y leer un valor float:
bb . asFloatBuffer{) .put{99471142);
print Ibb.getFloat 11 1 ;
bb.rewind() ;
II Almacenar y leer un valor double:
bb.asDoubleBuffer() .put(99471142);
print(bb.getDouble{) i
bb . rewind() ;
622 Piensa en Java
/ * Output:
i = 1025
H o w d y
12390
99471142
99471142
9.9471144E7
9 . 9471142E7
* /// ,-
Despus de asignar un objeto ByteBuffer, se comprueban sus valores para ver si el proceso de asignacin del buffer pone
a cero automticamente el contenido; como podemos ver. as sucede. Se comprueban los 1.024 valores (hasta el lmite de
buffer obtenido con Iimit( )), y veremos que todos ellos son cero.
La fonna ms fcil de insertar valores primitivos en un objeto ByteBuffer es obtener la "vista" apropiada de dicho buffer
utilizando asCharBuffer( ), asShortBuffer( ), etc. , y luego empleando el mtodo put( ) correspondiente a dicha lista.
Podemos ver en el ejemplo que ste es el proceso que se ha utilizado para cada uno de los tipos de datos primiti vos. El nico
de estos casos que resulta un poco ms extrao es el mtodo put( ) para el objeto ShortBuffcr, que requiere una proyec-
cin de datos (observe que la proyeccin trunca y modifica la proyeccin resultante). Todos los dems buffers utilizados
como vistas no requieren que se efecte ninguna proyeccin de datos en sus mtodos put( ).
Buffers utilizados como vistas
Los ubuffers utili zados como vistas" penniten examinar un objeto ByteBuffer subyacente a travs de la ventana que pro-
porciona cada tipo primitivo concreto. El objeto ByteBuffer seguir siendo el almacenamiento real que est "respaldando"
a esa vista. por lo que cualquier cambio que se realice en la vista se ver reflejado en las correspondientes modificaciones
de los datos contenidos en el objeto ByteBuffer. Como podemos ver en el ejemplo anterior, esto nos permite insertar cmo-
damente tipos primitivos en un buffer de tipo ByteBuffer. Una vista pennite tambin leer tipos primitivos a partir de un
objeto ByteBuffer, bien de uno en uno (tal como lo pemlite ByteBuffer) o por lotes (almacenando en matrices). He aqu
un ejemplo en el que se manipulan objetos int en un objeto ByteBuffer a travs de una vista IntBuffer:
// : io/lntBufferDemo.java
// Manipulacin de valores int en un objeto ByteBuffer mediante IntBuffer
import java.nio.*;
public class IntBufferDemo
private static final int BSIZE = 1024;
public static void main(String[] args }
ByteBuffer bb = ByteBuffer.allocate (BSIZE) ;
IntBuffer ib = bb.aslntBuffer(};
// Almacenar una matriz de valores int:
ib.putlnew intlJ{ 11, 42, 47, 99, 143, 811, 1016 });
// Lectura y escritura en posiciones absolutas:
System. out.println(ib . get(3)) ;
ib.putI3,1811);
1/ Establecimiento de un nuevo lmite antes rebobinar el buffer.
ib.flipl) ;
while (ib .hasRemaining ())
int i = ib.get( };
System.out .println(il;
/ * Output:
99
11
42
47
1811
143
811
1016
* ///,-
18 Entrada/salida 623
Se utili za primero el mtodo sobrecargado put( ) para almacenar una matriz de valores nt. Las siguientes ll amadas a los
mtodos ge!() y pu!() acceden directamente a una posicin in! dentro del objeto By!eBuffer subyacente. Observe que estos
accesos mediante la posicin absoluta estn tambin disponibl es para los tipos primitivos si manipulamos directamente el
objeto By!eBuffer.
Una vez rellenado el objeto ByteBuffer con objetos in! o algn otro tipo primiti vo a travs de un bllffer de vista, podemos
escribir el objeto By!eBuffer directamente en un canal. Tambin podemos, con igual fac ilidad, leer de un canal y usar un
buffer de vista para convertir todo a un tipo concreto de primitiva. He aqu un ejemplo que interpreta la secuencia de bytes
como valores short, int, 11oat, long y double generando diferentes buffers de vista para el mismo objeto ByteBufTer:
JI: io/Vi ewBuffers.java
import java.nio. *;
import static net.mindview.util.Print.*;
public class ViewBuffers {
pUblic static void main (String[] args ) {
ByteBuffer bb = ByteBuffer.wrap(
new byte [] { O, O, O, O, O, O, O, ' a' });
bb.rewind {) ;
printnb ("Byte Buffer ") ;
while(bb.hasRemaining(})
printnb {bb.position{)+ " -> " + bb.get () + " );
print () ;
CharBuffer eb =
( (ByteBuffer ) bb. rewind () ) .asCharBuffer () ;
printnb ( "Char Buffer " ) ;
while (cb.hasRemaining (
printnb(eb.position() + " -> " + eb.get() + U);
print () ;
FloatBuffer fb =
(( ByteBuffer ) bb.rewind ( .asFloatBuffer () ;
printnb ( "Float Buffer ");
while(fb.hasRemaining()}
printnb(fb.position()+ ->" + fb.get () + n);
print () ;
IntBuffer ib =
( (ByteBuffer ) bb. rewind () ) . aslntBuffer () ;
printnb("Int Buffer ");
while (ib.hasRemaining {
printnb(ib.position{)+ " -> " + ib.get() + ");
print ();
LongBuffer lb =
(( ByteBuffer ) bb.rewind ()) .asLongBuffer();
printnb ( "Long Buffer ");
while ( lb.hasRemaining (
printnb(lb.position()+ " -> " + lb.get () + ");
print () ;
ShortBuffer sb =
(( ByteBuffer )bb.rewind( )) .asShortBuffer () ;
printnb ( "Short Buffer" ) ;
while (sb.hasRemaining(
printnb (sb.position{)+ -> " + sb.get() + U) ;
print () ;
DoubleBuffer db =
(( ByteBuffer)bb.rewind( )) . asDoubleBuffer() ;
printnb ( "Double Buffer ") ;
624 Piensa en Java
while (db.hasRemaining ()
printnb(db position{)+ " -> n + db.get() + " ) i
/ * Output:
->0,1->0, Byte Buffer O
Char Buffer O ->
2->0,3-> O, 4 - > O, S - > O, 6 - > O, 7 - > 97,
, 1 ->
Float Buffer - > 0.0, 1
2 -> ,3 -> a,
-> 1.36E-43,
Int Buffer -> 0, 1 -> 97,
Long Buffer O -> 97,
Short Buffer O - > 0, 1 -> 0, 2 -> 0, 3 - > 97,
Double Buffer -> 4.8E- 322,
* /// ,-
El obj eto ByteBuffer se genera "envolviendo" una matri z de ocho bytes que a continuacin se visualiza a travs de buffers
de vista apropiados para todos los di ferentes tipos primiti vos. Podemos ver en el siguiente diagrama las di stintas formas en
que los datos aparecen cuando se los lee desde los diferent es tipos de buffers:
o I
O
O I
O
O I
O O
J
97 byte
a char
O O O 97 shOI1
O 97 int
0. 0 l.36E-43 Ooat
97 long
4.8E-322 doubl e
Estos valores se corresponderan con la salida del programa.
Ejercicio 24: (1 ) Modifique IntBufferDemo.java para utilizar valores double.
Terminaciones
Las diferentes mquinas pueden utilizar di ferentes esquemas de ordenacin de los bytes a la hora de almacenar los datos.
Las mquinas con "tem1inacin alta" (big endian) colocan el byte ms signifi cati vo en la direccin de memoria ms baja,
mi entras que las mquinas con "terminacin baja" (liule endian) colocan el byte ms significati vo en la direccin de memo-
ri a ms alta.
A la hora de almacenar una magnitud con un tamao superior a un byte, como por ejemplo int, float , etc., puede ser nece-
sario tener en cuenta la ordenacin de los bytes. Un obj eto ByteBuffer almacella los datos en fom1ato de tem1inacin alta
y los datos enviados a travs de una red siempre utilizan tenninacin alta. Podemos cambiar el tipo de tenninacin de un
obj eto ByteBuffer utili zando order() y pasando a di cho mtodo el argumento ByteOrder.BIG_ENDIAN o
ByteOrder.LITTLE_ENDIAN.
Considere un obj eto ByteBuffer que contenga los siguientes dos bytes:
b1 b2
18 Entrada/salida 625
Si leemos los datos como un valor shor! (ByteBuffer.asShortBuffer( )), obtendremos el nmero 97 (00000000 01100001 l ,
pero si cambiamos a tenninacin baja, obtendremos el nmero 24832 (O 1I 0000 1 00000000).
He aqu un ejemplo que muestra cmo se modifica la ordenacin de los bytes en los caracteres dependiendo de la termina-
cin elegida:
ji: io/Endians . java
JI Diferencias de terminacin y almacenamiento de datos .
import java . nio . *
import java . util. *
i mport static net.mindview.util . Print .*
public class Endians (
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer,wrap(new byte [12] ) i
bb . asCharBuffer() . put("abcdef " ) i
print(Arrays.toString(bb . array())) i
bb . rewind{) i
/ ,
[0,
[ O ,
[97 ,
bb . order(ByteOrder . BIG_ENDIAN) i
bb . asCharBuffer () . put ("ahcdef " ) i
print{Arrays . toString(bb . array())) ;
bb . rewind() ;
bb . order{ByteOrder . LITTLE_ENDIAN) ;
bb . asCharBuffer() .put(tlabcdefll);
print(Arrays . toString{bb.array())) i
Output :
97, 0 , 98, 0 , 99, 0, 1 0O, 0, 101,
97,
,
98, 0, 99, 0 , 1 0O , 0 , 101,
0 , 98 ,
,
99 ,
,
100 , 0 , 101 , 0,
, /// , -
,
102]
,
102]
102, O]
Al objeto ByteBuffer se le asigna suficiente espacio para almacenar todos los bytes de una matri z de caracteres, de modo
que podemos invocar el mtodo array( ) para visua lizar los bytes subyacentes. El mtodo array( l es "opcional" y slo se
puede invocar sobre un buffer que est respa ldado por una matri z; en caso contrario, se generar una excepcin
U nsu pportcdOpcration Exception.
Al visualizar los bytes subyacentes, podemos ver que la ordenacin predetenninada coincide con la impuesta por el s i s t ~
ma de tenninacin alta, mi entras que el sistema de terminacin baja invierte los bytes.
Manipulacin de datos con buffers
El diagrama de la pgina sigui ente ilustra las relaciones entre las clases nio, para poder entender mej or cmo se transfi eren
y se convierten los datos. Por ejemplo, si queremos escribir una matri z de tipo byte en un archi vo, tenemos que envolver la
matri z byte utilizando el mtodo ByteBuffcr.wrap( ), abrir un canal en el flujo FileOutputStream usando el mtodo
getChannel( ) y luego escribir los datos en el canal FileChannel a partir de obj eto By te Buffer.
Observe que ByteBuffer es la njca fonna de transferir datos hacia y desde los canales y que nosotros slo podemos crear
un buffer autnomo con un tipo de datos primiti vo, u obtener uno a partir de un objeto ByteBuffer empleando un mtodo
"as". En otras palabras, no se puede convertir un buffer con tipo de datos primiti vo en un objeto ByteBuffer. Sin embargo,
puesto que podernos transferir datos primiti vos hacia y desde un objeto ByteBuffer a travs de un buffer de vista, esta r s ~
tricci n realmente no es tal.
Detalles acerca de los buffers
Un objeto Buffer est compuesto por datos y por cuatro ndices que penniten acceder a estos datos y manipularlos eficien-
temente: marca, posicin, lmite y capacidad. Exi sten mtodos para asignar valores a estos ndices, para reinicializarlos y
para consultar su valor (vase la tabla de las Pginas 626627).
626 Piensa en Java
Sistema de archivos subyacente o red
t t
Filel npulStream Socket
FileOutputStream DatagramSocket
ce Utilidades 1)
Canales l
RandomAccessFile ServerSocket
t getChannelO
r1 write(BvteBuffer)
FileChannel
ByteBuffer I
read(ByteBuffer)
map(FileChannel.MapMode, position, size)
; ~ ;
: I MappedByteBuffer I
,
,
, ,
: Apare?8 en el espacio de :
: direCCiones del proceso :
~ _ .
~
arraYO/get(byteO)
I byteO
wrap(byteO)
1 1"
arrayO/get(charO)
.1 1
asCharBufferO
charD charBuffer
wrap(charO)
a rray( )/get( d ou bleO)
1 DoubleBuffer l
asDoubleBuffer()
I doubleo 1"
wrap(doubleO)
1
arrayO/get(ftoatO)
.1 1
asFloatBufferO
ftoatO
1-
FloatBuffer
wrap(ftoatO)
1
array(Vget(intO)
1
asl ntBuffer()
intO
1" .1
In!Buffer
wrap(intO)
1
1_
arrayO/get(longO)
.1 1
asLongBufferO
10ngO LongBuffer
wrap(longO)
arraYO/get(shortO)
1 ShortBuffer 1
asShortBuffer()
1 shortO 1-
wrap(shortO)
,. ' _ . - Codifi cacin/decodifi cacin utilizando ByteBuffer - . _ . _ . _ . _ . _ . _ . _ . _ . -',
A un flujo de bytes codificado'
Charset
encode(CharBuffer)
newDecoder()
decode(ByteBuffer)
De un flujo de bytes codificado
capacity( ) Devuelve la capacidad del bujJ"eI:
clear( ) Borra el buffer, establece la posicin en cero y asigna al lmite el valor de la capacidad.
Invocamos este mtodo para sobreescribi r un buffer existente.
Oip( ) Asigna al /imite el valor de posicin y asigna a posicin el valor cero. Este mtodo se uti-
liza para preparar el buffer para una lectura despus de haber escrito datos en l.
limit( ) Devuelve el valor del lmite.
18 Entrada/sal ida 627
Iimit(int lim) Establece el valor del/mire.
mark( ) Establece la marca en el valor correspondiente a posicin.
position( ) Devuelve el valor de posicin.
position(int pos) Establece el valor de posicin.
remaining( ) Devuelve (lmife - posicin).
hasRemai ning( ) Devuelve true si existe algn elemento entre posicin y lmite.
Los mtodos que insertan y extraen datos del buffer actualizan estos ndices para reflejar los cambios.
Este ejemplo utili za un algoritmo muy simpl e (intercambio de los caracteres adyacentes) para cifrar y descifrar los caracte-
res contenidos en un objeto CharBuffer:
/1 : ioj UsingBuffers. j ava
import java.nio.*
import static net.mindview.util.Print.*
public class Us i ngBuffers {
private static void symmetricScramble (CharBuffer buffer ) {
while (buffer. hasRemaining ( {
buffer.mark () ;
char e l = buffer.get () ;
ehar e 2 = buffer.get () ;
buffer.reset () ;
bu ffer .put (e2) .put (el ) ;
publie static void main {String (] args )
ehar() data = "UsingBuffers".toCharArray () i
ByteBuffer bb ByteBuffer.alloeate {data.length * 2 ) ;
CharBuffer eb = bb.asCharBuffer () ;
cb.put (data l ;
print (eb.rewind { i
symmetri c Sc r amble (eb ) i
print {cb.rewind {) ;
symmetrie Se ramble (cb ) i
print (cb.rewind {}} i
/ * Output:
UsingBuffers
s UniBgf uefsr
UsingBu ffers
* /// ,-
Aunque podramos generar un buffer de tipo CharBuffer directamente invocando wrap() con una matriz de tipo char,
asignamos en su lugar un objeto ByteBuffer subyacente, generndose el objeto e h.rBuffer como una vista del ByteBuffer.
Este mtodo enfatiza el hecho de que nuestro objetivo es siempre manipulado como un objeto ByteBuffer, ya que es ste
objeto el que interacta con un canal.
He aqu el aspecto del buffer a la entrada del mtodo the symmetricScramble( ):
628 Piensa en Java
La posicin apunta al primer elemento del buffer, y la capacidad y el lmite apuntan al ltimo elel11ento.
En symmetricScramble( ), el bucle while realiza una serie de iteraciones hasta que posicin es equivalente a lmite. La
posicin del buffer vara cada vez que se invoca una funciona get( ) o put( ) relativa. Tambin se pueden invocar mtodos
get( ) y put() absolutos, que incluyen un argumento de ndice que especifica la ubicacin en la que la operacin get( ) o
put( ) tienen lugar. Estos mtodos no modifican el valor de la posicin del bl/ffer.
Cuando el control entra en el bucle whi le, el valor de marca se fija utilizando una llamada a mark( ). El estado del buffer
es entonces:
Las dos llamadas a get() relativas guardan el valor de los dos primeros caracteres en las variables el y c2. Despus de estas
dos llamadas, el buffer tendr el siguiente aspecto:
Para realizar el intercambio, necesitamos escribi r e2 en posicin = O Y el en posicin = l. Podemos emplear el metodo abso-
luto de insercin para conseguir esto, o asignar a posicin el valor de marca, que es precisamente lo que hace el mtodo
reset( ):
Los dos mtodos put() escriben e2 y luego el :
Durante la siguiente iteracin del bucle, se asigna a marca el valor actual de posicin:
18 Entrada/sal ida 629
El proceso contina hasta que se ha recorrido todo el buffer. Al final del bucle while, posicin apuntar al final del buffer.
Si imprimimos el buffer, slo se imprimirn los caracteres entre posicin y imite. Por tanto, si querernos mostrar el Conte-
nido compl eto del buffer, deberemos fijar la posicin al principio del buffer uti lizando rewind( ). He aqu el estado del
buffer despus de la ll amada a rewnd( ) (el valor de marca pasa a ser indefinido):
Cuando se invoca de nuevo la funcin symmetricScramble( ), el buffer CharBuffer pasa por el mi smo proceso y se res-
taura a su estado original.
Archivos mapeados en memoria
Los archi vos mapeados en memoria penniten crear y modificar archivos que sean demasiado grandes como para cargarlos
en memoria. Con un archi vo mapeado en memoria, podemos aCnlaf como si todo el archi vo se encontrara en la memoria y
podemos acceder a l tratndol o simplemente como si fuera una matriz de muy gran tamao. Esta tcnica si mplifica enor-
memente el cdi go que es necesario escribir para poder modi fi car el archi vo. He aqu un ejemplo si mple:
11 : io/LargeMappedFiles.java
II Creacin de un archivo de muy gran tamao
II utilizando el mapeado de memoria.
// {RunByHand}
import java .nio .*;
import java.nio.channels.*
import java .io.*;
i mport static net.mindview.util.Print.*
public class LargeMappedFiles {
static int length = Ox8FFFFFF; 1I 128 MB
public static void main(String[] args) throws Exception
MappedByteBuffer out =
new RandomAccessFile ( " test. dat ", "rw" ). getChannel ()
.map(FileChannel.MapMode.READ_WRITE, O. length);
for(int i = O; i < length; i++)
out .put ((byte) 'x');
print ("Finished writing") ;
for (int i = length/2; i < length/2 + 6; i++)
printnb ( (char)out .get (il);
}
/// , -
Para efectuar tanto leculras como escrituras, comenzamos con un objeto RandomAccessFile, obtenemos un canal para
dicho archivo y luego invocamos map( ) para generar un bnffer MappedByteBufTer, que es un tipo particular de bujIer
directo. Observe que es necesari o espec ificar el punto de inicio y la longitud de la regin en la que queramos mapear el
archivo; esto implica que tenemos la posibilidad de mapear regiones pequeas de un archivo de gran tamao.
MappedByteBuffer hereda de ByteBufTer, asi que dispone de todos los mtodos de dicha clase. Aqu slo mostramos los
usos ms simples de put( ) y get( ), pero tambin podemos empl ear mtodos como asCharBuffer( ), etc.
El archi vo creado en el programa anterior tiene 128 MB de longitud, lo cual es un tamao probablemente mayor de lo que
el sistema operati vo pennitir residi r en memoria en cualquier momento detenninado. El archivo parece estar completamen-
te accesible, aunque en realidad slo se cargan en memoria partes del mi smo, intercambindose por otras partes a medida
que es necesario. De esta fonna, puede modificarse fcilmente un archivo de muy gran tamao (hasta dos 2 GB). Observe
que se utiliza la funcionalidad de mapeo de archivos del sistema operativo subyacente para maximizar el rendimiento.
630 Piensa en Java
Rendimiento
Aunque el rendimiento del "antiguo" mecanismo de flujos de datos de E/S se ha mejorado al implementarlo con oi o, el acce-
so a archivos mapeados tiende a ser muchisimo ms rpido. Este programa hace una comparativa simple de rendimiento:
11 , io/MappedIO.java
import java.na. ;
import java.nio.channels.*
import java.io. *
public class MappedIO
private static int numOflnts = 4000000;
private static int numOfUbufflnts = 200000;
private abstraet static class Tester {
private String name;
public Tester (String name) { this. name
public void runTest () {
name; }
System.out.print(name + ": ");
try (
long start = System.nanoTime ();
test ();
double duratan = System. nanoTime{) - start
System.out.format("%.2f\n", duration/l.Oe9);
catch (IOExeeption e) {
throw new RuntimeException(e);
public abstract void test() throws IOExeeption;
private statie Tester[] tests = {
new Tester (" Stream Wri te") {
),
publie void test () throws IOException
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream (new File (lttemp. tmplt) ) ;
for(int i = O; i < numOflnts; i++)
dos.writelnt(i) ;
dos.clase() ;
new Tester (ItMapped Write") {
),
public void test() throws IOException
FileChannel fe =
new RandomAceessFile(lItemp.tmplt, "rw")
.getChannel() ;
IntBuffer ib = fc.map(
FileChannel.MapMode.READ_WRITE, O, fC.size(
.aslntBuffer() ;
for(int i = O; i < numOflnts; i++)
ib.put(i ) ;
fe.close() ;
new Tester("Stream Read
lt
) {
public void test() throws IOExeeption {
DatalnputStream dis = new DatalnputStream(
new BufferedlnputStream(
new FilelnputStream (lttemp. tmp") ;
for(int i = O; i < numOflntsi i++)
dis.readlnt() ;
dis.close() i
)
) ,
new Tester{"Mapped Read") {
public void test() throws IOException (
FileChannel fe = new FilelnputStream(
new File (lItemp .tmpll ) ) .getChannel();
IntBuffer ib = f c.map(
FileChannel . MapMode.READ_ONLY, O, fC.size{
. asIntBuffer () ;
while (ib.hasRemaining () )
ib.get();
fe. close () ;
)
).
new Tester ( "Stream Read/Write") {
public vold test() throws IOException
RandomAccessFile raf = new RandomAccessFile(
new File("temp.tmp ll), "rw");
raf .writelnt (1);
for (int i = O; i < numOfUbufflnts; i++) {
raf. seek 1 raf .length () - 4 );
raf.writelnt(raf,readlnt( ;
raf.close() ;
)
) ,
new Tester ( "Mapped Read/Write") {
)
) ;
public void test() throws IOException
FileChannel fe = new RandomAccessFile(
new File("temp.tmp"), "rw") .getChannel();
IntBuf fer ib = fc.map (
FileChannel.MapMode.READ_WRITE, 0, fC.size(
.asIntBuffer() ;
ib.put(O) ;
for(int i = 1; i < numOfUbuffInts; i++)
ib.putlib.getli - 1));
fe. close () ;
public static void main(String [] args) {
for(Tester test : tests)
test.runTest() ;
/ * Output, 190\ match)
Stream Write: 0.56
Mapped Write: 0.12
Stream Read: 0.80
Mapped Read: 0.07
Stream Read/ write: 5.32
Mapped Read /Write: 0.02
* /// , -
18 Entrada/salida 631
Como hemos visto en ejemplos anteriores de este libro, runTest() se utiliza con el mtodo de plantillas para crear un marco
de pruebas para diversas implementaciones de test( ) definidas en subclases internas annimas. Cada una de estas subcla-
ses realiza un tipo de prueba, por lo que los mtodos tcst( ) tambin nos proporcionan un prototipo para realizar las distin-
tas actividades de E/S.
632 Piensa en Java
Aunque podra parecer que una escritura mapeada debera utilizar un flujo FileOutputStream, todas las operaciones de sali-
da en el mecani smo de mapeo de archivos deben utili zar un objeto RandomAccessFile, al igual que se hace con las opera-
ciones de lectura/escritura en el programa anterior.
Observe que los mtodos tcst() incluyen el tiempo necesario para inicializar los di stintos objetos de E/S, de modo que aun-
que la configuracin de los archivos mapeados puede requerir un gasto de procesami ento considerable, la ganancia global
de velocidad. por comparacin con la E/S basada en flujos de dat os resulta significati va.
Ejercicio 25: (6) Experimente cambiando las instrucciones ByteBuffer.aUocate( ) de los ejemplos de este capitulo por
8yteBuffcr.allocateDirect(). Demuestre las diferencias de rendimi ento que existen, pero observe tambi n
si el ti empo de arranque de los programas se modifica de manera perceptible.
Ejercicio 26: (3) Modifique strings/JGrep.java para utili zar archivos mapeados en memoria al estilo nio de Java.
Bloqueo de archivos
El bloqueo de archi vos pennite sincroni zar el acceso a un archivo utili zado como recurso compartido. Sin embargo, las dos
hebras que compiten por el mismo archivo pueden estar en diferentes mquinas virtuales Java, o bien una de ellas puede ser
una hebra de programaci n Java y la otra puede ser alguna hebra nativa del sistema operati vo. Los bloqueos de archivo son
visibl es para otros procesos del sistema operativo, porque el mecani smo de bloqueo de archivos de Java se mapea
mente sobre la funcionalidad de bloqueo nati va del sistema operativo.
He aqui un ejemplo simple de bloqueo de archivos.
11 : io/FileLocking.java
import java.nio.channels. * ;
import java .util.concurrent.*
import java.io. *;
public class FileLocking
public static void main(String[] args) throws Exception {
FileOutputStream fos= new FileOutputStream(ltfile.txt
n
) i
FileLock fl = fos. getChannel () . tryLock () i
if (fl '= null) {
System. out.println("Locked File" );
TimeUnit . MILLISECONDS.sleep(lOO) ;
fl. release () ;
System. out . println("Released Lock"J i
fos.close() i
1* Output:
Locked File
Released Lock
* ///,-
Podemos obtener un bloqueo (FileLock) sobre el archi vo completo invocando tryLock() o lock() sobre un objeto
FileChannel. (SocketChannel, DatagramChannel y ServerSocketChannel no necesitan bloqueos, ya que son inherente-
mente entidades de un nico proceso, generalmente un socket de red no se comparte entre dos procesos). tryLock( ) es no
bloqueante: este mtodo trata de establecer el bloqueo, pero si no puede (porque algn otro proceso ya ha establecido el
mismo bloqueo y ste no es de tipo compartido), simplemente se limita a volver del mtodo tenninando as la llamada.
lock() se bloquea hasta que adquiere el bloqueo indicado, o hasta que se interrumpe la hebra que ha invocado lock( ),
o hasta que se cierra el canal para el cual se ha invocado el mtodo lock( ). Un bloqueo se libera utilizando FileLock.
release( ).
Tambin es posible bloquear una parte del archivo con:
tryLock{long posicin, long tamao, boolean compartido)
o
lock(long posicin, long tamao, boolean compartido)
18 Entrada/salida 633
que bloquea la regin (ta ma o - posicin). El tercer argumento especifica si este bloqueo es compartido.
Aunque los mtodos de bloqueo que no utilizan argumentos pueden adaptarse a los cambios en el tamao de un archivo, los
bloqueos con un tamao fijo no se modifican cuando cambia el tamao del archivo. Si se establece un bloqueo para una
regin comprendida entre posicin y posici n + t amao y el archivo se incrementa ms all de posici n + t ama o, enton-
ces la seccin situada despus de posicin + tama o no estar bloqueada. Los mtodos de bloqueo que no utili zan argu-
mentos bloquean el archivo completo, incluso si ste aumenta de tamalio.
El soporte para los bloqueos exclusivos o compartidos debe ser proporci onado por el sistema operativo subyacente. Si el
sistema operativo no soporta los bloqueos compartidos y se solicita uno de estos bloqueos, en su lugar se emplea un blo-
queo exclusivo. El tipo de bloqueo (compartido o exclusivo) puede consultarse utilizando FileLock.isShared().
Bloqueo de partes de un archivo mapeado
Como hemos mencionado anterionnente, el mecani smo de mapeo de archivos se utiliza principalmente para archivos de
muy gran tamaiio. Puede que necesitemos bloquear partes de dicho archivo de gran tamaiio, de modo que se pennita a otros
procesos modifi car pa11es del archivo no bloqueadas. Esto es lo que sucede, por ejemplo, con una base de datos. de tal mane-
ra que sta pueda ser utili zada por ITIuchos usuarios a la vez.
He aqu un ejemplo con dos hebras de programacin, cada una de las cuales bloquea una parte di stinta de un archivo:
11 : io/LockingMappedFiles.java
II Bloqueo de partes de un archivo mapeado .
// (RunByHand)
import java.nio.*;
import java . nio.channels.*
import java.io.*;
public class LoekingMappedFiles
static final int LENGTH Ox8FFFFFF II 128 MB
static FileChannel fe;
public static void main(String[] args) throws Exception
fe =
new RandomAecessFile("test.dat", tlrw
tl
) .getChannel () ;
MappedByteBuffer out =
fc.map(FileChannel.MapMode.READ_WRITE, O, LENGTH)
far (int i = Di i < LENGTH i++}
out . put ( (byte) 'x I ) i
new LockAndModify(out, O, O + LENGTH/3)
new LockAndModify(out, LENGTH/2, LENGTH/2 + LENGTH/4);
private static class LockAndModify extends Thread {
private ByteBuffer buff;
private int start, end;
LockAndModify(ByteBuffer mbb, int start, int end) {
this.start start;
this. end = end;
mbb. limit (end) ;
mbb.position(start) i
buff o mbb . slice();
start() ;
public void run() {
try (
/1 Bloqueo exclusivo sin solapamiento:
FileLock fl :::: fe . lock (start, end, false)
System.aut.println{"Locked: "+ start +" ta "+ end);
II Realizar modificacin:
while(buff.pasition() < buff.limit() 1)
buff . put ((byte) (buff .get () + 1));
634 Piensa en Java
fl.release () ;
System.out.println ( "Released: "+start+" to "+ end) i
catch (I OException e l {
throw new Runt imeException (e ) ;
La clase de hebra LockAndModify confi gura la regin del buffer y crea con slice() un fragmento para modifi carl o. En
run(), se establece el bloqueo sobre el canal del archi vo (no se puede establecer un bloqueo sobre el buffer, slo sobre el
canal). La ll amada a lock() es muy simil ar a establ ecer un bl oqueo de un obj eto en una hebra de programacin: despus de
la llamada di spondremos de una seccin crti ca con acceso exclusivo a di cha parte del archi vo.
5
Los bloqueos se liberan automti camente cuando tennjna la ej ecucin de la mquina JVM o cuando se cierra el canal para
el que se hayan establecido los bloqueos, pero tambin se puede invocar explcitamente release() sobre el obj eto FileLock
como se muestra en el ejemplo.
Compresin
La biblioteca de E/S de Java conti ene clases que permiten manejar fluj os de lectura y escri tura en fom13to comprimido. Estas
cl ases se envuelven en otras clases de E/S para proporcionar la funcionalidad de compresin.
Estas clases no deri van de las cl ases Reader y \Vriter, sino que forman parte de las jerarquas InputStream y
OutputStream. Esto se debe a que la bibli oteca de comprensin trabaja con bytes, no con caracteres. Sin embargo, a veces
podemos vernos forzados a mezclar los dos tipos de fluj os (recuerde que puede utili zar InputStreamReader y
OutputStreamWriter para proporcionar un mecani smo senci ll o de conversin entTe un tipo y otro).
Clase de compresin Funcin
Checkedlnl1utStream GetCheckSum() proporciona la suma de comprobacin para cualquier objeto InputSlream
(no si mplemente descompresin).
CheckedOutputStream GetChcckSum() proporciona la suma de comprobacin para cualqui er objeto OutputStream
(no si mplemente compresin).
DenaterOutputStream Clase base para clases de comprensin.
ZipOutputStream UIl fl ujo DeflaterOutputStream que comprime datos en el fonnato de archivos Zip.
GZIPOutputStream UIl fl uj o DenaterOutputStream que compri me datos en el fonnato de archivos GZLP.
lnflaterl nputStream Clase base para clases de descomprensin.
ZiplnputStream Un flujo InflaterlnputStream que descompri me los datos que hayan sido al macenados en el
formato de archi vos Zip.
GZIPlnputStrcam Un flujo lnflaterlnputStream que descomprime los datos que hayan sido almacenados en el
fomlato de archi vos GZI P.
Aunque exi sten muchos algoritmos de compresin, Zip y GZrP son posiblemente los ms comnmente utili zados. De este
modo, podemos manipular fcilmente los datos comprimidos con muchas de las herramientas di sponibles para la lectura y
escritura de estos fonnatos de archi vo.
5 Puede encontrar ms detalles acerca de las hebras de programacin en el Capitulo 21, Concurrencia.
18 Entrada/salida 635
Compresin simple con GZIP
La interfaz GZIP es simple y resuha, por tanto, apropiada cuando disponemos de un nico flujo de datos que queramos com-
primir (en lugar de un contenedor de fragmentos de datos poco similares). He aqu un ejemplo en el que se comprime un
nico archivo:
//: io/GZIPcompress.java
II {Args, GZIPcompress.java}
import java.util.zip.*
import java.io.* ;
public class GZIPcompress
public static void main(String[) argsl
throws IOException {
if (args .length == O) {
System.out.println(
"Usage: \nGZIPcompress file\n" +
n\tUses GZIP compression to compres S " +
"che file ta test.gz
ll
);
System.exit(l) ;
BufferedReader in = new BufferedReader(
new FileReader(args[Ol});
BufferedOutputStream out = new BufferedOutputStream(
new GZIPOutputStream(
new FileOutputStream ( 11 test. gz 11) ) ) ;
System.out.println(IIWriting file");
int C;
whilec = in.read()) != -1)
out .write(c) i
in. close () ;
out.close() ;
System. out. println ("Reading f ile
U
) ;
BufferedReader in2 = new BufferedReader(
new InputStrearnReader(new GZIPlnputStream(
new FilelnputStream (11 test . gz") ) ) l ;
String Si
whiles = in2.readLine()) != null)
System.out.println(s) i
/* (Execute to see output) * ///:-
La uti li zacin de Jas clases de compresin resulta sencilla; basta con envoJver el flujo de salida en un objeto GZIPOutput-
Stroam o ZipOutputStream, y eJ Oujo de datos de entrada en un objeto GZIPlnputStream o ZiplnputStream. Todo Jo
dems son lecturas y escrituras de E/S nom1ales. ste es un ejemplo de mezcla de los flujos de datos ori entados a caracte-
res con Jos Oujos de datos orientados a bytes; in utiliza Jas clases Reader, mientras que el constructor de GZIP-
OutputStr.am sJo puede aceplar un objeto OutpuIStre.m, no un objeto Writer. Cuando se abre eJ archivo, el objeto
GZIPlnputStre.m se convierte en un objeto Reador.
Almacenamiento de mltiples archivos con Zip
La biblioteca que soporta eJ fonnato Zip es ms ampli a. Con este fonnato, podemos almacenar fcilmente mltipJes archi-
vos y existe incluso una clase separada para facilitar el proceso de lectura de un archivo Zip. La biblioteca utiliza el fonna-
to Zip estndar, as que funciona de fonna transparente con todas las herramientas Zip que podemos descargar actualmente
a travs de Internet. El siguiente ejemplo ti ene la misma fonna que el ejempl o anterior, pero pennite tratar tantos argumen-
tos de la lnea de comandos como queramos. Adems, ilustra el uso de las clases Checksum para calcular y verificar la suma
de comprobacin del archivo. Existen dos tipos de cJases Checksum: AdJer32 (que es Ja ms rpida) y CRC32 (que es ms
lenta, pero ligeramente ms precisa).
636 Piensa en Java
ji : ioj ZipCompress.java
1/ Utiliza compresin Zip para comprimir cualquier
1/ nmero de archivos que se indique en la lnea de comandos.
II {Args, ZipCompress.java}
import java.util.zip.*
import java.io.*;
import java . util.*
import statie net.mindview.util.Print . *
public cIass ZipCompress {
public statie void main (String[] args )
throW5 IOException {
FileOutputStream f = new FileOutputStream (11 test. zipl!) i
CheckedOutputStream esum =
new CheckedOutputStream(f, new Adler32 ( ;
ZipOutputStream zas = new ZipOutputStream{csum) ;
BufferedOutputStream out =
new BufferedOutputStream( zos ) ;
zas. setComment ( "A test of Java Zipping" ) ;
JI Sin embargo, no hay un mtodo getComment (} correspondiente .
f or (String arg : argsl {
print {IIWriting file" + arg l ;
BufferedReader in =
new BufferedReader(new FileReader(arg
zos.putNextEntry (new ZipEntry {arg})
int c
while c = in.read ( != -1 )
out.write {c) i
in . close () i
out.flush {)
out. clase () ;
II La suma de comprobacin slo es vlida
II despus de cerrar el archivo!
print ( "Checksum: " + csum. getChecksum{ ) .getValue ( i
II Ahora extraer los archivos:
print ( uReading file" ) ;
FilelnputStream fi = new Filelnput Stream ( " test. zip" ) i
CheckedlnputStream csumi =
new CheckedlnputStream(fi, new Adler32 ( i
ZiplnputStre am in2 = new ZiplnputStream(csumi )
BufferedlnputStream bis = new BufferedlnputStream(in2 ) i
ZipEntry ze;
while ze = in2 .getNextEntry () ! = null ) {
print ( "Reading file 11 + ze ) ;
int x
while x = bis . read ( ! = -1 )
System.out.write (x ) ;
i f (args.length = = 1)
print ( "Checksum: 11 + csumi.getChecksum() .getValue ()} ;
bis.close ()
II Forma alter nativa de abrir y leer archivos Zip:
ZipFile zf = new ZipFile ( "test.zip" ) ;
Enumeration e = zf . entries ()
while (e. hasMoreElements ( {
ZipEnt r y ze2 = (Zi pEntry) e . nextElement () ;
18 Entrada/sal ida 637
print(UFile: " + ze2 ) i
// ' .. y extraer los datos como antes
/ * if (args.length == 1) * /
/ * (Execute to see outpUC) * / / 1:-
Para cada archivo que baya que aadir al archivo comprimido, es preciso invocar putNextEntry( ) y pasarle al mtodo un
objeto ZipEntry. El objeto ZipEntry contiene una amplia interfaz que permite consultar y configurar todos los datos
disponibles en esa entrada concreta del archivo Zip: nombre, tamaiios comprimido y sin comprimi r, fecha, suma de
comprobacin eRe, campo adicional de datos, comentario, mtodo de compresin e indicador de si se trata de un direc-
torio. Sin embargo, an cuando el f0n11ato Zip dispone de una fonna para establecer una contrasea, esta caracterstica no
est soportada en la biblioteca Zip de Java. Y aunque CheckedloputStream y CheckedOutputStream soportan las sumas
de comprobacin Adler32 y CRC32, la clase ZipEntry slo proporciona una interfaz para CRC. sta es una restri ccin
del fonnato Zip subyacente, pero se trata de una restriccin que puede impedirnos uti li zar la clase Adlcr32 que es ms
rpida.
Para extraer los archivos, ZiplnputStream dispone de un mtodo getNextEntry( ) que devuel ve la siguiente entrada
ZipEntry, si es que existe alguna. Como altelll ativa ms sucinta, podemos leer el archi vo utilizando un objeto ZipFile, que
dispone de un mtodo entries( ) para devol ver un objeto de tipo Enumeration con las entradas del archi vo Zip.
Para leer la suma de comprobacin, debemos conseguir acceder de alguna forma al objeto Checksum asociado. En el ejem-
plo, retenemos una referencia a los objetos CheckedOutputStream y CheckedlnputStream, pero tambin podramos
habemos limitado a conservar una referencia al objeto Checksum.
Un mtodo bastante absurdo dentro de la biblioteca Zip es setComment( j. Como se muestra en ZipComprcss.java, pode-
mos fijar un comentario a la hora de escribi r un archivo, pero no existe forma de recuperar el comentario en el objeto
Ziplnput5tream. Parece que los comentari os slo se soportan de manera completa accediendo entrada por entrada median-
te ZipEntry.
Por supuesto. no tenemos por qu Limitarnos a emplear archivos a la hora de utili zar las bibliotecas GZIP o Zip; podemos
comprimi r cua lquier cosa, incluyendo datos que vayan a enviarse a travs de una conexin de red.
Archivos Java (JAR)
El formato Zip tambin se utili za en el formato de archivo JAR (Java ARchive), que es una fonna de recopilar un grupo
de archivos en un nico archivo comprimido, igual que Zip. Sin embargo, corno todos los dems componentes de Java, los
archivos lAR son archivos int erplatafonna, as que no hay necesidad de preocuparse acerca de los problemas de portabili-
dad. Pueden incluirse tambi n archi vos de audio y de imagen, adems de los archivos de clases.
Los archivos JAR resultan particularmente ti les a la hora de trabaj ar con Internet. Antes de que aparecieran los archivos
JAR, el explorador web tena que reali zar solicitudes repet idas a un servi dor web para poder descargar todos los archi-
vos que confom1aban un appler. Adems, estos archivos no estaban comprimidos, al combinar todos los archi vos de un
applel concreto en un nico archivo JAR, slo es necesaria una solicitud al servidor y la transferencia se reali za ms rpi-
damente, grac ias a la compresin. Adems, la entrada de un archivo JAR puede estar firmada digitalmente para aumentar
la seguridad.
Un archi vo JAR est compuesto por un nico archivo que contiene una coleccin de archivos comprimidos en fonnato Zip,
junto con un "manifiesto" que los describe (podemos crear nuestro propio manifiesto, pero si no lo hacemos, el programa
jar lo har por nosotros). Puede encontrar ms infonnac in acerca de los manifi estos JAR en la documentacin del JDK.
La utilidad jar incluida en el IDK de Sun comprime automticamente los archi vos que elijamos. Esta utilidad se invoca
mediante la lnea de comandos:
jar [opciones] destino [manifiesto] archivo(s ) de entrada
Las opciones son simplemente un conjunto de letras (no hace falta ningn guin ni ningn otro s mbolo indicador). Los
usuarios de Unix/ Linux se percatarn de la similitud que existe con las opciones de taro Las opciones disponibl es son:
638 Piensa en Java
e Crea un archivo nuevo o vaco.

Muestra la tabla de contenido .
x Extrae lodos los archivos.
x archivo Extrae el archivo indicado.
r Comunica al programa: "Voy a proporcionarte el nombre del archivo", Si no se usa esta opcin, jar
presupone que su enl rada procede de la entrada estndar, 0, si est creando un archivo, que su sali-
da ir a la sal ida estndar.
m Especifi ca que el primer argumento va a ser el nombre del archivo de manifiesto creado por el
usuario.
,-
Genera una salida ms prolija que describe lo que jar est haciendo.
O Slo almacena los archivos, sin comprimirlos (utilice esta opcin para crear un archivo JAR que
pueda incluir en su ruta de clases).
M No crea automticamente un archivo de manifiesto.
Si se incluye un subdirectorio dentro de los archivos que hay que insertar en el archivo JAR, dicho subdirectorio se aade
automticamente incluyendo todos sus subdirectorios, etc. La infonnacin de ruta tambin se preserva.
He aqui algunas fonna .ipicas de invocar jaro El siguiente comando crea un archivo JAR denominado myJar File.jar que
contiene todos los archivos de clases del directorio actual, junto con un archivo de manifiesto generado automticamente:
jar cf rnyJarFile.jar *. class
El siguiente comando es como el del ejemplo anterior, pero aade un archivo de manifiesto creado por el usuario que se
denomina rnyManifestFile.mf:
jar cmf myJarFile. j ar myManifestFile.mf *.class
Este otro comando genera una tabla de contenidos de los archivos en myJarFHe.jar:
jar tf myJarFile.jar
En el siguiente comando se aade la opcin de salida "verbosa", para proporcionar infonnacin ms detallada acerca de los
archivos myJarFile.jar:
jar tvf myJarFil e .jar
Suponiendo que audio, classes e image sean subdirectorios, el siguiente comando combina todos los subdirectori os dentro
del archivo myApp.jar. Tambin se incluye la opcin "verbosa" para obtener infonnacin adicional sobre un proceso mi en-
tras que est trabajando el programa jar:
jar cvf rnyApp. j ar audio classes image
Si se crea un archivo JAR utilizando la opcin O (cero), dicho archivo puede incluirse en la variable CLASSPATH:
CLASSPATH=" libl. j ar i lib2 . jar i "
Con esto, Java podr explorar libl.jar y Iib2.jar en busca de archivos de clase.
La herramienta j ar no es de propsi to tan general como una utilidad Zip. Por ejemplo, no podemos aadir archi vos a un
archivo JAR ni actualizar los archivos existentes; los archivos JAR slo pueden crearse partiendo de cero. Asin1i smo, tam-
poco se pueden desplazar archivos a un archi vo JAR, borrando los originales a medida que se los desplaza. Sin embargo,
un archivo JAR creado en una plataforma podr ser ledo transparente mente por la herramienta jar en cualquier otra plata-
fonna (evitndose as uno de los probl emas que en ocasiones afecta a las uti lidades Zip).
Como veremos en el Captulo 22, me/faces grficas de usuario, los archivos JAR se utili zan para empaquetar componen-
tes JavaBeans.
18 Entrada/salida 639
Serializacin de objetos
Cuando se crea un objeto, ste existe durante todo el tiempo que se le necesite, pero siempre deja de existir en cuanto el pro-
grama termina. Aunque esto tiene bastante sentido a primera vista, existen situaciones en las que sera enonnemente ti l que
un programa pudiera existi r y almacenar su informacin incluso cuando el programa no se estuviera ejecutando. Si esto fuera
as, la siguiente vez que iniciramos el programa, el objeto ya se encontrara all y contendra la misma infonnacin que
tuviera la vez anterior que se ejecut el programa. Por supuesto, podemos conseguir un efecto similar escribiendo la infor-
macin en un archivo o en una base de datos, pero si tratamos de mantener el espritu de que todo sea un objeto, resultara
bastante conveniente poder declarar un objeto como "persistente", y que el sistema se encargara de resolver todos los deta-
lles por nosotros.
El mecanismo de serializacin de objetos de Java nos pennite tomar cualquier objeto que implemente la interfaz
Serializable y transfonnarlo en una secuencia de bytes que pueda restaurarse posterionnente de modo completo, para rege-
nerar el objeto original. Esto es as incluso si estamos trabajando a travs de una red, lo que significa que el mecani smo de
serializacin trata de compensar automticamente las diferencias existentes en los di stintos sistemas operativos. En otras
palabras, podemos crear un objeto en una mquina Windows, serializarlo y enviarl o a travs de la red a un mquina Unix,
donde podr ser correctamente reconstruido. No es necesario preocuparse acerca de las representaciones de los datos en las
distintas mquinas, de la ordenacin de bytes, ni de cualquier otro detall e.
En s misma, la seri alizacin de objetos resulta interesante porque nos permite implementar lo que se denomina persisten-
cia ligera. El concepto de persistencia quiere decir que el ti empo de vida de un objeto no est detem1inado por si un pro-
grama se est ej ecutando; el objeto contina existiendo entre sucesivas invocaciones del programa. Tomando W1 objeto
serializable, escribindolo en disco para posterionnente restaurar dicho objeto cuando se vuelva a invocar el programa,
podemos obtener el efecto de persistencia. La razn por la que a esa persistencia se le denomina "ligera" es que no pode-
mos limitarnos simplemente a definir un objeto utili zando algn tipo de palabra clave "persistent" y dejar que el sistema
se ocupe de los detalles (aunque quiz pueda hacerse esto en el futuro) . En lugar de ello. podemos serial izar y des-seriali-
zar explcitamente los objetos en nuestro programa. Si necesitamos lm mecani smo de persistencia ms seri o, considere la
uti lizacin de alguna herramienta como (hllp:l/hibernate.sourceforge.net). Para obtener ms detalles, consulte Thinking in
Enterprise Java, que se puede descargar en la direccin H'W\V. MindVieH'.nel.
La serializacin de objetos se aadi al lenguaje para soportar dos caractersticas principales. El mecanismo RMI (Rernote
Me/llod Invoco/ion, invocacin remota de mtodos) de Java pennite que los objetos que residen en otras mquinas se com-
porten como si estuvieran en nuestra propia mquina. Cuando se envan mensajes a los objetos remotos, la serializacin de
objetos es necesaria para transportar los argumentos y los valores de retomo. El mecanismo de RM] se analiza en Thinking
in Enterprise Java.
La serializacin de objetos tambin es necesaria para el sistema de componentes JavaBeans, descrito en el Captulo 22,
me/faces grficas de uSI.lQr;o. Cuando se utiliza un componente Bean, su infom1acin de estado suele configurarse, gene-
ralmente, en tiempo de diseo. Esta infonnacin de estado debe almacenarse, para poder recuperarse posteriormente cuan-
do se inicie el programa; el mecanismo de serializacin de objetos se encarga de esta tarea.
La seriali zacin de un objeto es una tarea bastante simple, siempre y cuando el objeto implemente la interfaz Serializable
(sta es una interfaz marcadora que no incluye ningn mtodo). Cuando se aadi la serial izacin al lenguaje, se modifica-
ron muchas clases de la biblioteca estndar para hacerlas serial.izables. incluyendo todos los envoltori os de los tipos primi-
tivos, todas las clases contenedoras y muchas otras. Incluso los objetos Class pueden serial izarse.
Para serial izar un objeto, creamos algn tipo de objeto OutputStream y luego lo envolvemos dentro de un objeto
ObjectOutputStream. Con esto, lo nico que necesitamos es invocar writeObjcct( ), y el objeto se serializar y se envia-
r al flujo de sal ida OutputStream (la serializacin de objetos est orientada a bytes, por 10 que uti liza las jerarquas
InputStream y OutputStream). Para inverti r el proceso, envolvemos un objeto InputStream dentro de un objeto
Objectlnput5tream e invocamos readObject() . Lo que este mtodo nos devuelve es, como de costumbre, una referencia
a un objeto generalizado de tipo Object, con lo que es necesario realizar una especializacin para que todo funcione correc-
tamente.
Un aspecto particulannente inteli gente del mecanismo de serializacin de objetos es que ste no slo guarda una imagen de
nuestro objeto, sino que tambin sigue todas las referencias contenidas en nuestro objeto y guarda esos objetos, siguiendo a
su vez todas las referencias de cada uno de esos objetos, etc. Esto se denomina en ocasiones la "red de objetos" a la que un
nico objeto puede estar conectado, e incluye matrices de referencias a objetos, adems de objetos miembros. Si tuviramos
640 Piensa en Java
que mantener nuestro propio esquema de seriali zacin de objetos, mant ener el cdigo para poder seguir todos nuestros vn-
culos sera enanncmente dificil. Sin embargo, el mecanismo de seriali zacin de obj etos de Java parece poder reali zar esta
tarea de manera muy preci sa, utili zando sin ninguna duda algn algoritmo optimi zado que recorre la red de objetos. El
siguiente ejemplo pennite probar el mecanismo de seriali zacin utili zando una cadena de objetos vinculados, cada uno de
los cuales tiene un enlace al siguiente segmento de la cadena, as C0l110 una matri z de referencias a objetos de una clase di s-
tinta, Data:
1/: io/Worm.java
/1 Ilustra el mecanismo de serializacin de objetos.
import java.io.;
import java.util. * ;
import static net . mindview.util.Print.*;
class Data implements Serializable
private int n;
public Data(int n ) ( this.n = n;
public String toString () { return Integer . toString (n ) ; }
public class Worm implements Serializable {
private static Random rand = new Random(47);
private Data[] d = (
};
new Data{rand . nextlnt(lO)),
new Data(rand.nextlnt(10)),
new Data(rand.nextlnt(lO)
private Worm next;
private char c;
II Valor de i == nmero de segmentos
public Worm(int i, char x l {
print{"Worm constructor: " + i);
e = X;
if(--i :> O)
next new Worm{i, (char) (x + 1));
public Worm () {
print("Default constructor"};
public String toString()
StringBuilder result
result.append{c) ;
resul t. append (" (") ;
for(Data dat : d)
new StringBuilder(":") i
result.append(dat) i
result. append ( " ) ti ) ;
if(next != null)
result.append(next) ;
return result.toString{);
public static void main(String[J argsl
throws ClassNotFoundException, IOException
Worm w = new Worm(6, 'a');
print("w = " + w);
ObjectOutputStream out = new ObjectOut putStream(
new FileOutputStream(ltworm.outltl l ;
out. writeObj ect ("Worm storage\n");
out.writeObject{wl;
out.close{) i II Tambin vaca la salida
/ ,
Worm
Worm
Worm
Worm
Worm
ObjectlnputStream in = new ObjectlnputStream(
new FilelnputStream("worm.out")) i
String s = (Stringlin . readObject();
Worm w2 = (Worm)in.readObj ect () i
print (s + "w2 = " + w2) i
ByteArrayOutputStream bout =
new ByteArrayOutputStream();
ObjectOutputStream out2 = new ObjectOutputStream(bout) i
out2 .writeObject(tlWorm storage\n");
out2 .wri teObject(w) j
out2 . flush () ;
ObjectlnputStream in2 = new ObjectlnputStream(
new ByteArraylnputStream(bout.toByteArray{))) j
S = (Stringlin2 . readObject();
Worm w3 (Worm)in2.readObject();
print (s + "w3 = ti + w3);
Output:
constructor: 6
constructor: S
constructor: 4
constructor: 3
constructor: 2
Worrn constructor: 1
w = ,a(853) ,b(1l9) ,c(802) ,d(788) ,e (199) ,f(881)
Worm storage
w2 = ,a(853) ,b(1l9) ,c(802) ,d (788) ,e(199) ,f(881)
Worm storage
w3 = ,a(853) ,b(1l9) , c(802) ,d (788) ,e(199) ,f(881 )
* ///,-
18 Entrada/salida 641
Para que las cosas sean interesantes, la matri z de objetos Data contenida en la cadena Worm se inici al iza con nmeros alea-
torios (de esta forma, eliminamos las sospechas de que el compilador est conservando algn tipo de meta-infon11acin).
Cada segmento de Worm se etiqueta con un valor char que se genera automticamente como parte del proceso de genera-
cin recursiva de la li sta enlazada de objetos Worm. Cuando se crea un Worm, se le dice al constructor la longitud que que-
remos que tenga. Para construir la referencia next, invoca al constructor de Worm con una longitud inferior en una unidad.
etc. La referencia "ext fina l se deja con el va lor null. lo que indica el final de la cadena Worm.
El obj etivo de esto es construir una estmctura razonablemente compleja que no pueda serial izarse fci lmente. Sin embargo,
el acto de seri ali zar es bastante simple. Una vez que se crea el objeto ObjectOutputStream a partir de algn otro fiujo de
datos, el mtodo writeObject() pennite seri alizar el objeto. Observe que tambin se ha incluido una llamada a
writeObject( ) para un objeto String. Se pueden tambi n escribir todos los tipos de datos primili vos ut ili zando los mismos
mtodos que DataOutputStream (comparten la misma interfaz).
Existen dos secciones de cdigo separadas que tienen un aspecto similar. La primera escribe y lee un archivo, mientras que
la segunda, para tener un ejemplo ms variado, escribe y lee una matri z ByteArray. Podemos leer y escribir un objeto, uti-
lizando el mecanismo de seri ali zacin, en cualquier fluj o DatalnputStream o DataOutputStream, incluyendo (como
puede verse en Thinking in Entelprise Java) una red.
Podemos ver, examinando la sa lida, que el objeto des-seriali zado contiene todos los enl aces que estaban en el obj eto ori-
ginal.
Observe que no se invoca ningn constmctor, ni siquiera el constructor predetenninado, en el proceso de des-seriali zacin
de un objeto de tipo Scrializable. El objeto completo se restaura recuperando los dat os desde el fiujo de entrada Input-
Stream.
Ejercicio 27: ( 1) Cree una clase Scrializable que contenga una referencia a un objeto de una segunda clase Serializable.
Cree una instancia de esa clase, serialcela en di sco, resturela a continuacin y verifique que el proceso
ha funcionado correctamente.
642 Piensa en Java
Localizacin de la clase
Podramos preguntamos qu es lo que hace falta para poder recuperar un objeto a partir de su estado serial izado. Por ejem-
plo. suponga que serializamos un objeto y lo enviamos como un archivo o lo mandamos a travs de una red hacia otra
mquina. Podra un programa en la atTa mquina reconstnlir el objeto utilizando nicamente los contenidos del archivo?
La mejor fomla de responder a esta cuestin es (como siempre) reali zando un experimento. El siguiente archivo est con-
tenido en el subdirectorio de este captulo:
JI : io/Alien.java
/1 Una clase serializable.
import java.io.*
public class Alien implements Serializable {} /// :-
El archivo que crea y serial iza un objeto Alien est incluido en el mismo directorio:
/1 : io/FreezeAlien.java
II Crear un archivo de salida serializable.
import java.io.*;
public class FreezeAlien
public static void main(String[] args ) throws Exception {
ObjectOutput out = new ObjectOutputStream(
new FileOutputStream("X.file"));
Alien quellek = new Alien () ;
out.writeObject(quellek) i
)
111 ,-
En lugar de capturar y tratar las excepciones. este programa adopta la poco elegante solucin de pasar las excepciones hacia
fuera de main( ), con lo que se infonnar de su existencia a travs de la consola.
Una vez compilado y ejecutado, el programa genera un archivo denominado X.fiIe en el directorio io. El siguiente cdigo
est incluido en un subdirectorio denominado xftl es:
11: io/xfiles/ThawAlien.java
II Tratar de recuperar un archivo serializado sin la
II clase del objeto que est almacenado en dicho archivo.
II (RunByHand)
import java.io.*;
public class ThawAlien
public static void main(String(] args) throws Exception
ObjectInputStream in = new ObjectInputStream(
new FileInputStream (new File (" .. ", IIX. file") l l ;
Object mystery = in.readObject{);
System. out.println(mystery.getClass{)l;
1* Output:
class Alien
*111 ,-
La simple operacin consistente en abrir el archivo y leer el objeto mystery requiere disponer del objeto Class correspon-
diente a Alieo; la mquina JVM no puede localizar Alicn.c1ass (a menos que se encuentre en la ruta de clases, lo que no
deberia ser el caso en este ejemplo). Por ello, obtenemos una excepcin ClassNot FoundException. La mquina JVM debe
ser capaz de encontrar el archivo .class asociado.
Control en la serializacin
Como podemos ver, el mecanismo predetenninado de serializacin es bastante sencillo de usar. Pero qu sucede si tene-
mos necesidades especiales? Quiz, haya problemas especiales de seguridad y no queramos serial izar ciertas partes del obje-
18 Entrada/salida 643
to, o quiz no tiene semido que uno de los subobjetos sea serial izado si de todos modos hay que crear de nuevo ese subob-
jeto cuando recuperemos el objeto completo.
Podemos controlar el proceso de serial izacin implementando la interfaz Externalizable en lugar de la interfaz
Seri alizablc. La interfaz Externalizable amplia la interfaz Scrializable y aade dos mtodos, wrileExlernal() y
readExternal( ). que se invocan automticamente para el obj eto durante la seri alizacin y la des-serializacin, para poder
realizar esas operaciones especiales que necesitamos.
El siguiente ejemplo muestra impl ementaciones simpl es de los mtodos de la interfaz Exlernalizablc. Observe que Blip J
Y Blip2 son casi idnticos salvo por una sutil diferencia (trate de descubrirla examinando el cdi go):
11 , io/ Blips.java
// Uso simple de Externalizable, junto con un problema.
import java.io.*
import static net.mindview.util.Print.*
class Slipl implements Externalizable
public Blipl () {
print ( "Slip! Constructor" ) i
public void writeExternal (ObjectOutput out )
throws IOException {
print ("Slipl. writeExternal") i
public void readExternal(Objectlnput in)
throws IOException, ClassNotFoundException
print ( "Blipl.readExternal
U
) i
class Blip2 implements Externalizable
Blip2 () {
print ( "Blip2 Constructor" ) i
public vo id writeExternal (ObjectOutput out )
throws IOException {
print ( "Blip2 . wri teExternal" ) i
public void readExternal (Objectlnput in)
throws IOException, ClassNotFoundException
print(IISlip2.readExternal
U
) i
public class Blips {
public static void main (String[] args )
throws I OException, ClassNotFoundExc eption
print ( "Constructing objects: U) i
Blipl bl = new Blipl () ;
Blip2 b2 = new Blip2 ();
Objec tOutputStream o = new Obj ectOutputStream(
new FileOutputStream ( "Blips .out " ) )
print ( U Saving obj ects: U) ;
o.writeObject (bl )
o.writeObject {b2 ) i
o.close ()
// Ahora obtenerlos de nuevo :
ObjectlnputStream in = new ObjectlnputStream(
new FilelnputStream("Blips.out
U
;
print ( "Recovering bl : " ) ;
644 Piensa en Java
bl = (Blipl) in. readObj ect () ;
1/ Ha fallado! Genera una excepcin:
/ /! print (URecovering b2: 11) i
//! b2 = (Blip2) in. readObject ();
}
} / * Output,
Constructing objects:
Slipl Constructor
Blip2 Constructor
Saving objects :
Blipl.writeExternal
Blip2 . writeExternal
Recovering bl:
Blip! Constructor
Blipl.readExternal
* /// ,-
La razn de que el objeto Blip2 no se recupere es que, al tratar de hacerlo, se genera una excepcin. Puede ver la diferen-
cia entre Blipl y Slip2? El constmctor de Blipl es pblico, mientras que el constmctor de Blip2 no lo es, yeso es lo que
provoca la excepcin al intentar efectuar la recuperacin. Pruebe a definir como pblico el constructor de Blip2 y elimine
los comentarios I/! para ver los resultados correctos.
Cuando se recupera bl , se invoca el constructor predetenninado de BHpl . Esto difi ere del proceso nonnal de recuperacin
del objeto Serializable, durante el cual el objeto se reconstruye enteramente a partir de los bits almacenados, sin efectuar
ninguna llamada a un constructor. Con un objeto Externalizable, tienen lugar todas las tareas normales predetenninadas de
constmccin (incluyendo las inicial izaciones en los puntos donde se definen los campos), despus de lo cual se invoca
readExternal(). Es necesario tener esto en cuenta (en particular el hecho de que tienen lugar todas las tareas predeternli-
nadas de construccin), para poder obtener el comportamiento correcto de los objetos Externalizable.
He aqu un ejemplo que muestra qu es lo que hay que hacer para almacenar y recuperar un objeto Externalizable:
/ / : io/Blip3. java
// Reconstrucccin de un objeto externalizable.
import java . io.*
import static net.rnindview.util . Print. *
public c lass Blip3 implements Externalizable
private int i
private String S i // Sin inicializacin
public Blip3 () {
print ("Blip3 Constructor" 1 i
// s, i no inicializados
public Blip3 (String x, int al {
print ("Blip3 (String x, int al" 1 i
s = x
i = a
// s & i inicializados slo en el constructor no predeterminado.
public String t oSt ring {) { return s + i }
public void writeExternal(ObjectOutput out}
throws IOException {
print ( "Blip3. writeExternal" J
// Hay que hacer esto:
out.writeObject(sJ;
out.writeInt (i)
public void readExternal(Object I nput in)
throws IOException, ClassNotFoundException
print ( "Blip3 . readExternal")
JI Hay que hacer esto:
s (String)in.readObject()
i = in.readlnt();
public static void main(Stringr] args)
throws IOException, ClassNotFoundException
print("Constructing objects:
II
);
Blip3 b3 = new Blip3{"A String 11, 47};
print (b3) i
ObjectOutputStream o = new ObjectOutputStream{
new FileOutputStream( "Blip3 .Qut
ll
;
print ( "Saving object:")
o.writeObject(b3) ;
o.cIase{);
JI Ahora extraer los datos:
ObjectlnputStream in = new ObjectlnputStream(
new FilelnputStream{"Blip3.out
ll
));
print (IIRecovering b3: 11) ;
b3 = (Blip3)in.readObject{);
print Ib3 I ;
/ * Output:
Constructing objects:
Blip3 (String X, int al
A String 47
Saving object:
Blip3.writeExternal
Recovering b3:
Blip3 Constructor
Blip3.readExternal
A String 47
*/1/,-
18 Entrada/salida 645
Los campos s e i slo se inciali zan en el segundo constructor, pero no en el constructOr predetenninado. EstO quiere decir
que si no iniciali zamos s e i en readExternal(), s ser null e i ser cero (ya que el espacio de almacenamient o del objeto
se pone a cero en el primer paso de la creacin del objeto). Si desactivamos mediante comentarios las dos lneas de cdigo
a continuacin de las frases: "Hay que hacer esto:" y ejecutamos el programa, podremos ver que al recuperar el objeto s es
null e i es cero.
Si estamos heredando de un objeto Externalizable, lo que haremos normalmente ser invocar las versiones de la clase base
de writeExtern.l( ) y readExternal( ), para almacenar y recuperar apropiadamente los componentes de la clase base.
Para hacer que las cosas funcionen correctamente, no slo hay que escribi r los datos importantes de los datOs del objeto
durante el mtodo writeExternal( ) (no hay ningn comportamiento predetenninado que escriba ninguno de los objetos
miembro de un objeto Externalizable), sino que tambin hay que recuperar esos datos en el mtodo readExternal() . Esto
puede resultar confuso a primera vista, porque la rea!jzacin de las tareas de construccin predetenninadas para un objeto
Externalizable podran hacer parecer que se est produciendo automticamente algn tipo de operacin de almacenamien-
to y recuperacin, pero en realidad no es as.
Ejercicio 28: (2) En Blips.java, copie el archivo y renmbrelo como BlipCheek.java. Renombre tambi n la clase Blip2
como BlipCheek (hacindola pblica y eliminando el mbito pblico de la clase Blips en el proceso).
El imine las marcas de comentario I/! del archi vo y ejecute el programa, incluyendo las lneas problemti-
cas. A continuacin, desactive con un comentario el constructor predetenninado de BlipCheck. Ejecute el
programa y expl ique por qu funciona. Observe que, despus de la compi lacin, es necesario ejecutar el
programa con "j.v. Blips" porque el mtodo main() sigue estando en la clase Blips.
Ejercicio 29: (2) En Blip3.java, desactive con comentarios las dos lineas situadas despus de las frases: "Hay que hacer
esto:" y ejecute el programa. Explique el resultado y las razones de que ste difiera de lo que sucede cuan-
do las dos lneas fonnan parte del programa.
646 Piensa en Java
La palabra clave transient
Cuando estamos controlando la serializacin. puede que exista un subobjeto concreto que no queramos que sea automti ca-
mente guardado y restaurado por el mecanismo de serializacin de Java. Esto suele suceder cuando dicho subobjeto repre-
senta infonnacin confidencial que no queramos serial izar, como por ejemplo una contrasea. Incluso si esa informacin es
de tipo prvate dentro del obj eto, una vez que ha sido serializada resulta posible que alguien acceda a ella leyendo un archi-
vo o interceptando una transmi sin de red.
Una fomla de evitar que las partes confidenciales del objeto sean serial izadas consiste, como hemos visto previamente, en
implementar la clase Exter nalizable. En ese caso, no hay nada que se serial ice automticamente y podemos serial izar expl-
citamente slo aquellas partes que sean necesarias dentro de writeExt er nal( ).
Si n embargo, si estamos trabajando con un objeto de tipo SeriaHzable, toda la tarea de seriali zacin tiene lugar automti-
camente. Para controlar esto, podemos desactivar la serializacin campo a campo utilizando la palabra clave t ransient , que
lo que viene a decir es: "No te preocupes de guardar o restaurar esto, yo me har cargo de ello".
Por ejemplo, considere un objeto Logon que mantenga informacin acerca de un ini cio de sesin concreto. Suponga que,
una vez verificados los datos de inicio de sesin, queremos almacenar los datos, pero sin la contrasea. La forma ms fcil
de hacer esto es implementando Serializabl e y marcando el campo password como transient. He aqu un ejemplo:
/ / : io/Logon.java
l/Ilustra la palabra clave 11 transient u .
import java.util.concurrent. * ;
import java.io.*;
import java.util. *;
import static net.mindview.util.Print.*;
public class Logon implements Serializable
private Date date: new Date(};
private String username;
private transient String password;
public Logon (String name, String pwd}
username name;
password : pwd;
public String toString(}
return "logon info: \ n username:" + username +
" \ n date:" + date + " \ n password:" + password;
public static void main(String[) args } throws Exception
Logon a : new Logon ("Hulk", "myLittlePony");
print ("logon a : " + a) i
ObjectOutputStream o = new ObjectOutputStream(
new FileOueputStream ( " Logon. out" ) ) ;
o.writeObject(a ) ;
o.close{);
TimeUnit.SECONDS.sleep {1); 1/ Retardo
/ 1 Ahora recuperar los datos:
ObjectlnputStream in : new ObjectlnputStream(
new FilelnputStream{IILogon.out
lt
)) ;
print ("Recovering obj ect at " + new Date ( ) ) ;
a : (Logon)in.readObject() i
print (" logon a = " + a);
1* Output: (Sample)
logon a = logon info:
username: Hulk
date: Sat Nov 19 15:03:26 MST 2005
password: myLittlePony
Recovering object at Sat Nov 19 15:03:28 MST 2005
logan a = logan info:
username: Hulk
date: Sat Nov 19 15:03:26 MST 2005
password: null
* /// ,-
18 Entrada/salida 647
Podemos ver que los campos date y username son normales (no de tipo transient), por lo que se los serial iza automtica-
mente. Sin embargo, el campo password es de tipo transient , as que no se almacena en disco; asimismo, el mecanismo de
seriali zacin no hace nada por intentar recuperarlo. Cuando se recupera el objeto, el campo password contiene el valor "ull.
Observe que, mientras toString() est constmyendo un objeto String utilizando el operador sobrecargado '+', las referen-
cias null se convierten automticamente en la cadena "null".
Tambin puede ver que el campo date se almacena en disco y se recupera desde all, no siendo generado de nuevo.
Puesto que los objetos Extcrnalizablc no almacenan ninguno de sus campos de manera predetenninada, la palabra clave
transient es para ser usada nicamente por los objetos Serializable.
Una alternativa a Externalizable
Si no desea impl ementar la interfaz Externalizable, existe otra tcnica alternativa. Puede implementar la interfaz
Scrializable y aFwdir (observe que decirnos "aadir" y no "sustituir" o "implementar") sendos mtodos denominados
writeObject() y readObjcct( ) que se invocarn automticamente cuando el objeto se serialice o des-serial ice, respectiva-
mente. En otras palabras, si proporcionamos estos otros mtodos se usarn esos mtodos en lugar del mecani smo predeter-
minado de serializacin.
Los mtodos deben tener estas signaturas exactas:
private void writeObject(ObjectOutputStream stream)
throws IOExcepton
prvate void readObject(ObjectInputStream stream)
throws IOExcepton, ClassNotFoundException
Desde un punto de vista de diseo, las cosas pueden ser bastante complicadas si recurrimos a esta solucin. En primer lugar,
podemos pensar que como estos mtodos no fonnan parte de una clase base ni de la interfaz Serializable, deberan ser defi-
nidos en sus propias interfaces. Pero obselVe que esos mtodos estn definidos como private, lo que significa que slo los
pueden invocar otros miembros de esta clase. Sin embargo, en realidad no se invocan desde otros miembros de esta clase,
sino que son los mtodos writeObject() y readObject() de los objetos ObjectOutputStream y ObjectlnputStream los
que se encargan de invocar a los mtodos writeObject() y readObject() de nuestTO objeto (observe cmo estoy contenin-
dome para no entrar en una larga discusin acerca de lo inapropiado que resulta utilizar aqu los mismos nombres de mto-
dos; por decirlo en pocas palabras: resulta enormemente confuso). Puede estar preguntndose cmo es posible que los
objetos ObjectOutputStream y ObjectlnputStream tengan acceso privado a mtodos de nuestra clase. Lo nica respues-
ta en la que podemos pensar es que esto forma parte de la magia de la serializacin
6
Cualquier cosa que definamos en una interfaz es automti camente de tipo public, por lo que si writeObject() y
readObject() deben ser privados, eso quiere decir que no pueden formar parte de una interfaz. Puesto que querernos ajus-
tamos a las signaturas exactamente, el efecto es el mismo que si estuviramos implementando una interfaz.
Cabe imaginar que, cuando invocamos ObjectOutputStream.writeObject(), el objeto de tipo Serializable que pasamos a
ese mtodo es interrogado (utilizando, sin duda, el mecani smo de refl exin) para ver si implementa su propio mtodo
writeObject( ). En caso afirmativo, se omi te el proceso normal de seriali zacin y se invoca el mtodo writeObject( ) per-
sonalizado. La mi sma situacin se produce para el mtodo readObject().
Existe otra consideracin adicional que debemos tener en cuenta. Dentro de nuestro mtodo writeObject(), podemos deci-
dir llevar a cabo la accin writeObject() predeterminada invocando defaultWriteObject(). De la misma forma, dentro de
6 La seccin Interfaces e infannacin de tipos" al final del Capitulo t4./lIformacin de lipos, muestra cmo es posible acceder a mtodos privados desde
fuera de la clase.
648 Piensa en Java
readObject( ) podemos invocar defaultReadObject(). He aqu un ejemplo si mple en el que se ilustra cmo puede contro-
larse el almacenamiento y la recuperacin de un objeto Serializable:
/ / : io/SerialCtl. java
JI Control de la serializacin aadiendo nuestros propios
/ / mtodos writeObject () y readObject () .
import java.io. *
public class SerialCtl implements Serializable {
private String a
private transient String bi
public SerialCtl (String aa, String bb) {
a UNat Transient: 11 + aa i
b = "Transient : " + bb
public String toString () { return a + "\n 11 + b i }
private void writeObject(ObjectOutputStream stream)
throws IOException {
stream.defaultWriteObject() i
stream.writeObject{b) i
private void readObject(ObjectlnputStream stream)
throws IOExeeption, ClassNotFoundExeeption
stream.defaultReadObjeet(} ;
b = (String}stream.readObjeet();
publie statie void main(String[] args)
throws IOExeeption, ClassNotFoundExeeption
SerialCtl se = new SerialCtl ("Testl ti, "Test2");
System.out.println("Before:\n
ll
+ se);
ByteArrayOutputStream buf= new ByteArrayOutputStream{);
ObjeetOutputStream o = new ObjeetOutputStream{buf);
o.writeObjeet (se);
// Ahora recuperar los datos:
ObjeetlnputStream in = new ObjeetlnputStream{
new ByteArraylnputStream(buf.toByteArray()));
SerialCtl se2 = (SerialCtl) in.readObject () ;
System.out.println(IAfter:\n" + sc2) i
/* Output:
Befare:
Not Transient: Testl
Transient: Test2
After:
Not Transient: Testl
Transient: Test2
*///,-
En este ej emplo, uno de los campos Strillg es de tipo nonnal y el otro est definido como transient, para demostrar que el
campo que no es de tipo transient es guardado por el mtodo defaultWriteObject() mientras que el campo transient se
guarda y restaura explcitamente. Los campos se ini cializan dentro del constructor en lugar de en el punto de defini cin, para
demostrar que no estn siendo inicializados por ningn mecani smo de tipo automtico durante la des-serializacin.
Si util izamos un mecanismo predetenninado para escribir las partes no transitori as (no marcadas como transient) del obje-
to, debemos invocar defaultWriteObject() como primera operacin en writeObject(), y defaultReadObject() como pri-
mera operacin en readObject(). Se trata de sendas llamadas a mtodos que resultan un tanto extraas. Podra parecer, por
ejemplo, que estamos invocando defaultWriteObject() para 1111 objeto ObjectOulputSlTeam sin pasarle ningn argumen-
to, a pesar de lo cual, ese mtodo es capaz de averiguar la referencia a nuestro objeto y cmo escribir todas las partes no
transitorias. Verdaderamente asombroso.
18 Entrada/salida 649
El almacenamiento y recuperacin de los objetos transient utiliza un cdigo ms familiar. Y, sin embargo, examine atenta-
mente lo que sucede: en man( j, se crea un objeto Seralet! y luego se seriali za en un flujo ObjeelOulputStream (obser-
ve en este caso que se utiliza un buffer en lugar de un archivo; para el objeto ObjcctOutputStream no hay ninguna
diferencia). La serializacin ti ene lugar en la lnea:
o .writeObject (se);
El mtodo wrteObject( j debe examinar se para ver si dispone de su propio mtodo wrteObjeet( j (no comprobando la
interfaz, ya que no existe ninguna, ni el tipo de la clase, sino buscando realmente el mtodo mediante el mecanismo de refle-
xin). Si el objeto dispone de ese mtodo, lo utilizar. Para readObject( ) se utiliza una tcnica similar. Quiz sta fuera la
nica forma prctica con la que se poda resolver el problema, pero hay que reconocer que resulta un tanto extraa.
Versionado
Es posible que queramos modificar la verslOn de una clase serializable (por ejemplo, los objetos de la clase original
podran estar almacenados en una base de datos). Este tipo de mecanismo est soportado en el lenguaje, aunque lo ms pro-
bable es que no tengamos que recurrir a l ms que en casos especiales; el mecanismo requiere un anlisis ms en profun-
didad que no vamos a realizar aqu. Los documentos del IDK descargables en la direccin hllp:/ljava.su17.com analizan este
terna de forma bastante exhaustiva.
Tambin podr observar en la documentacin del JDK muchos comentarios que comienzan con la advertencia:
los objetos serializados de l/na determinada clase no sern compatibles con las fllturas versiones de
Swing y que el soporTe actual de seriali=acin resulta apropiado para el almacenamiento a corto plazo
O para la invocacin RMl entre aplicaciones ..
Esto se debe a que el mecanismo de versionado es demasiado sencillo como para funcionar de manera fiable en todas las
situaciones. especialmente con JavaBeans. Los diseliadores del lenguaje estn trabajando para corregir el diseo, y a eso es
a lo que hace referencia la advertencia.
Utilizacin de la persistencia
Resulta bastante atractiva la posibilidad de utilizar la tecnologa de serializacin para almacenar parte del estado del progra-
ma, de modo que se pueda posteriormente restaurar con sencillez el programa y devolverlo a su estado actual. Pero, antes
de poder hacer esto, es necesario que respondamos algunas cuestiones. Qu sucede si serial izamos dos objetos que tienen
una referencia a un tercer objeto? Cuando se restauren esos dos objetos a partir de su estado serializado, obtenemos una
nica instancia de un tercer objeto? Qu sucede si serial izarnos los dos objetos en archivos separados y los des-seriali za-
mos en diferentes partes del programa?
He aqu un ejemplo que ilustra el problema:
jj: iojMyWorld.java
import java . io .* ;
import java.util.*
import static net.mindview. util.Print .*
class House implements Serializable {}
class Animal implements Serializable
private String name;
private House preferredHouse;
Animal (String nm, House h) {
name = nm;
preferredHouse = h
public String toString()
return name + 11 [11 + super . toString () +
11], 11 + preferredHouse + 11 \n ti i
650 Piensa en Java
public class MyWorld {
public static void main(String[] args)
throws IOException, ClassNotFoundException
House house = new House() i
List<Animal> animal s = new ArrayList<Animal>() i
animals. add (new Animal ("Bosco the dog" I house}) i
anima!s. add (new Animal ("Ralph the hamst e r
tl
, house)) i
animals. add (new Animal ("Molly the cat
ll
, house}) ;
print ("animals: 11 + animals) i
ByteArrayOutputStream buf! =
new ByteArrayOutputStream(} i
ObjectOutputStream 01 = new ObjectOutputStream(bufl);
ol.writeObject(animalsl i
ol . writeObject(animals) i JI Escribir un segundo conjunto
1/ Escribir en un flujo de datos diferente:
ByteAr rayOutputStream buf2 =
new ByteArrayOutputStream(} i
ObjectOutputStream 02 = new Obj ectOutputSt r eam (buf2) ;
02.writeObject(animals) i
// Ahora recuperar los datos:
ObjectlnputStream inl = new ObjectlnputStream(
new ByteArrayl nputStream{bufl.toByteArray {);
ObjectlnputStream in2 = new ObjectlnputStream{
new ByteArrayl nputStream{buf2.toByteArray()}) i
List
animalsl
animals2
animals3
(List) inl . readObject () I
(List) inl.readObject () I
(List) in2.readObject () ;
print ("animalsl :
print ("animals2 :
print ( "ani mals3:
/ * Output: (Samplel
+ animalsl);
+ animals2);
+ animals3);
animals : [Bosco t he dog[Animal@addbfl] I House@42e B16
Ralph the hamster[Animal@9304bl] I House@42eB16
, Molly the cat(Animal@190dll], House@42eBl6
1
animalsl: [Bosco the dog [Animal@de6f34], House@156eeBe
Ralph the hamster[Animal@47b4BO), House@156eeBe
, Molly the cat[Animal@19b4ge6], House@l56eeBe
1
animals2: [Bosco the dog[Animal@de6f34] I House@156eeBe
Ralph the hamster[Animal@47b4BO), House@l56eeBe
I Molly the cat[Animal@l9b4ge6], House@156eeBe
1
animals3: (Bosco the dog [Animal@lOd448], House@eOelc6
Ralph the hamster[Animal@6calc], House@eOelc6
I Molly the cat[Animal@lbf2l6a], House@eOelc6
1
, /// , -
Un aspecto interesante del ejemplo es que resulta posibl e utili zar el mecanismo de serializacin de objetos con una matriz
de tipo byte, corno fOfila de obtener una "copia profunda" de cualquier objeto de tipo Serializable (una copia profunda
quiere decir que estamos dupli cando la red compl eta de obj etos, en lugar de slo los objetos bsicos y sus referencias). La
copia de objetos se cubre en detalle en los suplementos en lnea del libro.
Los obj etos de tipo Animal contienen campos de tipo House. En main(), se crea una li sta de estos objetos Animal y se la
seria liza dos veces en sendos fluj os de datos. Cuando se des-serializan e imprimen esos flujos de datos, podemos ver un
ejemplo de la salida que se obtendra (en cada ej ecucin las posiciones de memoria correspondientes a los obj etos sern
diferentes).
18 Entrada/salida 651
Por supuesto, lo que cabria esperar es que los objetos des-serial izados tuvieran direcciones diferentes de las de sus origina-
les. Pero observe que en animals1 y animals2 aparecen las mismas direcciones, incluyendo las referencias al objeto House
que ambos comparten. Por otro lado. cuando se recupera animals3, el sistema no tiene fon113 de saber que los objetos de
este a tTo flujo de datos son alias de los objetos del primer flujo de da lOS, as que construye una red de objetos completamen-
te distinta.
Mientras estamos seria lizando todo en un nico flujo de datos, recuperaremos la mi sma red de objetos que hayamos escri-
to, sin que se pueda producir ninguna duplicacin accidental de los objetos. Por supuesto, podemos modificar el estado
de los objetos en el tiempo que transcurre entre la escritura del primer objeto y del ltimo, pero eso es nuestra responsabi -
lidad; los objetos se escribirn en el estado en que se encuentren (y con cualesquiera conexiones que tengan con otros obje-
tos) en el momento de serial izarlos.
Lo ms seguro. si queremos guardar el estado de un sistema, es hacer la serializacin en fonna de operacin 'atmica'. Si
serial izamos algunos objetos, realizamos otras tareas y luego serial izamos ms objetos, etc .. no estaremos guardando el esta-
do del sistema de una fonna segura. En lugar de ello, incluya todos los objetos que fonnan parte del estado de su sistema
en un nico contenedor y escriba simplemente dicho contenedor como parte de una nica operacin. Entonces podr res-
taurarlo tambien con una nica llamada a metodo.
El siguiente ejemplo es un sistema imaginario de diseo asistido por computadora (CAD, compllfer-aicled design) que ilus-
tra la tecnica descri ta. Adems. el ejemplo plantea la cuestin de los campos estticos; si examina la documentacin del
JDK, podr ver que Class es Serializable, as que debe ser sencillo almacenar los campos de tipo static serial izando sim-
plemente el objeto Class . En cualquier caso. parece una solucin ll ena de sent ido comn.
11: io/StoreCADState .java
II Almacenamiento del estado de un supuesto sistema CAD.
import java.io.*
import java.util.*
abstract class Shape implements Serializable
public static final int RED = 1, BLUE = 2, GREEN 3
private int xPos, yPos, dimension;
private static Random rand = new Random(47);
private static int counter = O;
public abstract void setColor(int newColor);
public abstract int getColor();
public Shape (int xVal, int yVal, int dim) {
xPos = xVal
yPos = yVal;
dimension = dim
public String toString()
return getClass() +
"color [" + getColor () + ,,) xPos [11 + xPos +
11 J yPos [" + yPos + 11 J dim [" + dimension + "1 \n" ;
public static Shape randomFactory ()
int xVal = rand.nextlnt(lOO);
int yVal = rand.nextlnt(100);
int dim = rand.nextlnt(100);
switch (counter++ % 3 ) {
default:
case O: return new Circle(xVal, yVal, dim);
case 1: return new Square (xVal, yVal, dim);
case 2: return new Line(xVal, yVal, dim);
class Circle extends Shape {
652 Piensa en Java
private sta tic int color : RED;
public Circle(int xVal, int yVal, int dim) (
super(xVal, yVal, dim);
public void setColor (int newColor) { color
public int getColor () { return color; }
class Square extends Shape {
private static int color;
public Square (int xVal, int yVal, int dim) (
super (xVal, yVal, dim);
color :: RED;
public void setColor (int newColor) { color
public int getColor () { return color; }
class Line extends Shape {
private static int color :: RED;
public static void
serializeStaticState(ObjectOutputStream os)
throws IOException { os. writelnt (color); }
public static void
deserializeStaticState(ObjectlnputStream os)
throws IOException { color: oS.readlnt();
public Line (int xval, int yVal, int dim) {
super (xVal , yVal, dim) i
public void setColor{int newColor) ( color
public int getColor () { return color; }
public class StoreCADState (
newColor; }
newColor; }
newColor; }
public static void main(String{) args) throws Exception {
List<Class<? extends Shape shapeTypes ::
new ArrayList<Class<? extends Shape() i
II Aadir referencias a los objetos class:
shapeTypes.add(Circle.class) ;
shapeTypes.add(Square.class) ;
shapeTypes.add(Line.class) ;
List<Shape> shapes : new ArrayList<Shape>() i
II Construir algunas forma geomtricas:
for(int i :: O; i < 10; i++)
shapes.add(Shape.randomFactory()) ;
II Configurar todos los colores estticos como GREEN:
for(int i "" O; i < 10; i++)
((Shape )shapes .get (i)) .setColor(Shape.GREEN);
II Guardar el vector de estado:
ObjectOutputStream out :: new ObjectOutputStream(
new FileOutputStream ( "CADState .out n 1) ;
out.writeObject(shapeTypes) ;
Line. serializeStaticState (out) ;
out.writeObject{shapes) ;
II Mostrar las formas geomtricas:
System.out.println(shapes) i
1* Output:
[class Circlecolor[3] xPos(58) yPos[SS) dim[93]
class Squarecolor[3] xPos(61 } yPos[61] dim[29]
class Linecolor[3] xPos[68] yPos[O] dim(22]
class Circlecolor[3] xPos(7] yPos[88] dim[28]
class Squarecolor[3] xPos(Sl] yPos[89] dirn[91
class Linecolor[3] xPos[78] yPos[98] dim[61]
class Circlecolor(3] xPos(20] yPos[58] dim[16]
class Squarecolor[3] xPos(401 yPos[ll] dim[22]
class Linecolor[3J xPos[4] yPos[83] dim(6]
class Circlecolor[3] xPos[75] yPos[lO] dim[42]
18 Entrada/salida 653
La clase Shape implementa Serializable, por lo que cualquier cosa que herede de Shapc ser tambin automticamente de
tipo Serializable. Cada objeto Shape contiene datos y cada clase derivada de Sbape contiene un campo slalic que determi-
na el color de todos esos tipos de objetos Shape (si insertramos un campo esttico en la clase base slo lendramos un
campo, ya que los campos estticos no se duplican en las clases derivadas). Los mtodos de la clase base pueden ser susti-
tuidos para configurar el color de los diferentes tipos (los mtodos estticos no se acoplan dinmicamente, as que son mto-
dos normales). El mtodo randomFaclory() crea un objeto Shape diferente cada vez que se lo invoca, utili zando valores
aleatorios como datos para el objeto Shape.
Circle y Square son extensiones sencillas de Shape; la nica diferencia es que Circle iniciali za color en el plinto de defi-
nicin) mientras que Square lo inicializa en el constmctor. Dejaremos el anlisis de Une para ms adelante.
En main( ), se utili za un contenedor ArrayList para almacenar los objetos Class y el otro para almacenar las fonnas geo-
mtricas.
La recuperacin de los objetos es bastantc scncilla:
11 : io/RecoverCADState.java
II Restauracin del estado del supuesto sistema CAD.
II (RunFirst' StoreCADState)
import java.io.*;
import java . util. *;
public class RecoverCADState
@SuppressWarnings (tlunchecked" )
public static void main(String[] args) throws Exception
ObjectlnputStream in = new ObjectlnputStream(
new FilelnputStream ( tlCADState .out
tl
)) i
II Leer en el mismo orden en que fueron escritos:
List<Class<? extends Shape shapeTypes =
(List<Class<? extends Shape)in.readObject() i
Line.deserializeStaticState{in) ;
List<Shape> shapes = (List<Shapein.readObject() i
System.out.println(shapes) i
1* Output:
[class Circlecolor [1] xPos [58] yPos [55] dim [93]
class Squarecolor[O] x Pos[61] yPos(61] dim[29]
class Linecolor[3] xPos[68j yPos[O] dim[22]
class Circlecolor[l] xPos[7] yPos[88] dim[28j
class Squarecolor[O] xPos[51] yPos(89] dim{9]
class Linecolor[3] xPos[78] yPos[98] dim[61]
class Circlecolor[1] xPos[20] yPos[58] dim[16]
class Squarecolor[O] xPos[40j yPos[11] dim[22]
class Linecolor[3] xPos[4] yPos[83) dim[6]
class Circlecolor[l] xPos[75] yPos[10] dim[42]
654 Piensa en Java
Puede ver que los valores de xPos, yPos y dim son almacenados y recuperados sat isfactoriamente, pero existe algn pro-
blema con la recuperacin de la infonnaci n de tipo static. Todos los valores son "3" al entrar, pero al salir son distintos.
Los obj etos Cirelc ti enen un valor de 1 (RED, que es la definicin) y los obj etos Squarc ti enen un valor de O (recuerde que
se ini ciali zaban en el constructor). Es como si los datos de tipo static no se hubieran seriali zado en absoluto! En efecto, as
es: an cuando la clase Class es Serializable, no hace lo que cabria esperar. Por tanto, si queremos seriali zar valores de tipo
sta tic. debemos hacerlo nosotros mismos.
Para esto es para lo que se utilizan los mtodos seriatizeSt.tieState( ) y deserializeStatieState( ) de tipo statie en Line.
Como podemos ver, se los invoca explcitamente como parte del proceso de almacenamiento y de recuperacin (observe
que es necesario mantener el orden de escritura y lectura en el archivo de seriali zacin). Por tanto, para hacer que estos pro-
gramas funcionen correctamente es necesario:
l. Aadir sendos mtodos serializeStatieState() y deserializeStatieState( ) a las clases.
2. Eliminar el contenedor ArrayList shapeTypes y todo el cdigo relacionado con l.
3. Aadir llamadas a los nuevos mtodos estticos de seriali zacin y des-seriali zacin en las clases que representan
a las di stintas fonnas geomtri cas.
Otra cuestin en la que hay que pensar es la de la seguridad, ya que el mecani smo de seriali zacin tambin guarda los datos
de tipo priva te. Si tenemos problemas de seguridad, dichos campos deben marcarse como transient. Pero entonces, ser
necesario di sear alguna fornla segura de almacenar dicha infonnacin, para que cuando efectuemos una restauracin, poda-
mos reiniciali zar dichas variabl es de tipo private.
Ejercicio 30: ( 1) Corrija el programa CADState.java como se ha descrito en los prrafos anteriores.
XML
Una importante limitacin de la seriali zacin de objetos es que es una solucin vlida slo para Java: slo los programas
Java pueden des-serial izar sus objetos. Una solucin ms interoperabl e consiste en convertir los datos a formato XML, lo
que pennite que sean consumidos por una amplia variedad de platafonl1as y de lenguaj es.
Debido a su popularidad, existe un nmero enormemente grande y confuso de opciones para programar con XML, inclu-
yendo las bibliotecas javax.xml.* di stribuidas con el JDK. Aqu, he dec idido utili zar la biblioteca XOM de cdigo abierto
de ElIione Rusty Harold (puede descargar los archivos y la documentacin en wWH',xom.nu) porque parece ser la fonna ms
simpl e y directa de generar y modifi car cdigo XML utili zando Java. Adems, XOM pone un gran nfasis en garantizar la
correccin del cdigo XML.
Como ejemplo, suponga que tenemos objetos Person que contienen campos para representar el nombre y el apell ido, los
cuales queremos serali zar mediante cdigo XML. La sigui ente clase Person tiene un mtodo getXML() que utili za XOM
para convert ir los datos Person en un objeto EJement XML y un constmctor que toma un objeto Element y extrae los datos
Person apropiados (observe que los ejemplos XML estn en su propio subdirectorio):
ji : xml / Person.java
JI Utilizar la biblioteca XOM para escribir y leer XML
JI {Requires: nu.xom.Node You must install
/1 the XOM library fram http: //www.xom.nu }
import nU.xom.*
import java . io.*;
import java . util.*;
public class Person
private String first, last;
public Person (String first I String l astl {
this . first = first
this.last = last
1/ Generar un objeto Element XML a partir de este objeto Person:
public Element getXML () {
Element person = new Element("person");
Element firstName = new Element (" first ") ;
firstName.appendChild{first) ;
Element lastName = new Element (" last 11) i
lastName.appendChild{last) ;
person . appendChild (firstName) i
person. appendChild (lastName) ;
return person;
// Constructor para restaurar un objeto Person
/1 a partir de un objeto Element XML:
public Person (Element person) {
first= person.getFirstChildElement{lifirst
tl
) .getValue();
last = person.getFirstChildElement("1ast
n
) .getValue();
public String toString{) return first + 11 11 + last;
JI Hacer que sea legible:
public static void
format{OutputStream os, Document dac) throws Exception {
Serializer serializer= new Serializer(os, "IS0-8859-1");
serializer.setlndent(4) ;
serializer. setMaxLength (60) ;
serializer.writeldoc) ;
serializer.flush() ;
public static void main(String[] args) throws Exception {
List<Person> people = Arrays.asList(
new Person ("Dr. Bunsen", "Honeydew" ),
new Person ( "Gonzo", "The Great"),
new Person("Phillip J. ", "Fryll));
System.out.println(people} ;
Element root = new Element ( "people" ) ;
for{Person p : peoplel
root.appendChild(p.getXML()) ;
Document doc = new Document(root);
format(System.out, dac};
format(new BufferedOutputStream{new FileOutputStream{
"People.xml" )), doc) ;
/ * Output:
[Dr. Bunsen Honeydew, Gonzo The Great, Phillip J. Fry]
<?xml version="l.O" encoding="ISO-8859-1"?>
<people>
<person>
<first>Dr. Bunsen</first>
<last>Honeydew</last>
</ person>
<person>
<first>Gonzo</first>
<last>The Great</last>
</ person>
<person>
<first>Phillip J.</first>
<last>Fry</last>
</person>
</people>
* /// ,-
18 Entrada/salida 655
Los mtodos XOM son bastante auto-explicativos y puede encontrarlos en la documentacin de XOM.
656 Piensa en Java
XOM tambi n conti ene una clase Serializer que, como podemos ver, se utili za en el mtodo format( ) para transformar el
cdigo XML a un [onnato ms legible. Con invocar simplemente toXML() todo el sistema funciona, asi que Serializer es
una herrami enta bastante til.
Des-seri alizar los objetos Person a partir de un archi vo XML tambin resulta sencillo:
11 : xml / People. j ava
II {Requ ires: nu.xom. Node You must install
II the XOM library from http. llwww.xom . nu }
II {RunFirst: Person}
import nu. xom. *
import java.util. *
public class People e x tends ArrayList<Person> {
public People {String fileName ) throws Exception
Document doc = new Builder {) . build {fileName ) ;
Elements elements =
doc . get RootElement ( ) . getChi l dEl ements ( ) ;
for {int i = o i < elements . size () i i++ )
add {new Person (elements.get(i ) ;
publ i c stat i c void main(Stri ng(] args ) t hrows Ex c e ption {
People p -:: new Pe ople ( II Pe ople . xml" ) i
System. out . println (p ) ;
1* Output :
[Dr . Bunsen Honeydew, Gonzo The Great, Phillip J. Fry]
* /11 ,-
El constructor People abre y lee un archivo usando el mtodo Builder.build() de XOM, y el mtodo getChildElements()
genera una li sta Elements (no es un objeto List estndar de Java, sino un obj eto que slo tiene un mtodo size() y un mto-
do get( ), Harold no quera obligar a los programadores a utili zar Java SES, pero segua queriendo di sponer de un contene-
dor que fuera seguro en lo que respecta a tipos). Cada obj eto Element de esta lista representa un objeto Person, por lo que
se lo entrega al segundo constructor de Persono Observe que esto requiere que conozcamos por adelantado la estructura
exacta del archi vo XML, pero sta suele ser la nonna en este tipo de probl emas. Si la estructura no se ajusta a lo que espe-
ramos, XOM generar una excepcin. Tambin podramos escribir cdigo ms complejo que analizar el documento XML
en lugar de hacer suposiciones acerca del mi smo, para aquellos casos en los que tengamos una informacin menos concre-
ta acerca de la estructura XML entrante.
Para que estos ejemplos puedan compilarse, es necesario incluir los archi vos JAR de la di stribucin XOM dentro de nues-
tra ruta de clases.
Esto slo es una breve introduccin a la programacin XML con Java y a la bibli oteca XOM; para obtener ms informa-
cin, consult e WWlV.xom.nu.
Ejercicio 31: (2) Aada una infonmacin de direccin postal a Person.java y People.java.
Ejercicio 32: (4) Utilizando un contenedor Map<String,lnteger> y la utilidad net.mindview.utiI.TextFile, escriba un
programa que cuente el nmero de apariciones de las di stintas palabras en un archi vo (utili ce "\\W+"
como segundo argumento para el constmctor TextFile). Almacene los resultados como un archi vo XML.
Preferencias
La API Preferences est mucho ms prxima a lo que son los mecani smos de persistencia que a los de serializacin de obje-
tos, porque se encarga de almacenar y recuperar automticamente infonnacin. Sin embargo, su uso est restringido a con-
juntos de datos limitados de pequeo tamao: slo se pueden almacenar primitivas de objetos String, y la longitud de cada
objeto String no puede ser superior a 8K (no es un tamao pequeo, pero tampoco nos permite construir ninguna aplica-
cin seria). Como su propio nombre sugiere, la API Preferences est di seada para almacenar y extraer preferencias de usua-
rio y opciones de configuracin de los programas.
18 Entrada/salida 657
Las preferencias son conjuntos de clave-valor (como los contenedores Map) que se almacenan en una jerarqua de nodos.
Aunque la jerarqua de Dodos puede utilizarse para crear estmcturas complicadas. lo nannal es crear un nico nodo con el
mismo nombre que nuestra clase y almacenar all la infonn3cin. He aqu un ejemplo simple:
JI : io/PreferencesDemo.java
import java.util.prefs.*;
import static net.mindview.util.Print.*
public class PreferencesDemo {
public static void main(String[] argsl throws Exception
Preferences prefs = Preferences
,userNodeForPackage (PreferencesDemo. class) ;
prefs.put(tlLocation", ItOz ");
prefs.put ( tlFootwear", "Ruby Slippers");
prefs.putlnt("Companions", 4);
prefs.putBoolean("Are there witches?", true) i
int usageCount = prefs.getlnt ( "UsageCount", O) i
usageCount++;
prefs .putlnt ("UsageCount", usageCount) i
for (String key : prefs . keys())
print(key + ": "+ prefs.get(key. null ) ;
// Siempre hay que proporcionar un valor predeterminado:
print ( "How many companions does Dorothy have? " +
prefs . getlnt ("Companions", O)) i
/ * Output: (Sampl e)
Location: Oz
Footwear: Ruby Slippers
Companions: 4
Are there witches?: true
UsageCount: 53
How many companions does Dorothy have? 4
* /// ,-
Aqui, se utiliza user NodeForPackage( ), pero tambi n podramos elegir systemNodeForPackage( ); la eleccin es hasta
cierto puma arbitraria; pero la idea es que "user" es para preferencias de los usuarios individuales, mientras que "system"
es para las opciones generales de configuracin de una instalacin. Puesto que main() es de tipo statie. se utiliza
Preferenecs Oemo. cl ass para utili zar el nodo, pero dentro de un mtodo no esttico, probablemente utili zaramos
getClass(). No es necesario utilizar la clase actua l como identificador del nodo, aunque esa es la prctica habitual.
Una vez creado el nodo, estar di sponible para cargar o leer los datos. Este ejemplo carga el nodo con varios tipos de ele-
mentos y luego obtiene las claves con keys( ). Las claves se devuelven como un objeto Str ingl l. lo que puede resultar un
tanto sorprendente si estamos acostumbrados a utilizar el mtodo keys( ) de la biblioteca de colecciones. Observe el segun-
do argumento de get( ): se trata del valor predeternlinado que se genera si no existe ninguna entrada para dicho valor de
clave. Mientras estamos real izando una iteracin a travs de un conjunto de claves, siempre sabemos si existe una entrada,
as que resulta seguro utilizar nuH como valor predetemlinado, pero lo nonnal es que estemos extrayendo una clave nomi-
nada, como en:
prefs.getlnt("Companions", O)) i
En el caso nonnal, lo que conviene es proporcionar un valor predetenninado razonable. De hecho, podemos ver una estruc-
tura bastante tpica en las lneas:
int usageCount = prefs.getlnt("UsageCount", O);
usageCount++i
prefs.putlnt("UsageCount", usageCount) i
De esta fonna, la primera vez que ejecutemos el programa, UsageCount tendr el valor cero, pero en las subsiguientes invo-
caciones ser distinto de cero.
658 Piensa e n Java
Al ejecutar PreferencesDemo.java, podemos ver que, en efecto, UsageCount se increment a cada vez que se ejecuta el pro-
grama. pero dnde se almacenan los datos? No aparece ningn archi vo local despus de ejecutar el programa por primera
vez. La API Preferences utiliza los recursos apropiados del sistema para llevar a cabo su tarea y estos recursos variarn
dependiendo del sistema operativo. En Windows, se utili za el Registro (puesto que ste es ya de por s una jerarqua de nodos
con parejas clave-valor). Pero lo importante es que la infom13cin se almacena de alguna manera mgica y transparente, de
fanna que no tenemos que preocupamos de c6mo funciona el mecani smo en un sistema o en otro.
Habra mucho ms que comentar acerca de la API Preferences. por lo que puede consultar la documentacin del IDK, que
resulta bastante comprensible para obtener ms detalles.
Ejercicio 33: (2) Escriba un programa que muestre el va lor actual de un directorio denominado "directorio base" y que
pida que introduzcamos un nuevo valor. Utilice la API Preferences para almacenar el valor.
Resumen
La biblioteca de flujos de datos de E/ S de Java sati sface los requisitos bsicos. Podemos efectuar lecturas y escrituras a tra-
vs de la consola, un archivo. un bloque de memori a o incluso a travs de Internet. Mediante el mecani smo de herencia
podemos crear nuevos tipos de objetos de entrada y de salida. E incluso podemos aadir un mecani smo simple de ampli a-
bilidad a los tipos de objetos que un flujo de datos puede aceptar, redefiniendo el mtodo toString() que se invoca autom
ticamente cada vez que pasarnos un objeto a un mtodo que est esperando un argumento de tipo String (la limitada
"conversin de tipos automtica" de Java).
Existen diversas cuesti ones que la documentaci n y el di seno de la biblioteca de flujos de E/S dejan sin resolver. Por ejem-
plo, hubiera resultado muy conveniente que pudiramos especificar que se generara una excepcin cada vez que tratramos
de sobreescribir un archivo a la hora de abrirlo para ll evar a cabo una salida de datos. al gunos sistemas de programacin
permiten especificar que queremos abrir un archivo de salida, pero slo si ste no exi sta anterionnente. En Java, parece que
debemos utili zar un objeto File para deternlinar si existe un archivo, porque si lo abrimos como FileOutputStream o
FileWriter, siempre ser sobreescrito.
La biblioteca de fluj os de E/S tiene sus ventajas y sus inconvenientes; se encarga de reali zar una parte de la tarea y es una
biblioteca portable. Pero si no estamos familiarizados con el patrn de diseo Decorador, el di seno de la biblioteca no resul-
ta intuitivo, por 10 que existe una cierta curva de aprendizaje y tambin requiere ms esfuerzo a la hora de explicar el fun-
cionamiento de la biblioteca a los que estn aprendiendo el lenguaje. Asimismo, se trata de una biblioteca incompleta; por
ejemplo, no tendramos por qu tener necesidad de escribir utilidades como TextFile (la nueva utilidad Print\Vriter de Java
SES representa un paso en la solucin cOfTecta, pero slo se trata de una solucin parcial). Se han efectuado grandes mejo-
ras en Java SE5, aadiendo por ejemplo los mecanismos de fom18teo de salida que siempre han estado soportados en prc-
ticamente todos los dems lenguajes.
Una vez que comprendamos el patrn de di seo Decorador y que comencemos a utili zar la biblioteca en aquellas situacio-
nes donde haga falta la flexibilidad que sta proporciona, comenzaremos a sacar provecho de su di seo, siendo esa ventaja
sufi ciente para compensar las lneas de cdigo adicionales que se requi eren para incorporar esa funcionalidad.
Puede encontrar las solucionc:> a los ejercicios seleccionados en el documento electrnico n,e Thinking ;11 Ja\'a Allllolllled So/mon G/lide, disponible para
la venta en wwu:\!indVj"w.nel.
Tipos enumerados
La palabra clave enum nos permite crear un nuevo tipo con un conjunto restringido de valores
nominados, y tratar dichos valores como componentes normales del programa. Esta
caracterstica resulta ser enormemente til.
l
Las enumeraciones se han introducido brevemente al final del Captulo 5, Inicializacin y limpieza. Sin embargo, ahora que
comprendemos los ternas ms avanzados de Java, podemos realizar un anlisis ms detallado de la fu ncionalidad de la enu-
meracin incluida en Java SES. Como veremos, las enumeraciones nos pem1iten realizar cosas enomlemente interesantes,
aunque este captulo tambin nos permitir comprender mejor otras caractersticas del lenguaje que ya hemos presentado
antes, como los genricos y el mecanismo de refl exin. Tambin hablaremos de unos cuantos patrones de di seo adicio-
nales.
Caractersticas bsicas de las enumeraciones
Como hemos visto en el Captulo 5, inicializacin y limpieza, podemos recorrer la li sta de constantes enum invocando el
mtodo values() para dicha enumeracin. El mtodo values( ) genera una matriz con las constantes enum en el orden en
que fueran declaradas, de modo que podemos utili zar la matriz resultante en, por ejemplo, un bucl eforeach.
Cuando se crea una enumeracin, el compilador genera por nosotros una clase asociada. Esta clase hereda automticamen-
te de java.lang.Enum, lo que proporciona ciertas capacidades que se ilustran en el siguiente ejemplo:
11 : enumerated/EnumClass.java
II Capacidades de la clase Enum
import static net.mindview. util.Print . *
enum Shrubbery { GROUND, CRAWLING, HANGING
public class EnumClass {
public static void main (String [] args) {
forlShrubbery s , Shrubbery.valuesl)) {
print(s + 11 ordinal: " + s.ordinal());
printnb(s.compareTo(Shrubbery.CRAWLING) + " ")
printnb(s.equals(Shrubbery . CRAWLING) + " ")
print(s == Shrubbery . CRAWLING)
print (s .getDeclaringClass () )
print(s.name{)) ;
print("---------------------- " )
1I Generar un valor enum a partir de una cadena de caracteres :
for(String s : "HANGING CRAWLING GROUNDII.split(U n )) {
Shrubbery shrub = Enum.valueOf (Shrubbery.class, s);
print (shrub ) ;
\ Joshua Bloch me ha ayudado enormemente en el desarrollo de este captulo.
660 Piensa en Java
/* Output:
GROUND ordinal: O
-1 false false
class Shrubbery
GROUND
CRAWLING ordinal: 1
O true true
class Shrubbery
CRAWLING
HANGING ordinal: 2
1 false false
class Shrubbery
HANGING
HANGING
CRAWLING
GROUND
*//1,-
El mtodo ordinal() genera un valor int que indica el orden de declaracin de cada instancia enum, comenzando en cero.
Siempre podemos comparar con seguridad instancias enurn utilizando =, y los mtodos equals() y hashCodc() se crean
de manera automtica y transparente. La clase Enum es de tipo Comparable, por lo que existe un mtodo compareTo(),
que tambin es de tipo Serializable.
Si invocamos gctDeclaringClass() para una instancia enum, podemos averiguar cul es la clase enum que se utiliza como
envoltorio.
El mtodo name( l devuelve el nombre tal como est declarado, y esto es lo que devuel ve tambin el mtodo loSlring( l.
El mtodo valueOf( l es un miembro esttico de Enum y devuelve la instancia enum correspondiente al nombre (en fonna
de cadena de caracteres) que se le pase; si no se locali za ninguna correspondencia. se genera una excepcin.
Utilizacin de importaciones estticas con las enumeraciones
Analizamos una variante del programa Burrito.java del Captulo 5, inicializacin y limpieza:
1/: enumerated/Spiciness.java
package enumerated;
pUblic enum Spiciness
NOT, MILO, MEDIUM, HOT, FLAMING
} //1,-
JI : enumeratedfBurrito.java
package enumerated;
import static enumerated.Spiciness.*
public class Burrito {
Spiciness degree;
public Burrito(Spiciness degree) ( this.degree = degree)
public String toString{) { return "Burrito is "+ degree;}
public static void main{String(] args) {
System.out.println(new Burrito (NOT) );
System.out.println(new Burrito(MEDIUM));
System.out.println(new Burrito (HOT) );
/* Output:
Burrito is NOT
Burrito is MEDIUM
Burrito is HOT
* /1/,-
19 Tipos enumerados 661
la importacin esttica trae todos los identificadores de instancias enum al espacio de nombres local, as que no es nece-
sario cualificarlos. Se trata de una buena idea o sera mejor ser explcito y cualifi car todas las instancias enum? La res-
puesta depender, probablemente, de nuestro cdigo. El compi lador no nos pennitir en ningn caso utilizar el tipo
incorreclO, por lo que 10 nico que nos debe preocupar es si el cdigo resultar confuso para el lector. En muchas situacio-
nes, probablemente resulte adecuado eliminar las cualificaciones, pero es algo que habr que evaluar caso por caso.
Observe que no es posible utilizar esta tcnica si la enumeracin est definida en el mismo archivo O en el paquete prede-
tem'nado (al parecer, se produj eron algunas di scusiones dentro de Sun sobre si deba pennitir hacer esto).
Adicin de mtodos a una enumeracin
Salvo por el hecho de que no podemos heredar de ella, una enumeracin puede tratarse de fonna bastante similar a las cIa-
ses nonnales. Esro quiere decir que podemos aadir mrodos a una enumeracin. Resulta incluso posible que una enumera-
cin disponga de un mtodo main( ).
Podemos, por ejemplo, generar para una enumeracin, descripciones que difieran de la proporcionada por el mtodo
toString() predetenninado, que simpl emente proporcione el nombre de esa instancia eoum. Para hacer esto, debemos pro-
porcionar el constructor con el fm de capnlrar infonnacin adicional y mtodos adicionales que proporcionen una descrip-
cin ampl iada, como en el ejemplo siguiente:
ti: enumerated/OzWitch . java
// Las brujas en la tierra de Oz.
import static net.mindview.util.Print.*
public enum OzWitch
// Las instancias deben definirse primero, antes de los mtodos:
WEST (11 Miss Gulch, aka the Wicked Wi tch of the West n) ,
NORTH(tlGlinda, the Good Witch of the North"),
EAST("Wicked Witch of the East, wearer of the Ruby It +
tlSlippers, crushed by Dorothy's house"),
SOUTH(uGood by inference, but missing");
private String description;
// El constructor debe tener acceso de paquete o privado:
private OzWitch(String description)
this.description = description
pUblic String getDescription () { return description
public static void main(String [] args) {
for{OzWitch witch : OzWitch.values{))
print(witch + 11: u + witch.getDescription (});
/ * Output:
WEST: Miss Gulch, aka the Wicked Witch of the West
NORTH: Glinda, the Good Witch of the North
EAST: Wicked Witch of the East, wearer of the Ruby
Slippers, crushed by Dorothy's house
SOUTH: Good by inference, but missing
* ///,-
Observe que si vamos a definir mtodos, tenemos que finalizar la secuencia de instancias enum con un carcter de punto y
coma. Asimismo, Java nos obliga a definir las instancias en primer lugar dentro de la enumeracin. Si tratamos de definir-
las despus de algunos de los mtodos o campos, obtendremos un error en tiempo de compil acin.
El constmctor y los mtodos tienen la misma fo nna que las de las clases nonnales, porque se trata de lino clase normal,
slo que con algunas restricciones. As que podemos hacer prcticamente todo lo que queramos con las enumeraciones (si
bien lo ms nonnal es que empleemos enumeraciones simples).
662 Piensa en Java
Aunque el constructor se ha definido en nuestro ejemplo como privado, no tiene demasiada importancia el tipo de acceso
que usemos: el constructor slo puede utili zarse para crear las instancias enum que se declaren dentro de la definicin de
la enumeracin; el compilador no nos permitir uti lizarlo para crear una nueva instancia una vez que est completada la defi-
nicin de la enumeracin.
Sustitucin de los mtodos de una enumeracin
He aqu otra tcnica para generar diferentes valores de cadena para las enumeraciones. En este caso, los mtodos de las ins-
tancias son correctos, pero queremos reformatearlas de cara a su visualizacin. La sustitucin del mtodo toStri ng() en una
enumeracin es igual que la sustitucin en una clase nonnal:
11: enumerated/SpaceShip .java
public enum SpaceShip {
SCOUT, CARGO, TRANSPORT, CRUISER, BATTLESHIP, MOTHERSHIP;
public String toString () {
String id = name{)
String lower = id . substring (1) .toLowerCase () i
return id.charAt{O) + lower
pub1ic static void main{String[] args) {
for {SpaceShip s : values (}) {
System.out.println(s )
1* Output:
Scout
Cargo
Transport
Cruiser
Battleship
Mothership
*/ // ,-
El mtodo toString( j obtiene el nombre de la instancia SpaceShip invocando name( j, y modificando el resultado de modo
que slo la primera letra est en mayscula.
Enumeraciones en las instrucciones switch
Una funcionalidad muy til de las enumeraciones es la forma en que pueden utilizarse en las instrucciones switch.
Normalmente, una instruccin switch slo funciona con valores enteros, pero como las enumeraciones tienen un orden ente-
ro asociado y la posicin de una instaocia puede obtenerse mediante el mtodo ordinal() (aparentemente esto es lo que hace
el compilador), las enumeraciones pueden emplearse tambin dentro de las instrucciones switch.
Aunque normalmente es preciso cualificar cada instancia enum con su tipo, esto no es necesario dentro de una instruccin
case. He aqu un ejemplo que emplea una enumeracin para crear un pequea mquina de estados:
11: enumerated/TrafficLight . java
II Enumeraciones en instrucciones switch.
import static net.mindview.util.Print.*
II Define un tipo enum:
enum Signal { GREEN, YELLOW, RED, }
public class TrafficLight {
Signal color = Signal.RED
public void change () {
switch (color) {
II Observe que no hay por qu escribir Signal.RED
JI dentro de la instruccin case:
case RED: color = Signal . GREEN
break;
case GREEN, color
=
Signal.YELLOW
break;
case YELLOW, color
=
Signal.RED
break;
public String taSerng () {
return "The traffic light is 11 + color;
main (String[] args)
new TrafficLight();
i<7i++l{
public sta tic void
TrafficLight t
for(int i = O;
print (t) ;
t.change() ;
/ *
Output:
The traffic light is RED
The traffic light is GREEN
The traffic light is YELLOW
The traffic light is RED
The traffic light is GREEN
The traffic light is YELLOW
The traffic light is RED
* ///,-
19 lipos enumerados 663
El compilador no se queja de que no haya ninguna instTuccin defaul t dentro de la estructura switch, pero eso no se debe
a que detecte que hay instrucciones case para cada instancia Signal. Si desacti vamos una de las instrucciones case median-
te un comentario, el compilador seguir sin quejarse. Esto quiere decir que es necesario tener cuidado y comprobar explci-
tamente que todos los casos estn cubiertos. Por otro lado, si estamos ejecutando la instruccin returo dentro de las
instrucciones case, el compilador si se queja,. si no incluimos una opcin default , incluso aunque hayamos cubierto todos
los valores de la enumeracin.
Ejercicio 1: (2) Utilice la importacin esttica para modificar Trafli cLight.java de modo que no haya que cualificar
las instancias enum.
El misterio de values( )
Como hemos indicado anterionnente, el compi lador se encarga de crear automticamente todas las clases enum y esas cIa-
ses heredan de la clase Enum. Sin embargo, si examinamos Enum, veremos que no hay ningn mtodo values( ). a pesar
de que nosotros s que lo hemos estado utili zando. Existe algn otro mtodo oculto? Podemos escribir un pequeo progra-
ma basado en el mecani smo de reflexin para averiguarlo:
11: enumerated/Reflection.java
/1 Anlisis de enumeraciones utilizando el mecanismo de reflexin.
import java.lang.reflect .*
import java . util.*
import net.mindview.util. *
import static net.mindview. util.Print. *
enum Explore { HERE, THERE
public class Reflection {
public static enumClass)
print(II----- Analyzing 11 + enumClass + " _____ 11);
print(tlrnterfaces:U) ;
664 Piensa en Java
for (Type t enurnClass.getGenericInterfaces ())
print ( t ) i
print ( "Base: " + enurnClass . getSuperclass ()) ;
print ( "Methods : " ) ;
Set<String> rnethods = new TreeSet<String> () ;
for (Method m : enurnClass . getMethods (
rnethods . add (rn . getNarne () ) ;
print (rnethods ) i
ret urn rnethods i
public static void rnai n (String (] args ) {
Set<St ring> exploreMethods = analyze {Explore. classl ;
Set<String> enurnMethods = analyze (Enurn . class l i
print ( "Explore. containsAl1 {Enum}? " +
exploreMethods.containsAII(enurnMethods ) ;
pr i ntnb(IIExpl ore.removeAl l(Enum) : ") i
exploreMethods.rernoveAII(enurnMethods) i
print(exploreMethodsJ;
II Descornpi lar el cdigo para l a enumeracin:
OSExecute. command ( " j avap Explore" ) ;
1* Output:
----- Analyzing class Explore
Interfaces:
Base: class java . lang . Enurn
Me thods:
(compareTo, equals, get Class, getDeclaringClass, hashCode,
name, notify, notifyAll, ordinal, toString, valueOf, values, waitl
----- Analyzing class java . lang.Enum
Interfaces:
java.lang.Cornparable<E>
interface java . io.Seriali zable
Base: class java . lang.Object
Methods:
(compareTo, equals, getCl ass, getDeclaringClass, ha shCode,
name, notify, noti f yAll, ordinal, toString, valueOf, waitl
Explore.containsAII(Enurn)? true
Explore.removeAII(Enum) ; [values]
Campiled frarn "Reflection. java"
final class Explore extends java.lang.Enurn{
public static final Explore HERE;
public static final Explore THERE;
public static final Explore[] values{) i
}
publ i c s t atic Explore valueOf (java.lang . String);
static {};
*/ // ,-
As que la respuesta es que values( ) es un mtodo esttico aadido por el compilador. Debemos ver que tambin se a'iade
a Explore el mtodo valueOfO dentro del proceso de creacin de la enumeracin. Esto resulta un tanto confuso, porque
tambin exi ste un mtodo valueOf() que forma parte de la clase Enum, pero dicho mtodo ti ene dos argumentos y el mto-
do a'iadido slo di spone de uno. Sin embargo, la utili zaci n del mtodo Set slo comprueba los nombres de los mtodos y
no las signaturas, por lo que despus de invocar Explore.removeAll(Enum), lo nico que queda es Ivalue'l.
A la salida, podemos ver que Explore ha sido definido como final por el compilador, por lo que no podemos heredar de una
enwneracin. Tambin existe una clusula de inicializacin esttica, la cual podemos redefInir como veremos ms tarde.
Gracias al mecanismo de borrado de tipos (descrito en el Capitulo1 5, Genricos) , el descompilador no di spone de inforrua-
cin completa acerca de EDum. por lo que muestra la clase base de Explore como una clase Enum simple, en lugar de
Enum<Explore>.
19 Tipos enumerados 665
Puesto que values( ) es un mtodo esttico insertado dentro de la defini cin de enum por el compilador, si generali zamos
un tipo enum a Enum, el mtodo values( ) no estar di sponible. Observe, sin embargo, que exi ste un mtodo
getEnurnCoDstants() en Class. por lo que incluso values() no fonna parle de la interfaz Enum, podernos seguir obtenien-
do las instancias enum a travs del obj eto Class:
ji: enumerated/UpcastEnum. java
// No hay mtodo values () si generalizamos l a enumeracin
enum Search { HITHER, YON
public class UpcastEnum {
public static void main(String[] args)
Search[] vals = Search.values();
Enum e = Search . HITHER; // Upcast
JI e.values(); /1 No hay mtodo values() en Enum
for(Enum en : e . get Cl ass() . get EnumConstants())
System. out . prin tln {en) ;
1* Output:
HITHER
YON
, /// , -
Como getEnurnConstants() es un mtodo Class, podemos invocarlo para una clase que no tenga enumeraciones:
11 : e numerated/NonEnum . java
public class NonEnum {
public static void main{String[] args) {
Class<Integer> intClass = I nteger . class ;
try (
for(Object en : intClass.getEnumConstants{))
System.out . pr intl n{en ) ;
catch {Except i o n e l {
System.out . println{e) ;
1* Output:
java.lang.NulIPointerException
,///>
Sin embargo, el mtodo devuel ve null , as que se generar una excepci n si tratamos de utili zar el resultado.
Implementa, no hereda
Ya hemos dicho que todas las enumeraciones amplan java.lang.Enurn. Puesto que Java no soporta la herenci a mltipl e,
esto qui ere decir que no se puede crear una enumeracin mediante herencia:
enum NotPossible extends Pet { . .. liNo f unciona
Sin embargo, s es posible crear una enumeracin que implemente una o ms interfaces:
11 : enumerat e d/cartoons /Enumlmpl e mentation . j a va
II Una enumeracin pue de implementar una interf az
package enumerated.cartoons;
import java.util. *;
import net . mindview. util. * ;
enum CartoonCharacter
implements Gene r ator<CartoonCharacter>
SLAPPY, S PANKY , PUNCHY, SILLY, SOUNCY, NUTTY, SOS;
666 Piensa en Java
private Random rand new Random(47)
public CartoonCharacter next () {
return values () [rand. nextlnt (values () . length) ] ;
public class Enumlmplementation {
public static <T> void printNext(Generator<T> rg) {
System.out.print(rg.next() + ", ti} i
public static void main(String(} args)
JI Elegir cualquier instancia:
CartoonCharacter ce = CartoonCharacter.BOB
for(int i '" O; i < 10; i++)
printNext (ce) ;
/* Output:
B08, PUNCHY, B08, SPANKY, NUTTY, PUNCHY, SLAPPY, NUTTY,
NUTTY, SLAPPY,
* ///,-
El resultado es algo extrao, porque para llamar a un mtodo es necesario tener una instancia de la enumeracin para la
cual invocarlo. Sin embargo, cualquier mtodo que admite un objeto Generator podr ahora aceptar una instancia
CartoonCharacter; por ejemplo, printNext( J.
Ejercicio 2: (2) En lugar de implementar una interfaz, defina ncxt() como un mtodo esttico. Cules son las venta-
jas y desventajas de esta sol ucin?
Seleccin aleatoria
Muchos de los ejemplos de este captulo requieren efectuar una seleccin aleatoria entre varias instancias cnum, como
vimos en Ca rtoonCharactcr.next() . Es posible generalizar esta tarea utilizando genricos e incluir el resultado en la biblio-
teca comn:
11 : net/mindviewjutil/Enums.java
package net . mindview.ucil;
import java.util.*;
public class Enums {
private static Random rand = new Random(47);
public static <T extends Enum<T T random (Class<T> ec) {
return random(ec.getEnumConstants()) i
public static <T> T random(T(] values)
return values[rand.nextlnt(values.length));
La extraa sintaxis <T cxtends Enum<T describe T como una instancia enum. Pasando Class<T>, hacemos que est
disponible el objeto clase, pudindose as generar la matri z de instancia enum. El mtodo random( J sobrecargado slo
necesita conocer que se le est pasando un objeto T I!' porque no necesita realizar operaciones de la clase Enum; slo nece-
sita seleccionar aleatoriamente un elemento de una matriz. El tipo de retomo es el tipo exacto de la enumeracin.
He aqu una prueba simple del mtodo ra"dom( J:
/1 : enumerated/RandomTest.java
import net.mindview.util.*;
enum Activity { SITTING, LYING, STANDING, HOPPING,
RUNNING, DODGING, JUMPING, FALLING, FLYING )
public class RandomTest {
public static void main {String[] args ) {
for ( int i = Oi i < 20; i++}
System.out.print (Enums . random{Activity.class ) + ti U) ;
/ * Oueput:
STANDING FLYING RUNNING STANDING RUNNING STANDING LYING
DODGING SITTING RUNNING HOPPING HOPPING HOPPING RUNNING
STANDING LYING FALLING RUNNING FLYING LYING
* // / , -
19 Tipos enumerados 667
Aunque Enum es una clase no demasiado compleja, ya veremos en este captulo que pennite ahorrarse muchas duplicacio-
nes. Las dupl icaci ones tienden a generar errores, as que eliminar esas dupl icaciones es un objeti vo bastante importante.
Utilizacin de interfaces con propsitos de organizacin
La imposibilidad de beredar de una enumeracin puede resultar un tanto frustante en algunas ocasiones. La razn para
tratar de heredar de una enumeraci n proviene en parte del deseo de aumentar el nmero de elementos de la enumeracin
original, y por otro lado del deseo de crear subcategoras empleando subtipos.
Podemos realizar la categorizacin agrupando los elementos dentro de una interfaz y creando una enumeraci n basada en
esa interfaz. Por ej emplo, supongamos que tenemos diferentes clases de alimentos y queremos definirlas como
nes, pero sin que por ello las di stintas clases de alimentos dej en de ser un tipo de una clase denominada Food. He aqu un
ejemplo:
11: enumerated/menu/Feed.java
11 Subcategorizacin de enumeraciones dentro de interfaces .
package enumerat ed.menu
public interface Food {
enum Appetizer implements Feod
SALAD, SOUP, SPRI NG_ROLLS;
enum MainCourse implements Food
LASAGNE, BURRITO, PAD_THAI,
LENTILS, HUMMOUS, VINDALOO;
enum Dessert implements Food {
TIRAMISU, GELATO, BLACK_FOREST_CAKE,
FRUIT, CREME_CARAMEL;
enum Coffee implements Food
BLACK_COFFEE, DECAF_ COFFEE, ESPRESSO,
LATTE, CAPPUCCI NO, TEA, HERB_TEA;
Puesto que el ni co mecani smo de subtipos disponible para una enumeracin est basado en la implementacin de interfa-
ce, cada enumeracin anidada implementa la interfaz envoltorio Food. Ahora s que podemos decir que "todo es un tipo de
Food", como podemos ver aqu :
11 : enumerated/menu/ TypeOfFood.java
package enumerated.menu
import static enumerated.menu . Food . *
public class TypeOfFood {
public static void main(String[] args ) {
Food food = Appetizer . SALAD
focd = MainCourse . LASAGNE
668 Piensa en Java
foad
foad
)
/// ,-
Dessert . GELATO;
Coffee.CAPPUCCINO
La generali zaci n a Food funciona para cada tipo enum que impl ementa Food, as que todos ell os son tipos de Food.
Sin embargo, una interfaz no resulta tan til como una enumeracin cuando queremos tratar con un conjunto de tipos. Si
deseamos di sponer de una "enumeracin de enumeraciones" podemos crear una enumeracin de ni vel superi or con una ins-
tancia para cada enumeracin de Food:
/ 1 : enumerated/menu/Course.java
package enumerated . menu;
import net.mindview. util. *
public enum Course {
APPETIZER ( Food . Appetizer . class ) I
MAINCOURSE(Food . Ma inCour se.class ) ,
DESSERT (Food.Dessert.class ) I
COFFEE (Food . Coffee . c l ass ) ;
privat e Food[] values
private Course (Classc::? extends Food> kind) {
values kind . getEnumConstants () i
public Food randomSelection () {
r eturn Enums . random(values l i
)
/// ,-
Cada una de las enumeraciones anteriores toma el correspondiente objeto Class como argumento del constructor, pudiendo
extraer de l todas las instancias enum utilizando getEnumConstantsO y almacenarlos. Estas instancias se utilizan poste-
rionnente en randomSelection( ), por lo que ahora podemos crear un men generado al eatoriamente sel eccionando el ele-
mento de Food de cada plato (eO"rse):
JJ : enumerated/ menu/ Meal . java
package enumerated.menu
public class Meal {
public static void main (String[] args l {
for (int i = O i i <: 5 i i++) {
for (Course course : Course.values (
Food food = course . randomSelecti on () i
System. out . println (food ) ;
System.out.println ( It- -- It l i
/ * Output :
SPRING ROLLS
VINDALOO
FRUIT
DECAF COFFEE
SOUP
VINDALOO
FRUIT
TEA
SALAD
BURRI TO
FRUIT
TEA
SALAD
BURRITO
CREME CARAMEL
LATTE
SOUP
BURRITO
TIRAMISU
ESPRESSO
19 Tipos enumerados 669
En este caso, la ventaja de crear una enumeracin de enumeraciones es que con ell o podemos iterar a travs de cada objeto
Course. Posteriormente, en el ejemplo VendingMachine.java, veremos otra tcni ca de categorizacin que est basada en
un conjunto diferente de restricciones.
Otra solucin ms compacta al problema de la categori zacin consiste en anidar enumerac iones dentro de otras enumera-
ciones, como en el ejemplo siguiente:
1/: enumerated/SecurityCategory.java
JI Una subcategorizacin ms sucinta.
import net.mindview.util.*
enum SecurityCategory (
STOCK(Security.Stock.class), BOND(Security.Bond.class) i
Security[] values
SecurityCategory(Class<? extends Security> kind)
values = kind.getEnumConstants();
interface Security {
enum Stock implements Security { SHORT, LONG, MARGIN
enum Bond implements Security { MUNICIPAL, JUNK }
public Security randomSelection()
return Enums.random(values};
public static void main(String[] args)
for(int i = O; i < 10i i++} {
SecurityCategory category =
Enums.random(SecurityCategory.class) ;
System.out.println(category + ": "+
category.randomSelection()) ;
/* Output:
BOND, MUNICIPAL
BOND, MUNICIPAL
STOCK, MARGIN
STOCK , MARGIN
BOND, JUNK
STOCK, SHORT
STOCK , LONG
STOCK, LONG
BOND, MUNICIPAL
BOND, JUNK
* /// ,-
La interfaz Security es necesaria para recopi lar todas las enumeraciones dentro de un tipo comn. Entonces, se realiza la
categorizacin dentro de SecurityCategory.
670 Piensa en Java
Si ut ili zamos esta sol ucin con el ejemplo Food. el resultado sera:
11: enumerated/menu/Mea12.java
package enumerated.menu;
import net.mindview.util. * ;
public enum Mea12 {
APPETIZER(Food.Appetizer.class) ,
MAINCOURSE(Food.MainCourse.class) ,
DESSERT(Food.Dessert.class) ,
COFFEE(Food.Coffee.class) ;
private Food[] values;
private Meal2 (Class<? extends Food> kind) {
values = kind.getEnumConstants();
public interface Food {
enum Appetizer implements Food
SALAD, SOUP, SPRING_ROLLS;
enum MainCourse implements Food
LASAGNE, BURRITO, PAD_THAI,
LENTILS, HUMMOUS, VINDALOO;
enum Dessert implements Food {
TIRAMISU, GELATO, BLACK_FOREST_CAKE,
FRUIT, CREME_CARAMEL;
enum Coffee implements Food
BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
LATTE, CAPPUCCINO, TEA, HERB_TEA;
publ ic Food randomSelection () {
return Enums. random (values) ;
public static void main(String[] args)
for(int i == O; i < 5; i++} {
for (Meal2 meal : Meal2. values () {
Food food = meal.randomSelection();
System.out.println(food) ;
System.out.println(II---II) ;
1* Misma salida que en Meal.java *111:-
Al final, se trata simplemente de una reorganizacin del cdigo, pero pennite obtener una estructura ms clara en algunos
casos.
Ejercicio 3:
Ejercicio 4:
Ejercicio 5:
Ejercicio 6:
( 1) Aada un nuevo objeto Course a Course,java y demuestre que funciona en Meal.java.
( 1) Repita el ejemplo anterior para MeaI2.java.
(4) Modifique controlNowelsAndConsonants.java para que utilice tres tipos de enum: VOWEL,
SOMETlMES_A_ VOWEL y CONSONANT. El constructor enum debe admitir las di stintas letras que
describen cada categora concreta de vocales y consonantes. Consejo: utilice varargs y recuerde que stos
crean automticamente una matriz.
(3) Existe alguna ventaja especial en anidar Appetizer, MainCourse, Dessert y Coffee dentro de Food
en lugar de definirlos como enumeraciones independientes que se limiten a impl ementar Food?
19 lipos enumerados 671
Utilizacin de EnumSet en lugar de indicadores
Un contenedor Set es una especie de coleccin que slo pennite aadir un ejemplar de cada tipo de objeto. Por supuesto,
una enumeracin requiere que todos SlI S miembros sean diferentes, por lo que podra parecer que tiene un comportamiento
si mi lar al de los conjuntos, pero como se puede aadir o eliminar elementos, las enumeraciones no resultan demasiado ti-
les como conjuntos. La clase EnumSet se ha aadido a Java SES para funcionar de manera conjunta con las enumeracio-
nes, con el fin de crear un sustituto para los tradicionales <bi ts indicadores" basados en valores enteros. Dichos indicadores
se emplean para renejar algn tipo de informacin de activado-desactivado, pero al final tenninamos manipulando bits en
lugar de conceptos. por lo que es bastante comn que escribamos cdigo bastante confuso.
EnumSet est di seada para maximi zar la velocidad, ya que debe competir de manera efectiva con los bits indicadores (las
operaciones de esta clase sern nonnalmente mucho ms rpidas que las de HashSet). Internamente, esta clase est repre-
sentada (en caso de que sea posible) por un nico valor long que se trata como un vector de bits. as que resulta extremada-
mente rpida y efi ciente. La ventaja es que con ella di sponemos de una forma mucho ms expresiva para indi car la presencia
o ausencia de una caracterstica binaria, sin necesidad de preocuparnos acerca del rendimientos del programa.
Los elementos de un conjunto EnumSet deben provenir de una nica enumeracin enum. Veamos un posible ejemplo
donde se uti liza una enumeracin de los lugares de un edificio donde hay instalado un sensor de alanna:
11 : enumerated/ AlarmPoints.java
package enumerated
public enum AlarmPoints
STAIRl, STAIR2, LOBBY, OFFICEl, OFFICE2, OFFICE3,
OFFICE4, BATHROOM, UTILITY, KITCHEN
// / ,-
Queremos utilizar el conjunto EnumSet para controlar el estado de alamla de los sensores:
jj : enumerated/EnumSets.java
I I Operaciones con conjuntos EnumSet
package enumeraced;
import java.util.*
import static enumerated.AlarmPoints.*;
import static net.mindview.util.Print.*;
public class EnumSets {
public static void main (String[] args )
EnumSet<AlarmPoints> points =
EnumSet.noneOf (AlarmPoints.class ) ; II Conjunt o vaco
points.add (BATHROOM) ;
print(pointsl;
points.addAll IEnumSet.of ISTAIRl, STAIR2, KITCHEN)) ;
print (points ) ;
points = EnumSet.allOf (AlarmPoints.class);
points.removeAII ( EnumSet.of (STAIRl, STAIR2, KITCHEN)) i
print (points ) ;
points.removeAII (EnumSet.range{OFFICEl, OFFICE4) ) i
print(points) i
points = EnumSet.complementOf (points ) ;
print (points) ;
1* Output:
[BATHROOM]
[STAIRl, STAIR2, BATHROOM, KITCHEN]
[LOBBY, OFFICEl, OFFICE2, OFFICE3, OFFICE4, BATHROOM, UTILITY]
[LOBBY, BATHROOM, UTILITY]
[STAIRl, STAIR2, OFFICEl, OFFICE2, OFFICE3, OFFICE4, KITCHEN]
*///,-
672 Piensa en Java
Se utili za una clusula de importacin esttica para simplificar el uso de las constantes enum. Los nombres de los mtodos
son bastantes auto-explicativos, y puede encontrar detalles completos de los mi smos en la documentacin del JDK. Cuando
examine esta documentacin, podr ver un detalle interesante: el mtodo of() ha sido sobrecargado tanto con varargs como
con mtodos individuales que admiten entre dos y cinco argumentos explcitos. Esto es un indi cio de la preocupacin por
el rendimjcnt o que imperaba a la hora de disear la clase EnumSet, porque un nico mtodo of( ) usando varargs podra
haber resuelto el problema, pero es li geramente menos eficiente que si se di spone de argumentos explcitos. As , si invoca-
mos of() con entre dos y cinco argumentos se obtienen las llamadas a mtodo explcitas (ligeramente ms rpidas), pero si
lo invocamos con un argumento o con ms cinco, se obtiene la versin varargs de of( ). Observe que, si lo invocamos con
un argumento, el compilador no construye la matriz varargs, as que no existe ningn gasto de procesamiento adicional si
se invoca dicha versin con un ni co argumento.
Los conjuntos EnumSet se construyen a partir de valores long, cada valor long tiene 64 bits y cada instancia enum requi e-
re un bit para indicar la presencia o ausencia. Esto signifi ca que podemos tener un conjunto EnumSet para una enumera-
cin de hasta 64 elementos si n utili zar ms que un nico valor long. Qu sucede si tenemos ms de 64 elementos en la
enumeracin?
11: enumerated/BigEnumSet.java
import java.util .*
public class BigEnumSet (
enum Big ( AO, Al, A2, A3, M,
A11, A12, A13, A14, A15, A16,
A22, A23, A24, A2S, A26, A27,
A33, A34, A3S, A36, A37, A3a,
A44, MS, M6, M7, MB, M9,
A5S, A5 6, AS7, Asa, AS9, A60,
A66, A67, MB, M9, A70, A71,
AS, A6, A7,
A17, A1B,
A2B, A29,
A39, A40,
ASO, AS1,
Ml, A62,
A72, A73,
public static void main (String [] args ) (
AS, A9, A1O,
A19, A20, A2l,
A30, A3l, A32,
A41, A42, A43,
AS2, AS3, AS4,
M3, A64, MS,
A?4, A7S )
EnumSet<Big:> bigEnumSet = EnumSet.allOf(Big.class) ;
System.out.println(bigEnumSet) ;
/ *
Output :
[AO, Al, A2, A3, M, AS, A6, A7, AB, A9, A1O, All, A12,
Al3, Al4, A1S, A16, A17, A1B, Al9, A20, A21, A22, A23, A24,
A25, A26, A27, A2a, A29, A30, A31, A32, A33, A34, A3S, A36,
A37, A3B, A39, MO, Ml, A42, M3, M4, MS, M6, M7, A4a,
M9, ASO, ASl, AS2, AS3, A54, ASS, AS6, AS?, A5B, A59, A60,
A6l, A62, A63, A64, A65, A66, A67, A6B, A69, A70, A71, A72,
A73, A74, A7SJ
* ///,-
La clase EnumSet, como podemos ver, no tiene ningn problema con las enumeraciones que tengan ms de 64 elementos,
por lo que cabe presumir que se aade otro valor long adicional cada vez que es necesario.
Ejercicio 7: (3) Localice el cdi go fuente correspondi ente a EnumSet y explique cmo funciona.
Utilizacin de EnumMap
Un mapa EnurnMap es un tipo de mapa especializado que requiere que sus claves formen parte de la mi sma enumeracin.
Debido a las restricciones impuestas en las enumeraciones. un mapa EnumMap puede impl ementarse internament e como
una matri z. Por tanto, son extremadamente rpidos, as que podemos utili zar libremente los mapas EnumMap para bsque-
das basadas en enumeraciones.
nicamente podemos invocar el mtodo put() para aquellas claves que fonnen palie de nuestra enumeraci n, pero por lo
dems la utilizacin es similar a la de los mapas ordinarios.
He aqu un ejemplo que ilustra el uso del patrn de di se'i.o basado en comandos. Este patrn comi enza con una interfaz que
contiene (nonnalmente) un nico mtodo y crea mltiples impl ementaciones de dicho mtodo, cada una con un comporta-
mi ento distinto. Basta con instalar los objetos COl11mand y el programa se encargar de llamarlos cuando sea necesario:
JI: enumerated/EnurnMaps.java
JI Fundamentos de los mapas EnumMap.
package enumerated
import java.util.*;
import static enumerated AlarmPoints.*
import static net.mindview.util.Print.*
interface Command { void action () ; }
public class EnumMaps {
public static void main (String [] args) {
EnumMap<AlarmPoints,Cornmand> em =
new EnumMap<AlarmPoints,Command> (AlarmPoints.class ) ;
em.put(KITCHEN, new Cornmand() {
public void action () { print (UKitchen fire! It ) i }
) ) ;
em.put(BATHROOM, new Command() (
public void action () { print ( "Bathroom alert!"); }
)l;
for(Map.Entry<AlarmPoints,Comrnand> e : em.entrySet{))
printnb (e . getKey () + ": ");
e.getValue() .action () ;
try { JI Si no hay ningn valor para una clave concreta:
em.get(UTILITY) .action() i
catch (Exception e ) {
print (e) ;
/ * Output:
BATHROOM: Bathroom alert!
KITCHEN: Kitchen fire!
java.lang.NullPointerException
*///,-
19 Tipos enumerados 673
Al igual que con EnumSet. el orden de los elementos en EnurnMap est determinado por su orden de definicin dentro de
la enumeracin cnum.
La ltima parte de main() muestra que siempre hay una entrada de clave para cada una de las instancias de la enumeracin,
pero el valor ser null a menos que hayamos invocado put() para dicha clave.
Una ventaja de un mapa EnumMap con respecto a los mtodos especificos de constallle (que se describen a continuacin)
es que el mapa EnumMap pem1ite modificar los objetos que representan los valores, mientras que, como veremos, los
mtodos especficos de constante se fijan en tiempo de compilacin.
Como veremos posteriormente en el captulo, los mapas EnumMaps pueden utilizarse para tareas de despacho mltiple en
aquellas si tuaciones en las que se disponga de mltiples tipos de enumeraciones que interaccionen entre s.
Mtodos especficos de constante
La enumeraciones Java tienen una caracterstica muy interesante que nos permite asignar a cada instancia enum un com-
portamiento distinto, creando mtodos para cada una de ellas. Para hacer esto, definimos uno o ms mtodos abstractos
como parte de la enumeracin, y luego definimos los mtodos para cada instancia enum. Por ejemplo:
// : enumerated/ConstantSpecificMethod.java
import java.util.*
import java.text . *
public enum ConstantSpecificMethod
DATE TIME {
674 Piensa en Java
) ,
String getInfo () {
return
DateFormat.getDatelnstance () . format (new Date ()) ;
CLASSPATH
String getlnfo ( )
return System. getenv ("CLASSPATH" ) ;
),
VERSION
String getlnfo ()
)
) ;
return System.getProperty ( "java.version" ) ;
abstraet String getlnfo () i
public static void main (String [] args ) {
for (ConstantSpecificMethod csm : va!ues ())
System.out.println (csm.getlnfo () ) ;
/ * ( Execute to see output) * /// :-
Podemos buscar e invocar los mtodos a travs de su instancia enum asociada. Esto se denomina a menudo cdigo condu-
cido por tablas (observe, en especial, la similitud con el patrn de di seo Comando mencionado anterionnente).
En la programacin orientada a objetos, se asocia un comportamiento di stinto con las diferentes clases. Dado que cada ins-
tancia de una enumeracin puede tener su propio comportamiento mediante mtodos especficos de constante, esto sugiere
que cada instancia es un tipo de datos di stinto. En el ejemplo anterior, cada instancia eflum se trata como el "tipo base"
ConstantSpccificMcthod, pero obtenemos un comportamiento polimrfico con la llamada a mtodo getInfo( ).
Sin embargo, esa similitud no puede llevarse ms lejos. No podernos tratar las instancias enum como si fueran tipos de
clases:
11 : enumerated/ NotClasses.java
II {Exec: javap -c LikeClasses}
import static net.mindview.util.Print.*
enum LikeClasses {
WINKEN { void behavior ( ) { print ("Behaviorl"); ) ),
BLINKEN { void behavior () { print ("Behavior2" ) ; ) ),
NOD { void behavior () { print ("Behavior3"); ) );
abstract void behavior( ) ;
public class NotClasses {
II void fl (LikeClasses. WINKEN instance ) {} lI No se puede
) / * Output,
Compiled from "NotClasses.java"
abstract class LikeClasses extends java.lang.Enum{
public static final LikeClasses WINKEN;
public static final LikeClasses BLINKEN
public static final LikeClasses NOO;
En fl(), podemos ver que el compilador no permite utili zar una instancia enum corno un tipo de clase, lo que tiene bastan-
te sentido si consideramos que el cdigo generado por el compi lador: cada elemento enurn es una instancia de tipo sta tic
final de LikeClasscs.
19 Tipos enumerados 675
Asimismo, como 5011 estticas, las instancias enum de las enumeraciones internas no se comportan como clases internas
normales, no podemos acceder a los campos o mtodos no estticos de la clase externa.
Veamos un ejemplo ms interesante, en el que se intenta representar un sistema de lavado de coches. A cada cliente se le da
un men de opciones para su lavado y cada opcin lleva a cabo una accin diferente. Podemos asociar cada opcin con un
mtodo especifico de constante y emplear un conjunto EnumSet para almacenar las selecciones del cliente:
ji: enumeratedjCarWash.java
import java.util.*
import static net.mindview util.Print.*
public class CarWash {
public enum Cycle {
UNDERBODY (
void action ()
},
WHEELWASH (
print (" Spraying the underbodytl); }
void action () { print ("Washing the wheels"); }
},
PREWASH (
void action () { print ("Loosening the dirt ti); }
},
BASIC (
void action () { print ("The basic wash"); }
},
HOTWAX
void action () { print ( "Applying hot wax"); }
},
RINSE (
void action() { print("Rinsing"); }
},
BLQWDRY (
void action () { print ("Blowing dry"); }
} ;
abstract void action() i
EnumSet <Cycle> cycles =
EnumSet.of(Cycle.BASIC, Cycle.RINSE) i
public void add(Cycle cycle) { cycles.add(cycle);
public void washCar () {
for(Cycle e : cycles)
c. action () ;
public String toString () { return cycles . toString () ;
public static void main{String[] args) {
CarWash wash = new CarWash();
print (wash) i
wash.washCar() ;
II El orden de adicin no es importante:
wash.add{Cycle.BLOWDRY) ;
wash.add{Cycle.BLOWDRY) i II Se ignoran los duplicadas
wash.add(Cycle.RINSE1;
wash.add{Cycle.HOTWAX) i
print{wash) ;
wash.washCar() ;
1* Output:
[BASIC, RINSE]
The basic wash
Rinsing
676 Piensa en Java
[BASIC, HOTWAX, RINSE, BLOWDRY]
The basic wash
Applying hot wax
Rinsing
Blowing dry
*///,-
La sintaxis para definir un mtodo especfico constante es, en la prcti ca. la de una clase interna annima, pero ms su-
cinta.
Este ejemplo muestra tambin otras caractersticas adicionales de los conjuntos EnumSet. Puesto que se trata de un conjun-
to, slo permitir almacenar un ejemplar de cada elemento, as que las llamadas duplicadas con add( ) con el mi smo argu-
mento sern ignoradas (esto tiene bastante sentido, ya que un bit slo se puede "activar" una vez). Asimismo, el orden en el
que aadamos las instancias enum no tiene importancia: el orden de salida est detenninado por el orden de declaracin
dentro de la enumeracin.
Es posible sustituir los mtodos especficos de constante, en lugar de implementar un mtodo abstracto? S que es posible,
como podemos ver aqu:
11: enumerated/OverrideConstantSpecific.java
import static net.mindview.util.Print.*
public enum OverrideConstantSpecific
NU'I' I BOLT,
WASHER {
void f() { print(IIOverridden method");
} ;
void f () { print ( ndefault behavior" ) ; }
public statie void main(String[] args) {
for(OverrideConstantSpeeific Des: values ())
printnb (Des + ": 11);
ocs . f () ;
1* Output:
NUT: default behavior
BOLT: default behavior
WASHER: Overridden method
*///,-
Aunque las enumeraciones impiden utilizar ciertos tipos de estructuras sintcticas, en general lo que deber hacer es expe-
rimentar con ellas como si tratara de clases normales.
Cadena de responsabilidad en las enumeraciones
En el patrn de d i s e ~ o Cadena de responsabilidad, creamos una serie de diferentes formas de resolver un problema y las
encadenamos. Cuando tiene lugar una so}jcitud se la pasa a travs de la cadena basta que se encuentra una de las solucio-
nes que pueda gestionarla.
Podemos implementar fcilmente una Cadena de responsabilidad simple utilizando mtodos especficos de constante.
Considere un modelo de una oficina de correos, que trate de gestionar cada correo de la forma ms general posible, y que
tiene que continuar intentando gestionar cada envo postal hasta conseguirlo o hasta llegar a la conclusin de que no es posi-
ble entregarlo. Cada uno de los intentos puede considerarse como un tipo de Estrategia (otro patrn de d i s e ~ o y la lista
completa forma una Cadena de responsabilidad.
Comenzaremos describiendo lo que es un envo postal. Todas las diferentes caractersticas de inters pueden expresarse uti-
lizando enumeraciones. Puesto que los objetos Mail (que representan los envos postales) se generarn aleatoriamente, la
fonna ms fcil de reducir la probabilidad de que, por ejemplo, a un envo postal le corresponda un valor VES para
GeneralDelivery (entrega de carcter general) consiste en entregar ms instancias que no correspondan con el valor YES,
as que las definiciones enum parecen un poco extraas al principio.
19 Tipos enumerados 677
Dentro de Maj), vemos el mtodo randornMail( ), que crea ej empl os aleatori os de envios postales de prueba. El mtodo
generator( ) produce un obj eto Iterable que utili za randomMail ( ) para generar una serie de obj etos que representan en-
vos postales, generndose un objeto cada vez que se invoca next( ) a travs del iterador. Esta estructura pennite crear de
manera sencilla un bucl e foreach invocando Mail.generator( ):
JI: enumeratedjPostOffice.java
// Modelado de una oficina de correos .
import java.util.*;
import net.mindview.util. * ;
import static net.mindview.util.Print. * ;
class Mail {
JI Los valores NO disminuyen la probabilidad de la seleccin aleatoria:
enum GeneralDelivery {YES,NOl,N02,N03,N04,NOS}
enum Scannability {UNSCANNABLE,YESl,YES2,YES3,YES4}
enum Readability {ILLEGIBLE,YES1,YES2,YES3,YES4}
enum Address {INCORRECT,OK1,OK2,OK3,OK4,OKS,OK6}
enum ReturnAddress {MISSING,OK1,OK2,OK3,OK4,OKS}
GeneralDelivery generalDelivery
Scannability scannability;
Readability readability;
Address address
ReturnAddress returnAddress
static long counter = O
long id = counter++
public String toString{) return "Mail 11 + id }
public String details () {
}
return toString() +
General Delivery: 11 + generalDelivery +
Address Scanability: " + scannability +
Address Readability: " + readability +
Address Address: " + address +
Return address : 11 + returnAddress
II Generar objeto Mail de prueba:
public static Mail randomMail () {
Mail m = new Mail{)
m.generalDelivery= Enums.random{GeneralDelivery.class)
m.scannability = Enums.random(Scannability.class);
m.readability = Enums.random{Readability.class)
m.address = Enums.random(Address.class);
m.returnAddress = Enums.random(ReturnAddress . class);
return m
public static Iterable<Mail> generator{final int count)
return new Iterable<Mail> () {
int n = count
public Iterator<Mail> iterator()
}
} ;
return new Iterator<Mail> () {
public boolean hasNext () { return n- - > O; }
public Mail next () { return randomMail () i }
public void remove{) { /1 No implementado
}
};
throw new UnsupportedOperationExcep tion()
678 Piensa en Java
public class PostOffice
enum MailHandler {
GENERAL_DELIVERY {
} ,
boolean handle (Mail m) {
switch (m. generalDelivery)
case YES:
print ("Using general delivery for ti + m);
return true:
default: return false:
MACHINE _ S CAN {
boolean handle (Mail m) {
switch (m. scannability)
case UNSCANNABLE: return false;
default:
switch(m.address)
case INCORRECT: return false
default:
print("Delivering "+ ro + 11 automatically")
return true;
}
} ,
VISUAL_INSPECTION {
} ,
boolean handle (Mail m) {
switch(m.readability) {
case ILLEGIBLE: return false:
default :
switch(m.address)
case INCORRECT: return false
default:
print("Delivering 11 + m + 11 normally"):
return true;
RETURN_TO_SENDER (
boolean handle(Mail m)
switch (m. returnAddress)
case MISSING: return false;
default:
}
};
print ("Returning
return true;
+ m + " to sender");
abstraet boolean handle(Mail m};
static void handle(Mail m}
for(MailHandler handler : MailHandler.values())
if (handler.handle (ml )
return;
print (m + " is a dead letterO!);
public static void main(String[] args)
for (Mail mail : Mail.generator ( 10})
print (mail.details ()) i
handl e (mail ) ;
print ( "****" ) ;
/ * Output:
Mail O, General Delivery : N02, Address Scanability:
UNSCANNABLE, Address Readability: YES3, Address Address: OK1,
Return address: OK1
Delivering Mail O normally
*.*.*
Mail 1, General Delivery : N05, Address Scanability: VES),
Address Readability: ILLEGIBLE, Address Address: OK5,
Return address: OK1
Delivering Mail 1 automatically
. *
Mail 2, General Delivery: VES, Address Scanability: YES3,
Address Readability: YES1, Address Address: OK1,
Return address: OK5
Using general delivery for Mail 2
.* ...
Mail 3, General Delivery: N04, Address Scanability: YES3,
Address Readability: YES1, Address Address : INCORRECT,
Return address: OK4
Returning Mail 3 to sender
* *.
Mail 4, General Delivery: N04, Address Scanability:
UNSCANNABLE, Address Readability: YES1, Address Address: INCORRECT,
Return address: OK2
Returning Mail 4 to sender
* *.
Mail 5, General Delivery: N03, Address Scanability: YES1,
Address Readability: ILLEGIBLE, Address Address: OK4,
Return address: OK2
Delivering Mail 5 automatically
*. *
Mail 6, General Delivery: VES, Address Scanability: YES4,
Address Readability: ILLEGIBLE, Address Address: OK4,
Return address: OK4
Using general delivery for Mail 6
*
Mail 7, General Delivery: VES, Address Scanability: YES3,
Address Readability: YES4, Address Address: OK2,
Return address: MISSING
Using general delivery for Mai l 7
** *. *
Mail 8, General Delivery: NO), Address Scanability: YES1,
Address Readability: YES3, Address Address: INCORRECT,
Return address: MISSING
Mail B is a dead letter
.* *
Mail 9, General Delivery: N01, Address Scanability:
UNSCANNABLE, Address Readability : YES2, Address Address: OK1,
Return address: OK4
Delivering Mail 9 normally
.* *.*
19 lipos enumerados 679
680 Piensa en Java
La Cadena de responsabilidad se expresa en la enumeracin enum MailHandler, y el orden de las definiciones enurn deter-
mina el orden en el que se intentarn apli car las diferentes estrategias para cada envo postal. Se intenta aplicar cada una de
las estrategias por turnos hasta que una de ellas tiene xito, o todas ellas fallan, en cuyo caso tendremos un envo postal que
no podr ser entregado.
Ejercicio 8:
Ejercicio 9:
Proyecto:
2
(6) Modifique PostOffiee.java para incluir la capaci dad de reenviar correo.
(5) Modifique la clase PostOffiee para que utilice un mapa EnurnMap.
Los lenguajes especializados como Prolog utilizan el encadenamiento inverso para resolver problemas
como ste. Utilizando PostOffice.java como base, haga una investigacin acerca de dichos lenguajes y
desarrolle un programa que permita aadir fcilmente nuevas "reglas" al sistema.
Mquinas de estado con enumeraciones
Los tipos enumerados pueden ser ideales para crear mq/linas de estado. Una mquina de estado puede encontrarse en un
nmero finito de estados especficos. Nonnalmente, la mquina pasa de un estado al siguiente basndose en una entrada,
pero tambin existen estados transitorios: la mquina saJe de estos estados en cuanto se ha realizado la correspondiente
tarea.
Existen ciertas entradas pennitidas para cada estado y las diferentes entradas cambian el estado de la mquina a diferentes
nuevos estados. Puesto que las enumeraciones reducen el conj unt o de posibles casos, resultan muy tiles para enumerar los
diferentes estados y entradas.
Cada estado tiene tambin normalmente algn tipo de salida asociada.
Una mquina expendedora es un buen ejemplo de mquina de estados. En primer lugar, definimos las entradas dentro de
una enumeracin:
11: enumerated/Input.java
package enumerated;
import java.util.*;
public enum Input {
NICKEL ( 5) . DIME (lO) , QUARTER (25 ) , DOLLAR ( lOO) .
TOOTHPASTE (20 0) , CHIPS(75 ) , SODA ( lOO) , SOAP(50 ) ,
ABORT_TRANSACTION {
public int amount () { 11 No permitir
throw new RuntimeException ( "ABORT. amount () JI ) ;
} .
STOP { II Esta debe ser la ltima instancia.
public int amount () { 1I No permitir
}
} ;
throw new RuntimeException ( "SHUT_ DOWN. amount () " ) ;
int value; II En centavos
Input ( int value ) { this. value value; }
Input () {}
int amount ( ) { return value; } ; II En centavos
static Random rand new Random (47 ) ;
public static Input randomSelection ()
II No incluir STOP:
return values () [rand.nextInt (values () .length - 1 ) ];
2 Los proyectos son sugerencias que pueden utilizarse, por ejemplo, como proyectos de fin de curso. Las soluciones a los proyeclOs no se incluyen en la
Glla de solllciolle:)'.
19 Tipos enumerados 681
Observe que dos de las entradas input tienen una cantidad asociada, as que definimos el mtodo amount() para represen-
tar la cantidad dentro de la interfaz. Sin embargo, resulta inapropiado para los otros dos tipos de Input , as que se generar
una excepcin si invocamos ese mtodo. Aunque se trata de un diseno un poco extrao (definir un mtodo en una interfaz
y luego generar una excepcin si se lo invoca para ciertas implementaciones), nos vemos obligados a utilizarlo debido a las
restricciones de la enumeraciones.
El objeto VendingMachine (mquina expendedora) reaccionar a estas entradas categorizndolas primero mediante la enu-
meracin Category, para poder conmutar mediante switch entre las diferentes categoras. Este ejemplo muestra cmo las
enumeraciones consiguen que el cdigo sea ms claro y ms fcil de gestionar:
11: enumerated/VendingMachine.java
II {Args: VendingMachineInput.txt}
package enumerated
import java.util.*;
import net.mindview.util.*;
import static enumerated.Input.*;
import static net . mindview . util.Print.*;
enum Category
MONEY(NICKEL, DIME, QUARTER, DOLLAR),
ITEM_SELECTION(TOOTHPASTE, CHIPS, SODA, SOAP) ,
QUIT_TRANSACTION(ABORT_TRANSACTION) ,
SHUT_DOWN(STOP) ;
private Input[] values
Category (Input. .. types) { values = types; }
private static categories
new
static {
for(Category c : Category.class.getEnumConstants())
for(Input type : c.values}
categories.put(type, cl;
public stacic Category categorize (Input input) {
return categories.get(input);
pUblic class VendingMachine {
private static State state = State.RESTING
private static int amount = Oi
private sta tic Input selection
enum StateDuration { TRANSIENT
enum State {
null
II Enumeracin de marcacin
RESTING {
).
void next (Input input) (
switch(Category.categorize(input))
case MONEY:
amount += input.amount();
state = ADDING_MONEY;
break;
case SHUT DOWN:
state = TERMINAL;
default:
ADDING_MONEY {
void next (Input input) {
switch{Category.categorize(input))
682 Piensa en Java
} ,
case MONEY:
amount += input.amount();
break;
case ITEM SELECTION:
selection = input;
if(amount < selection.amount())
print ("Insufficient money for " + selection);
el se state = DISPENSING;
break;
case QUIT_TRANSACTION:
state = GIVING_CHANGE;
break;
case SHUT DOWN:
state = TERMINAL;
default:
DISPENSING(StateDuration.TRANSIENT)
void next () {
print ("here is your " + selection);
amount -= selection.amount{) i
state = GIVING_CHANGE;
}
},
GIVING CHANGE{StateDuration.TRANSIENT)
void next() {
} ,
if{amount :> O) {
print ("Your change: " + amount);
amount = O i
state = RESTI NG
TERMINAL { void output 11 { print ("Halted"); } };
private boolean isTransient = false
State() {}
State (StateDuration trans) { isTransient = true;
void next (Input input) {
throw new RuntimeException ("Only call " +
"next(Input input) for non-transient states"};
void next ()
throw new RuntimeException ("Only cal! next () for n +
"StateDuration.TRANSIENT states") i
void output{) { print{amount};
static void run{Generator<Input:> gen)
while(state != State.TERMINAL) (
state.next(gen.next()) ;
while(state.isTransientl
state.next() ;
state.output() ;
public static void main(String[] argsl {
Generator<Input:> gen = new RandomlnputGenerator() i
if(args.length == 1)
gen = new FilelnputGenerator{args[O]);
run (gen) ;
/1 Comprobacin bsica de que todo est en orden:
class RandomlnputGenerator implements Generator<Input> {
public Input next () { return Input. randomSelection () ;
19 Tipos enumerados 683
JI Crear objetos Input a partir de un archivo de cadenas separadas por 'j':
class FilelnputGenerator implements Generator<Input> {
25
50
75
private Iterator<String> input;
public FilelnputGenerator (String fileName) {
input = new TextFile(fileName, ni") .iterator();
public Input next (1 {
if(!input.hasNext())
return null i
return Enum valueOf (Input. class, input. next () . trim () ) i
/* Output:
here i5 your CHIPS
O
100
200
here i5 your TOOTHPASTE
O
25
35
Your change: 35
O
25
35
Insufficient money for SODA
35
60
70
75
Insufficient money for SODA
75
Your change: 75
O
Halted
*///,-
Puesto que la seleccin entre las distintas instancias enum se suele realizar con una instruccin a switch (observe el esfuer-
zo adicional que ha hecho el lenguaje para que se pueda aplicar fcilmente una instruccin nvitch a las enumeraciones), una
de las cuestiones ms comunes que podemos preguntamos a la hora de organizar mltiples enumeraciones es: "Qu es lo
que quiero utilizar para la instruccin switch?" Aqu, lo ms fcil es proceder en sentido inverso a partir del objeto
VendingI\1achine observando que en cada estado Sta te, necesitamos conmutar con switch segn las categoras bsicas de
acciones de entrada: si se ha insertado dinero, si se ha seleccionado un elemento, si se ha abortado la transaccin y si se ha
apagado la mquina. Sin embargo, dentro de estas categoras tenemos diferentes tipos de monedas que pueden insertarse y
diferentes alimentos que se pueden seleccionar. La enumeracin Category agrupa los diferentes tipos de objetos Input de
modo que el mtodo categorize( ) puede producir el elemento apropiado Category dentro de una instruccin switch. Este
mtodo utiliza un mapa EnumMap para realizar las bsquedas de manera eficiente y segura.
684 Piensa en Java
Si estudiamos la clase VendingMachine, podemos ver cmo cada estado es diferente y responde de fanna distinta a las
entradas. Observe tambin los dos estados transitorios. En run() la mquina espera una entrada [nput y no deja de pasar a
travs de los estados hasta que deja de estar en un estado transitorio.
El sistema VendingMachine puede probarse de dos formas, utilizando dos objetos Generator diferentes. El objeto
RandomlnputGenerator simplemente genera de forma continua una serie de entradas, exceptuando SHUT _DOWN que
hace que se pare la mquina. Ejecutando este procedimiento durante un tiempo lo suficientemente largo, podemos compro-
bar que todo est en orden y verificar que la mquina no entrar en un estado incorrecto. El objeto FUeInputGenerator
toma un archivo que describe las entradas en fanna textual , las convierte en instancias enum y crea objetos Input. He aqu
un archivo de texto utilizado para producir la salida mostrada en el ejemplo:
jj :! enumeratedjVendingMachinelnput.txt
QUARTER; QUARTER; QUARTER; CHIPS;
DOLLAR; DOLLAR; TOOTHPASTE;
QUARTER; DIME; ABORT_TRANSACTION;
QUARTER; DIME; SODA ;
QUARTER; DIME; NICKEL; SODA;
ABORT_TRANSACTION;
STOP;
/// ,-
Una limitacin de este diseo es que los campos de VendingMachine a los que acceden las instancias de la enumeracin
enurn deben ser estticos, lo que significa que slo podemos tener una nica instancia VendingMachine. Esto no tiene por
qu ser un problema si pensamos en tilla implementacin real (Java embebido), ya que lo nonnal es que slo tengamos una
aplicacin por cada mquina.
Ejercicio 10: (7) Modifique la clase VendingMachine (nicamente) utilizando EnumMap de modo que un programa
pueda di sponer de mltiples instancias de VendingMachine.
Ejercicio 11: (7) En una mquina expendedora real, conviene poder aadir y modificar fci lmente el tipo de elementos
expedidos, porque los lmites impuestos a Input por una enumeracin resultan poco prcticos (recuerde
que las enumeraciones son para un conjunto restringido de tipos). Modifique VendingMachine.java para
que los elementos expedidos estn representados por una clase en lugar de ser parte de Input e inicialice
un contenedor ArrayList de estos objetos a partir de un archivo de texto (utilizando net.mindview.util.
TextFile).
Proyecto:
3
Disee la maquina expendedora utilizando funcionalidades de intemacionalizacin, de tal modo que una
mquina pueda fcilmente ser adoptada en todos los pases.
Despacho mltiple
Cuando estamos tratando con mltiples tipos que interactan entre s, los programas pueden llegar a ser especialmente com-
plejos. Por ejemplo, consideremos un sistema que analice sintcticamente y luego ejecute expresiones matemticas. Nos
interesara poder decir Number.plus(Number), Number.multiply(Number), etc., donde Number es la clase base de una
familia de objetos numricos. Pero cuando decimos a.plus(b), y no conocemos el tipo de exacto de a o b, cmo podemos
hacer que interacten apropiadamente?
La respuesta comienza con algo en lo que probablemente no haya pensado basta el momento: Java nicamente realiza lo
que se denomina despacho simple. En otras palabras, si estamos revisando una operacin sobre ms de un objeto cuyos tipos
sean desconocidos, Java slo puede invocar el mecanismo de acoplamiento dinmico para uno de esos tipos. Esto no resuel-
ve el problema descrito aqu, por lo que tenninamos detectando unos tipos manualmente e implementado, en la prctica,
nuestro propio comportamiento de acoplamiento dinmico.
La solucin se denomina despacho lIIltiple (en este caso, slo hay dos despachos, por lo que el mocanismo se denomina
despacho doble.). El polimorfismo slo puede tener lugar desde llamadas a mtodos, por lo que si queremos realizar un des-
pacho doble, deben existir dos llamadas a mtodos: la primera para detenninar el primer tipo desconocido y la segunda para
) Los proyectos son sugerencias que pueden utilizarse, por ejemplo, como proyectos de fin de curso. Las soluciones a los proyectos no se incluyen en la
GlIa de soluciones.
19 Tipos enumerados 685
determinar el segundo tipo desconocido. Con el despacho mltiple, debemos tener una llamada virtual para cada uno de los
tipos: estamos trabajando con dos j erarquas de tipos diferentes que estn interactuando, necesitaremos una llamada virtual
en cada jerarqua. Generalmente, lo que hacemos es establecer una configuracin tal que una nica llamada a mtodo pro-
duzca una ll amada a mtodo vi rtual , pernlitiendo detenninar as ms de un tipo a lo largo del proceso. Para obtener este
efecto, necesitamos trabajar con ms de un mtodo. Har falta una llamada a mtodo para cada operacin de despacho. Los
mtodos del siguiente ejemplo (que implementa el juego "piedra, papel, tijera") se denominan compete() y eval() y ambos
son miembros de un mi smo tipo. Estos mtodos producen uno de tres posibles resultados:
4
jj : enumeratedjOutcame.java
package enumerated
public enum Out come ( WIN, LOSE, DRAW ) ///,-
jj: enumeratedjRoShamBol.java
jj Ilustracin del mecanismo de despacho mltiple.
package enumerated
import java.util. *
import static enumerated.Outcome.*
interface Item {
Outcome compete(Item it);
Outcome eval (Paper p)
Outcome eval(Scissors s)
Outcome eval(Rack r)
class Paper implements Item {
public Outcome compete(Item it) { return it.eval(this)
public Outcome eval (Paper p) { return DRAW; }
public Out come eval (Scissors s) { return WIN
public Outcome eval (Rack r) { return LOSE }
public String toString () { return "Paper"; }
class Scissors implements Item {
public Out come compete (Item it) { return i t. eval (this ) i
public Out come eval (Paper p) { return LOSE }
public Outcome eval (Scissors s) { return DRAW i }
public Outcome eval (Rock r) { return WIN }
public String toString() { return "Scissors
ll
}
class Rock implements Item {
public Outcome compete(Item it) { return it.eval(this)
public Out come eval (Paper p) { return WIN i }
public Out come eval (Scissors s) { return LOSE; }
public Out come eval (Rock r) { return DRAW; }
public String toString() { return "Rock" }
public class RoShamBol {
static final int SIZE = 20
private static Randam rand = new Random(47)
public static Item newItem()
switch(rand.nextInt(3)} {
default:
case O: return new Scissors()
" Este ejemplo ha estado utilizndose desde hace muchos aos tanto en C++- como en Java (en Thinking n Parterns) en www.MindVie"Wel. antes de apa
4
recer, sin citar la fuente en un libro escrito por otros autores.
686 Piensa en Java
case 1: return new Paper();
case 2: return new Rack() i
public static void match(Item a, Item b) {
System.out.println(
a + n vs. 11 + b + ": 11 + a. compete (b) ) ;
public static void main(String[) args) {
for(int i = Di i < SIZE i++}
match(newltem(), newltem(;
/* Output:
Rack VS. Rack: ORAW
Paper VS . Rack: WIN
Paper VS. Rack: WIN
Paper VS. Rack: WIN
Scissors VE. Paper: WIN
Scissors VS. Scissors: DRAW
Scissors VS. paper: WIN
Rack vs. Paper: LOSE
Paper VS. Paper: DRAW
Rack VE. Paper: LOSE
Paper VS. Scissors: LOSE
Paper VS. Scissors: LOSE
Rack VS. Scissors: WIN
Rack vs. Paper: LOSE
Paper VS. Rack: WIN
Scissors vs. paper: WIN
Paper vs. Scissors: LOSE
Paper vs. Scissors: LOSE
Paper vs. Scissors: LOSE
Paper vs. Scissors: LOSE
* ///,-
lIem es la interfaz para los tipos con los que se va a realizar el despacho mltiple. RoShamBol.match( ) toma dos objetos
h em e inicia el proceso de doble despacho llamando a la [uncin hem.compete(). El mecanismo virtual detennina el tipo
de a, por lo que se activa dentro de la funcin compete() correspondiente al tipo concreto de a. La funcin compele() rea-
liza el segundo despacho llamando a eval( ) con el tipo restante. Pasndose a si mismo (Ihis) como un argumento a cval( )
se genera una llamada a la funcin eval() sobrecargada, preservndose as la infonnacin de tipo correspondiente al primer
despacho. Al completarse el segundo despacho, conocemos el tipo exacto de ambos objetos h em.
Preparar el mecanismo de despacho mltiple requiere de un montn de ceremonia, pero recuerde que la ventaja que se obtie-
ne es la elegancia sintctica que se consigue al realizar la Harnada: en lugar de escribir un cdigo muy complicado para deter-
minar el tipo de uno o ms objetos durante una llamada, simplemente decirnos: "Vosotros dos, no me importa de qu tipo
sois, pero interactuar apropiadamente entre vosotros!", Sin embargo, asegrese de que este tipo de elegancia sea importan-
te para usted antes de embarcarse en programas que impliquen un despacho mltiple.
Cmo despachar con enumeraciones
Realizar una traduccin directa de RoShamBol.java a una solucin basada en enumeraciones resulta problemtico, porque
las instancias enum no son tipos, as que los mtodos eval() sobrecargados no funcionan: no se pueden utilizar instancias
enum como argumentos de tipo. Sin embargo, existen distintas tcnicas para implementar tcnicas de despacho mlt iple
que penniten sacar provecho de las enumeraciones.
Una de sus tcnicas utiliza un constmctor para inicializar una instancia enum con una "fila" de resultados; contemplado en
su conjunto esto produce una especie de tabla de bsqueda:
JJ: enumerated/RoShamBo2.java
// Conmutacin de una enumeracin basndose en otra.
package enumerated
import static enumerated.Outcome.*
public enum RoShamBo2 implements Competitor<RoShamBo2>
PAPER(DRAW, LOSE, WIN),
SCISSORS(WIN, DRAW, LOSE),
ROCK(LOSE, WIN, DRAW) ;
private Outcome vPAPER, vSCISSORS, vROCK
RoShamBo2(Outcome paper,Outcome scissors,Outcome rack) {
this . vPAPER = paper;
this.vSCISSORS = scissorsi
this.vROCK = rack;
public Outcome compete (RoShamBo2 it) {
switch (it) {
default:
case PAPER: return vPAPER
case SCISSORS: return vSCISSORS
case ROCK: return vROCK;
public static void main(String[] args)
RoShamBo.play(RoShamBo2.class, 20);
/* Output:
ROCK V$. RQCK: DRAW
SCISSORS vs. ROCK: LOSE
SCISSORS vs. ROCK, LOSE
SCISSORS vs. ROCK, LOSE
PAPER vs. SCISSORS, LOSE
PAPER vs. PAPER: DRAW
PAPER vs. SCISSORS: LOSE
ROCK V$. SCISSORS: WIN
SCISSORS vs. SCISSORS, DRAW
ROCK vs. SCISSORS: WIN
SCISSORS vs. PAPER, WIN
SCISSORS vs. PAPER: WIN
ROCK vs. PAPER: LOSE
ROCK vs. SCISSORS, WIN
SCISSORS vs. ROCK, LOSE
PAPER vs. SCISSORS: LOSE
SCISSORS vs. PAPER, WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER: WIN
SCISSORS vs. PAPER, WIN
*///,-
19 TIpos enumerados 687
Una vez que se han detenninado ambos tipos en compete(), la nica accin consiste en devolver el resultado con Outcome.
Sin embargo. tambin podramos invocar otro mtodo. incluso (por ejemplo, a travs de un objeto Comando que hubiera
sido asignado en el constructor.
RoShamBo2.java es ms pequeo y ms senci llo que el ejemplo original por lo que tambin resulta ms fcil de controlar.
Observe que estamos todava utilizando dos despachos para determinar el tipo de ambos objetos. En RoShamBo1.java,
ambos despachos se realizaban mediante llamadas virtuales a mtodos pero aqu, slo se utiliza una llamada virtual a mto-
do en el primer despacho. El segundo despacho emplea una instruccin switch, pero esta solucin es perfectamente vlida
porque la enumeracin limita las opciones disponibles en la instruccin switch.
El cdigo relativo a la enumeracin ha sido separado para que pueda utilizarse en otros ejemplos. En primer lugar, la inter-
faz Competitor define un tipo que compite con otro Competitor:
688 Piensa en Java
JI: enumeratedjCompetitor.java
JI Conmutacin de una enumeracin basndose en otra.
package enumerated;
public interface Competitor<T extends Competitor<T {
Outcome compete(T competitorJ i
} 111>
A continuacin, definimos dos mtodos estticos (se hacen estticos para evitar tener que especificar el tipo del parmetro
explicitamente). En primer lugar, match() invoca a competc() para uno de los objetos Competitor comparndolo con otro,
y podemos ver que en este caso el parmetro de tipo slo necesita ser Competitor<T>. Pero en play(), el parmetro de tipo
tiene que ser tanto Enum<f> porque se lo utiliza en Enums.random() como Compctitor<f> porque se le pasa a
match( ):
JI: enumeratedjRoShamBo.java
JI Herramientas comunes para los ejemplos RoShamBo.
package enumerated
import net.mindview.util.*
public class RoShamBo {
public static <T extends Competitor<T
void match(T a, T b) (
System.out.println(
a + "vs. " + b + ": ti + a.compete(b)}
public static <T extends Enum<T> & Competitor<T
void play (Class<T> rsbClass, int sizel {
for(int i = O; i < size; i'H}
match (
}
111,-
Enums. random (rsbClass l ,Enums .random(rsbClass});
El mtodo play() no tiene un valor de retomo que implique el parmetro de tipo T, por lo que parece que podramos utili-
zar comodines dentro del tipo Class<f> en lugar de emplear la descripcin proporcionada en el ejemplo. Sin embargo, los
comodines no pueden abarcar ms de un tipo base, asi que estamos obligados a usar la expresin anterior.
Utilizacin de mtodos especficos de constante
Puesto que los mtodos especficos de constante nos pem1iten proporcionar diferentes implementaciones de mtodo para
cada instancia ellum, parece una solucin perfecta para configurar un sistema de despacho mltiple. Pero aunque se las
puede proporcionar de este modo diferentes comportamientos, las instancias enum no son tipos, por lo que no se las puede
emplear como argumentos de tipo en las signaturas de los mtodos. Lo mejor que podemos hacer en este caso es definir una
instmccin switch:
JJ: enumeratedJRoShamBo3.java
JJ Utilizacin de mtodos especficos de constante.
package enumerated;
import static enumerated.Outcome.*
public enum RoShamBo3 implements Competitor<RoShamBo3>
PAPER {
public Out come compete(RoShamBo3 it} {
switch(it) {
default: JI Para aplacar al compilador
case PAPER: return ORAW;
case SCISSQRS: return LOSE;
case ROCK: return WIN;
) ,
SCISSORS
public Out come compete (RoShamBo3 it) {
)
).
ROCK
switch{it} (
default:
case PAPER: return WI N
case SCISSORS: return DRAW
case ROCK: return LOSE;
public Outcome compete (RoShamBo3 it) {
)
) ;
switch (it)
default :
case PAPER: return LOSE;
case SCISSORS: return WIN
case ROCK: return DRAW
public abstraet Outcome compete(RoShamBo3 it);
public static void main(String[] args) {
RoShamBo.play(RoShamBo3.class, 20);
/ * Misma salida que RoShamBo2.java * /// :-
19 Tipos enumerados 689
Aunque este programa funciona y no resulta irrazonable, la solucin de RoShamBo2.java parece requerir menos cdigo a
la hora de aiiadir un nuevo tipo, por lo que resulta ms sencilla.
Sin embargo, RoShamBo3.java puede simplifi carse y comprimirse:
JI : enumerated/RoShamBo4.java
package enumerated
public enum RoShamBo4 implements Competitor<RoShamBo4>
ROCK (
).
public Outcome compete {RoShamBo4 opponent)
return compete (SCISSORS, opponent) i
SCISSORS
) ,
public Out come compete(RoShamBo4 opponent)
return compete {PAPER, opponent) i
PAPER
public Out come compete(RoShamBo4 opponent)
return compete {ROCK, opponent) i
)
) ;
Out come compete{RoShamBo4 loser, RoShamBo4 opponent)
return ({opponent == this) ? Outcome.DRAW
{(opponent == loser) ? Outcome.WIN
Outcome.LOSE)) i
public static void main(String[] args)
RoShamBo.play{RoShamBo4.class, 20) i
/* Misma salida que RoShamBo2.java * ///:-
690 Piensa en Java
Aqu, el segundo despacho es realizado por la versin de dos argumentos de compete( ), que realiza una secuencia de com-
paraciones y es, por tanto, similar a la accin de una instruccin switch. Este ejemplo es ms pequeo, pero resulta un poco
confuso. Para un sistema de mayor envergadura, esta confusin puede ser una desventaja.
Cmo despachar con mapas EnumMap
Es posible realizar un "verdadero" despacho doble utilizando la clase EnumMap, que est diseada especficamente para
trabajar con las enumeraciones. Puesto que el objetivo es conmutar entre dos tipos desconocidos, podemos usar un mapa
EnumMap de mapas EnumMap para realizar el doble despacho:
//: enumerated/RoShamBo5.java
// Despacho mltiple usando un mapa EnurnMap de mapas EnumMaps.
package enumerated
import java.util.*
import static enumerated.Outcome.*
enum RoShamBo5 implements Competitor<RoShamBoS>
PAPER, SCISSORS, ROCK
static EnurnMap<RoShamBo5,EnurnMap<RoShamBo5,Outcome
table = new EnurnMap<RoShamBo5,
EnurnMap<RoShamBo5,Outcome(RoShamBo5. class) ;
static {
for(RoShamBo5 it : RoShamBo5.values())
table.put(it,
new EnurnMap<RoShamBo5,Outcome>(RoShamBo5.class))
initRow(PAPER, DRAW, LOSE, WIN);
initRow(SCISSORS, WIN, DRAW, LOSE) i
initRow(ROCK, LOSE, WIN, DRAW) i
static void initRow(RoShamBo5 it,
Outcome vPAPER, Out come vSCISSORS, Out come vROCK) {
EnurnMap<RoShamBo5,Outcome> row =
RoShamBo5.table.get(it)
row.put (RoShamBo5. PAPER, vPAPER);
row.put{RoShamBo5.SCISSORS, vSCISSORS)
row.put(RoShamBo5.ROCK, vROCK) i
public Out come compete(RoShamBo5 it)
return table.get(this) .get(it);
public static void main(String[] args)
RoShamBo.play(RoShamBo5.class, 20)
/* Misma salida que RoShamBo2.java *///:-
El mapa EnurnMap se inicializa mediante una clusula static; podemos ver la estructura con forma de tabla de llamadas a
initRow( ). Observe el mtodo compete( ), donde puede verse que ambos despachos tienen lugar en una nica instruccin.
Utilizacin de una matriz 20
Podemos simplificar la solucin an ms dndonos cuenta de que cada instancia enum tiene un valor fijo (basado en su
orden de declaracin) y que el mtodo ordinal() genera este valor. Una matriz bidimensional que asigne los competidores
a los distintos resultados, permite obtener la solucin ms pequea y directa (y tambin posiblemente la ms rpida aunque
recuerde que EnumMap utiliza una matriz interna):
//: enumerated/RoShamBo6.java
/ / Enumeraciones utilizando ntablas" en lugar de despacho mltiple.
package enumerated
import static enumerated.Outcome.*
enum RoShamBo6 implements Competitor<RoShamBo6>
PAPER, SCISSORS, ROCK;
private static Outcome [] [J table :::
{ DRAW, LOSE, WIN }, II PAPER
} ;
{ WIN, DRAW, LOSE }, II SCISSORS
{ LOSE, WIN, DRAW }, II ROCK
public Outcome compete (RoShamBo6 other) {
return table [this. ordinal ()] (other. ordinal ()] i
public static void main{String[] args)
RoShamBo.play(RoShamBo6.class, 20);
19 Tipos enumerados 691
La tabla table tiene exactamente el mismo orden que las llamadas a initRow() en el ejemplo anterior.
El tamao pequeo de este cdigo resulta muy atractivo si lo comparamos con los ejemplos anteriores. en parte porque pare-
ce mucho ms fcil de entender y de modificar pero tambin porque parece una solucin mucho ms directa. Sin embargo,
no es una solucin tan "segura" como los ejemplos anteriores, porque utiliza una matriz. Con Wla matriz de mayor tamao,
podramos obtener el tamao inapropiado y, si las pruebas no cubrieran todas las posibilidades, algn error podra terminar
por deslizarse.
Todas estas soluciones representan diferentes tipos de tablas, pero merece la pena explorar la fomla de expresar cada una
de las tablas para localizar la que mejor se ajuste a nuestras necesidades. Observe que, aunque la solucin anterior es la ms
compacta, tambin resulta bastante rgida, porque slo permite producir una salida constante a partir de unas entradas cons-
tantes. Sin embargo, no hay nada que nos impida tener una tabla que genera un objeto de funcin. Para ciertos tipos de pro-
blemas, el concepto de "cdigo conducido por tablas" puede ser muy potente.
Resumen
An cuando los tipos enumerados no son terriblemente complejos por si mismos, hemos pospuesto este capllllo hasta este
momento debido a que queramos analizar lo que se puede bacer con las enumeraciones al combinarlas con caractersticas
tales corno el polimorfismo, los genricos y el mecanismo de reflexin.
Aunque son significativamente ms sofisticadas que las enumeraciones de e o e++, las enumeraciones Java siguen siendo
una caracterstica menor, algo sin lo que el lenguaje ha sobrevivido (aunque a costa de una gran complejidad) durante
muchos aos. A pesar de elJo, este captulo muestra el valor que una caracterstica "menor" puede tener. En ocasiones nos
proporciona el mecanismo adecuado para resolver un problema de manera elegante y clara y, corno hemos dicho en di ver-
sas ocasiones a lo largo del libro, la elegancia es importante y la claridad puede marcar la diferencia entre una solucin ade-
cuada y otra que fracasa porque las dems personas son incapaces de entenderla,
Hablando de claridad, una fuente muy lamentable de confusin proviene de la mala decisin que se tom en Java LO, con-
sistente en emplear el tmlino "enumeration" en lugar del tnnino ms comn y ampliamente aceptado de "iterator" para
referirse a un objeto que selecciona cada elemento de una secuencia (como hemos mencionado al hablar de las colecciones).
Algunos lenguajes hacen incluso referencias a los tipos de datos enumerados utilizando la palabra '"enumerators". Este error
se ha rectificado desde entonces en Java, pero la interfaz Enumeration no pudo, por supuesto, eliminarse de manera direc-
ta, por lo que sigue estando presente en el cdigo antiguo (ya veces en el cdigo nuevo!), en la biblioteca y en la docu-
mentacin.
Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico TITe TITinking il! Java AflIlO/ated Solllrioll GlIide, di sponible para
la venta en \\w\\'.lIIindViell'.l/el.
Anotaciones
Las anotaciones (tambin conocidas como melada/os) proporcionan una manera formalizada de
aadir informacin a nuestro cdigo que nos permita utilizar fcilmente dichos datos en algn
momento posterior. 1
Las anotaciones estn en parte motivadas por la tendencia general existente hacia combinar metadatos con los archivos de
cdigo fuente en lugar de mantenerlos en documentos externos. Tambin son una respuesta a las presiones provenientes de
otros lenguajes como C# en el sentido de aadir ms caractersticas al lenguaje.
Las anotaciones son uno de los cambios fundamentales del lenguaje introducidos en Java SES. Proporcionan infonnacin
que hace falta para describir completamente el programa, pero que no puede expresarse en Java. De este modo, las anota-
ciones nos permiten almacenar infaonacin adicional en un fonnata que es probado y verificado por el compilador. Pueden
utilizarse anotaciones para generar archivos descriptores o incluso nuevas definiciones de clases y para ayudar a facilitar la
tarea de escribir plantillas de cdigo. Utilizando anotaciones podemos mantener estos metadatos en el cdigo fuente Java y
conseguir con ello un cdigo de aspecto ms limpio, un mecanismo de comprobacin de tipos de compilacin y la API ano-
taciones como ayuda para construir herramientas de procesamiento de las anotaciones. Aunque hay unos cuantos tipos de
rnetadatos predefinidos en Java SE5, en general, el tipo de anotaciones que se aadan y lo que hagamos con ellas son res-
ponsabilidad completamente nuestra.
La sintaxis de las anotaciones es razonablemente simple y consiste principalmente en la adicin del smbolo @ al lengua-
je. Java SE5 contiene tres anotaciones predefinidas de propsito general , que estn definidas en java.lang:
@Override, para indicar que la definicin de un mtodo pretende sustituir otro mtodo de la clase base. Esta ano-
tacin genera un error de compilacin si se escribe mal accidentalmente el nombre del mtodo o si se proporcio-
na una signatura incorrecta.
2
@Deprecated, para que se genere una advertencia del compilador si se utiliza este elemento.
@SuppressWarnings, para desactivar las advertencias de compilador inapropiadas. Esta anotacin est penniti-
da, pero no soportada como en las versiones primeras de Java SE5 (la anotacin era ignorada).
Otros cuatro tipos adicionales de anotacin soportan la creacin de nuevas anotaciones, aprenderemos acerca de estos tipos
en este captulo.
Cada vez que creemos clases descriptoras o interfaces que impliquen una tarea repetitiva, normalmente utilizaremos anota-
ciones para automatizar y simplificar el proceso. Buena parte del trabajo adicional en Enterprise JavaBeans (Effi), por
ejemplo, se elimina mediante el uso de anotaciones en Effi3.0.
Las anotaciones penniten sustituir sistemas existentes como XDoclet , que es una herramienta independiente para docIet
(consulte el suplemento contenido en http://MindView.net/Books/BetterJava) diseado especficamente para crear doclets
1 Jeremy Meyer tuvo la gentileza de acudir a Crested Bulte y pasar all dos semanas trabajando conmigo en este captulo. Su ayuda ha sido extremadamen-
te valiosa.
2 EslO est, sin ninguna duda, inspirado en atTa caracterstica similar disponible en C#. La caracterstica de C# es una palabra clave y no una anotacin, y
est impuesta por el compilador. En otras palabras, si se sobreescribe un mtodo en C#, es necesario utilizar la palabra clave override. mientras que en
Java la anotacin @Override es opcional.
694 Piensa en Java
con estilo de anotacin. Por contraste. las anotaciones son verdaderas estructuras del lenguaj e y estn, por tanto, estructura-
das, comprobndose sus tipos en tiempo de compilacin. Al mantener toda la informacin en el propio cdigo fuente y no
en comentarios, el cdigo resulta ms limpio y fcil de mantener. Utilizando y ampliando la API y las herramientas de ano-
taciones, o empleando bibliotecas ex temas de manipulacin del cdigo intennedio como veremos en este captulo, podemos
realizar potentes tareas de inspeccin y manipulacin del cdigo fuente y del cdigo intennedio.
Sintaxis bsica
En el ejemplo siguiente. el mtodo testExecute( ) est anotado con @Test. Esto no hace nada por s mismo pero el compi-
lador comprobar que existe una definicin de la anotacin @Test en la ruta de construccin del programa. Como veremos
posterioffilellte en el captulo, podemos crear una herramienta que ejecute este mtodo por nosotros a travs del mecanismo
de reflexin.
11 : annotations/Testable.java
package annotations
import net.mindview.atunit.*
public class Testable {
public void execute()
Syst.em.out.println(!1Executing .. ")
@Test void testExecute() { execute() i
///>
Los mtodos anotados no difieren de los dems mtodos. La anotacin @Test de este ejemplo puede utilizarse en combi-
nacin con cualquiera de los modificadores como public, static o void. Sintcticamente, las anotaciones se utilizan de fonna
similar a los modificadores.
Definicin de anotaciones
He aqu la definicin de la anotacin anterior. Podemos ver que las definiciones de anotaciones se parecen bastante a las
definiciones de interfaz. De hecho, se compilan en archivos de clase, al igual que cualquier otra interfaz Java:
11: net/mindview/atunit/Test.java
1/ El marcador @Tes t.
package net.mindview.atunit
import java.lang.annotation.*
@Target(ElementType .METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {} ///,-
Aparte del smbolo @, la definicin de @Test se parece bastante a la de Wla interfaz vaCa. La definicin de una anotacin
tambin requiere las lIle/a-ano/aciones@Targety @Retention. @Targetdefinednde se puede aplicar esta anotacin (un
mlodo o un campo)@Retentiondefinesi las anolaciones eSlarn disponibles en el cdigo fuenle (SOURCE), en los archi-
vos de clase (CLASS), o en liempo de ejecucin (RUNTlME).
Las anotaciones contendrn usualmente elementos para especificar valores para las anotaciones. Un programa o una herra-
mienta pueden utilizar estos parmetros para procesar las anotaciones. Los elementos se asemejan a los mtodos de una
interfaz, excepto porque no se pueden declarar valores predetenninados.
Una anotacin sin ningn elemento, como la anotacin @Test anterior, se denomina anotacin marcadora.
He aqu una anotacin simple que controla los casos de uso en un proyecto. Los programadores anotan cada mtodo o con-
junto de mtodos que satisfacen los requisitos de un caso de uso concreto. El jefe de proyecto puede hacerse una idea del
progreso del proyecto contando los casos de uso implementados y los desarrolladores encargados de mantener el p r o y e ~
to pueden encontrar fcilmente los casos de uso si necesitan actualizar O depurar las reglas de negocio utilizadas en el sis-
tema.
/1: annotations/UseCase.java
import java.lang.annotation.*
@Target{ElementType.METHOD)
@Retention{RetentionPolicy .RUNTIME)
public @interface UseCase {
public int id();
public String description() default "no description"
!/ 1,-
20 Anotaciones 695
Observe que id y description se asemej an a declaraciones de mtodos. Puesto que el tipo de id es comprobado por el com-
pilador, se trata de una forma fiable de enlazar una base de datos de control con el documento de casos de uso y el cdigo
fuente. El elemento description tiene un valor predetenninado que es seleccionado por el procesador de anotaciones si no
se especifica ningn valor en el momento de anotar un mtodo.
He aqu una clase con tres mtodos anotados como casos de uso del programa:
// : annotations/PasswordUtils.java
import java.util.*;
public class PasswordUtils
@UseCase(id = 47, description
11 Passwords must contain at least one numeric")
public boolean validatePassword(String password)
return (password.matches("\\w*\\d\\w*tI)) i
@UseCase(id = 48}
public String encryptPassword(String passwordl {
return new StringBuilder (password) . reverse () . toString () ;
@UseCase(id = 49, description =
"New passwords can I t equal previously used ones
ll
)
public boolean checkForNewPassword(
List<String> prevPasswords, String password)
return IprevPasswords.contains(password);
Los valores de los elementos de anotacin se expresan como pares nombre-valor encerrados entre parntesis despus de la
declaracin @:UseCase. A la anotacin para encryptPassword() no se le pasa un valor en el ejemplo para el elemento des-
cription, por lo que cuando se procese la clase con un procesador de anotaciones aparecer el va lor predeterminado defmi-
do en @i nterface UseCase.
Podemos imaginarnos fcilmente cmo podra emplearse un sistema C0l110 ste para "esbozar" un programa y luego relle-
nar la funciona lidad a medida que vamos completando el diseno.
Meta-anotaciones
Actualmente slo hay tres anotaciones estndar (descritas anterionnente) y cuatro meta-anotaciones definidas en el lengua-
je Java. Las meta-anotaciones se utilizan para anotar anotaciones:
<Target Dnde puede aplicarse esta anotacin. Los posibles argumentos de EJementType son:
CONSTRUCTOR: declaracin de constmctor
rIELD: declaracin de campo (incluye constantes enum)
LOCAL_VARIABLE: declaracin de variable local
METHOD: declaracin de mtodo
PACKAGE: declaracin de paquete
PARAMETER: declaracin de parmetro
TYPE: clase, interfaz (incluyendo tipo de anotacin), o declaracin enum

696 Piensa en Java
@Retention Durante cunto tiempo se mantiene la informacin de anotacin. Los posibles argumen-
tos de RetentionPolicy son:
SOURCE: el compilador descarta las anotaciones.
CLASS: las anotaciones estn disponibles en los archivos de clases introducidos por el
compilador, pero pueden ser descartados por la mquina virtual.
RUNTll\1E: las anotaciones son retenidas por la mquina virtual en tiempo de ejecucin,
por lo que se pueden leer mediante el mecanismo de reflexin.
@Documented Incluye esta anotacin en Javadocs.
@Inherited Permite a las subclases heredar anotaciones padre.
La mayor parte del tiempo lo que haremos es definir nuestras propias anotaciones y escribir nuestros procesadores para tra-
tar con ellas.
Escritura de procesadores de anotaciones
Sin una herramienta para leerlas, las anotaciones son apenas ms tiles que los comentarios. Una parte importante del pro-
ceso de uso de las anotaciones consiste en crear y utilizar procesadores de anotaciones. Java SE5 proporciona una serie de
extensiones a la API de reflexin que nos ayudan a crear estas herramientas. Tambin proporciona una herramienta externa
denominada apt para ayudarnos a analizar el cdigo fuente Java que incluya anotaciones.
He aqu un procesador de anotaciones muy simple que lee la clase anotada PasswordUtils y emplea el mecanismo de refle-
xin para buscar marcadores @UseCase. Dada una lista de valores id, enumera los casos de uso y localiza aquellos que fal-
ten, infonnando de esa ausencia:
11: annotations/usecaseTracker.java
import java .lang.reflect.*
import java.util.*;
public class UseCaseTracker
public static void
trackUseCases{List<Integer> useCases, Class<?> el )
for (Method ro : el. getDeclaredMethods ()) {
UseCase uc = m.getAnnotation (UseCase.class ) ;
if {uc ! = nulll {
System.out.println ( "Found Use Case:" + uc .id () +
11 11 + uc. description () ) ;
useCases.remove(new Integer (uc.id ())) i
for (int i : useCases ) {
System. out. println ( "Warning: Missing use case- 11 + i ) i
public static void main (String[J args ) {
List<Integer> useCases = new ArrayList<Integer>() i
Collections.addAll (useCases, 47, 48, 49, 50 ) i
trackUseCases(useCases, PasswordUtils.class ) i
1* Output:
Found Use Case:47 Passwords must contain at least one numeric
Found Use Case:48 no description
Found Use Case:49 New passwords can't equal previously used ones
Warning: Missing use case-50
* /// ,-
20 Anotaciones 697
Este ejemplo utiliza tanto el mtodo de reflexin getDec1aredMethods() como el mtodo geIAnnolalion(), que proviene
de la interfaz AnnolaledElement (clases como Class, Melhod y Field implementan esta interfaz). Este mtodo devuelve
el objeto anotacin del tipo especificado, que en este caso es "UseCase". Si no hay anotaciones de ese tipo concreto en el
mtodo anotado, se devuelve un valor nulL Los valores de los elementos se extraen invocando d( ) Y description( ).
Recuerde que no hemos especificado ninguna descripcin en la anotacin para el mtodo encryptPassword(), por lo que
el procesador anterior localiza el valor predeterminado "no description" al invocar el mtodo description() para esa ano-
tacin concreta.
Elementos de anotacin
El marcador @UseCase definido en UseCase.java contiene el elemento id de tipo int y el elemento description de tipo
String. He aqu una lista de los tipos pennitidos para los elementos de anotacin:
Todas las primitivas (int, 110al, boolean etc.)
Slring
Class
enum
Annotation
Matrices de cualquiera de los tipos anteriores.
El compilador generar un error si se intenta emplear cualquier otro tipo. Observe que no est pennitido utilizar ninguna de
las clases envoltorio de los tipos primitivos, pero gracias a la caracterstica de conversin automtica, esto no es una verda-
dera limitacin. Tambi n podemos tener elementos que sean ellos mismos anotaciones. Como veremos un poco ms ade-
lante, las anotaciones anidadas pueden resultar muy tiles.
Restricciones de valor predeterminado
El compilador es bastante quisquilloso acerca de los valores predeterminados de los elementos. Ningn elemento puede
tener un valor no especificado. Esto quiere decir que los elementos deben tener valores predeterminados o valores propor-
cionados por la clase que utilice la anotacin.
Existe otra restriccin y es que ninguno de los elementos de tipo no primitivo pueden tener null corno valor, ni a la hora de
declararlos en el cdigo fuente ni cuando se los defina como valor predetem1inado dentro de la interfaz de anotacin. Esto
hace que resulte dificil escribir un procesador que acte de manera distinta dependiendo de la presencia o ausencia de un
elemento, porque todos los elementos estn presentes en la prctica en todas las declaraciones de anotaciones. Podemos
obviar este problema tratando de comprobar si existen valores especficos, como por ejemplo cadenas de caracteres vacas
o valores negativos:
jj: annotationsjSimulatingNull.java
import java.lang.annotation.*
@Target{ElementType .METHOD}
@Retention (RetentionPolicy.RUNTIME)
public @interface SimulatingNull {
public int id() default -1;
public String description () default !I!I i
///>
Esta estructura sintcti ca es bastante tpica en las definiciones de anotaciones.
Generacin de archivos externos
Las anotaciones son especialmente tiles a la hora de trabajar con sistemas que requieran algn tipo de infonnacin adicio-
nal como acompaamiento del cdigo fuente. Tecnologas como Enterprise JavaBeans (en las versiones anteriores a EJB3)
requieren numerosas interfaces y descriptores de implantacin que fonnan una especie de "plantilla" de cdigo, definida de
698 Piensa en Java
la misma fomla para cada componente bean. Los servicios web. las bibliotecas de marcadores personalizados y las herra-
mientas de mapeo objeto/relacional como Toplink y Hibf"matc a menudo requieren descriptores XML que son externos al
cdigo. Despus de definir WUI. clase Java, el programador debe lIevtlT a cabo la tediosa tarea de volver a especificar infor-
maciones tales como el nomb!'e, el paquete, etc., es declT, nfonnaciones que ya existen en la clase original. Cada vez que
utilizamos un archivo descriptor externo. tenllin3mos con dos fucntes de infonnacin separadas de una clase, lo que nor-
malmente conduce a que aparezcan problemas de sincronizacin del cdigo. Esto requiere tambin que los programadores
que trabajen en el proyecto sepan cmo editar el descriptor adems de cmo escribir programas Java.
Suponga que queremos proporcionar una funcionalidad bsica de mapeo objeto/relacional para automatizar la creacin de
una tabla de base de datos con el fin de almacenar un componente JavaBean. Podramos utilizar un archivo descriptor XML
para especificar el nombre de la clase, cada de sus miembros y la infonnacin acerca de su mapeo sobre la base de datos.
Sin embargo. utilizando anotaciones, podemos mantener toda la infonnacin en el archivo fuente del componente JavaBean.
Para hacer esto, necesitamos anotaciones para definir el nombre de la tabla de base de dalos asociada con el componente
bean, sus columnas y los tipos SQL que hay que hacer corresponder con las propiedades del componente bean.
He aqu una anotacin para un componente beall que le dice al procesador de anotaciones que tiene que crear una tabla de
base de datos:
//: annotations/database/DBTable.java
package annotations.database
import java. lang. annotation. *
11 Slo se aplica a clases

public @interface DBTable {
public String name () default HH
} 1//, -
Cada tipo de elemento ElementType que especifiquemos en la anotacin @;Target es una restriccin que le dice al compi-
lador que nuestra anotacin slo se puede aplicar a ese tipo concreto. Podemos especificar un slo valor de la enumeracin
enum ElernentType, o bien podemos especificar una lista f0n11ada por cualquier combinacin de valores separados por
comas. Si queremos aplicar la anotacin a cualquier tipo de elemento ElementType. podemos omitir la anotacin @'Target,
aunque esta solucin es bastante poco comn.
Observe que @DOTable tiene un elemento narne( ). de modo que la anotacin pueda suministrar un nombre para la tabla
de la base de datos que el procesador tiene que crear.
He aqu las anotaciones para los campos del componente JavaBean:
/1: annotations/database/Constraints.java
package annotations.database;
import java .lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy .RUNTIME)
public @interface Constraints {
boolean primaryKey() default false
boolean allowNull{) default true;
boolean uniqueCl default false;
!/ 1,-
1/: annotations/database/SQLString.java
package annotations.database
import java.lang.annotation.*;
@Target(ElementType . FIELDl
@Retention(Retent ionPolicy.RUNTIMEl
public @i nterface SQLString {
int value() default O;
String name() default 1111;
Constraints constraints() default @Constraints
} 111,-
jI: annotations/databasejSQLlnteger.java
package annotations.database
import java. lang.annotation. *
@Target(ElementType,FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLlnteger {
String name() default IIII
Constraints constraints(} default @Constraints
1/ 1,-
20 Anotaciones 699
La anotacin @Constraints pennite al procesador extraer los metadatos relativos a la tabla de la base de datos. Esto repre-
senta un pequeo subconjunto de las restricciones que generalmente ofrecen las bases de datos, pero nos permite hacemos
una idea general. Los elementos primaryKey( ), allowNull() y unique() tienen asignados valores predeterminados ade-
cuados. de modo que en la mayora de los casos un usuario de la anotacin no tendr que escribir demasiado texto.
Las otras dos anotaciones @interface definen tipos SQL. De nuevo, para que este sistema sea ms util, necesitamos defi-
nir una anotacin para cada tipo SQL adicional. En nuestro ejemplo, dos tipos sern suficientes.
Cada uno de estos tipos tiene un elemento name() y un elemento constraints( ). Este ltimo hace uso de la caracterstica
de anotaciones anidadas, para incluir la informacin acerca de las restricciones de base de datos aplicable al tipo de colum-
na. Observe que el va lor predetenninado para el elemento contraints() es @Constraints. Puesto que no hay valores de ele-
mentos especificados entre parntesis despus de este tipo de anotacin, el valor predetemnado de cODstraints( ) es en la
prctica una anotacin @Constraints con su propio conjunto de valores predetenninado. Para defi nir una anotacin
@Constraints anidada donde la caracterstica de unicidad est definida como true de manera predeterminada, podemos
definir su elemento de la fonna siguiente:
JJ: annotationsJdatabaseJUniqueness.java
JJ Ejemplo de anotaciones anidadas
package annotations.database
public @interface Uniqueness {
Constraints constraints()
default i
111,-
He aqu un componente bean simple que utiliza estas anotaciones:
JJ: annotationsJdatabaseJMember.java
package annotations.database
@DBTable (name "MEMBER")
public class Member {
@SQLString(30) String firstName
@SQLString(50) String lastName;
@SQLlnteger Integer age;
@SQLString(value : 30,
constraints : @Constraints(primaryKey
String handle i
static int memberCount
true) )
public String getHandle () { return handle i }
public String getFirstName () { return firstName i
public String getLastName () { return lastName;
publ ic String toString () { return handle }
public Integer getAge () { return age i }
1//,-
A la anotacin de clase @DBTable se le da el valor "MEMBER", que se utilizar como nombre de tabla. Las propiedades
de bean, firstName y lastName, estn ambas anotadas con @SQLString y sus valores de elementos son 30 y 50, respecti-
700 Piensa en Java
vamente. Estas anotaciones son interesantes por dos razones: en primer lugar, utilizan el valor predetenninado en la anota-
cin @Constraints anidada. y en segundo lugar emplean una caracterstica especial de abreviatura. Si definimos un ele-
mento de una anotacin con el nombre value. entonces no ser necesario utilizar la si ntaxi s basada en parejas de
nombre-valor siempre y cuando sea el nico tipo de elemento especificado; podemos limitamos a especificar el valor entre
parntesis. Esto puede apli carse a cualqui era de los tipos de elementos legales. Por supuesto, con esto estamos obligados a
ll amar a nuestro elemento "value", pero en el caso anterior, nos pem1ite emplear una especificacin de anotacin semnti-
camente significati va y muy fcil de leer:
@SQLString(30)
El procesador utiliza este valor para establecer el tamao de la columna SQL que cree.
Aunque la sintaxis relativa a los valores predetenninados es bastante limpia, puede volverse muy rpidamente muy comple-
ja. Observe la anotacin correspondiente al campo handle. Tiene una anotacin @SQLString, pero tambin necesita ser
una clave principal de la base de datos, por lo que es necesario activar el tipo de elemento en primaryKey en la anotacin
@Constraint anidada. Aqu es donde el ejemplo comienza a ser confuso. Ahora estamos forzados a utilizar la fonna, bas-
tante larga, basada en una pareja de nombre-valor para esta anotacin anidada, volviendo a especificar el nombre de ele-
mento y el nombre de la interfaz @interface. Pero, como el elemento de nombre especial value ya no es el nico valor de
elemento que se est especificando, no podemos emplear la fonna abreviada. Como puede ver, el resultado no es precisa-
mente elegant e.
Soluciones alternativas
Existen otras fonnas de crear anotaciones para ll evar a cabo esta tarea. Podramos, por ejemplo, tener una nica clase de
anotacin denominada @TableColumn con un elemento enum que definiera valores como STRlNG, lNTEGER, FLOAT,
etc. Esto elimina la necesidad de definir una anotacin @interface para cada tipo SQL, pero hace que sea imposible cuali-
ficar los tipos con elementos adicionales corno size (tamao) o precision (precisin), lo cual resulta probablemente ms til.
Tambin podramos utilizar un elemento de tipo String para describir el tipo SQL correcto, como por ejemplo, "VAR-
CHAR(30)" o "lNTEGER". Esto nos permite cualificar los tipos, pero nos fuerza a fijar en el cdigo la correspondencia
entre el tipo Java y el tipo SQL, lo cual no es una buena prctica de diseo. No conviene tener que recompilar las clases si
cambiamos las bases de datos, sera ms elegante limitarnos a decirle a nuestro procesador de anotaciones que estamos usan-
do una 'versin" diferente de SQL, y dejar que el procesador lo tenga en cuenta a la hora de procesar las anotaciones.
Una tercera solucin factible consiste en utilizar conjuntamente dos tipos de anotacin: @Constraints y el tipo SQL rele-
vante (por ejemplo, @SQLInteger), para anotar el campo deseado. Esto resulta ligeramente complicado, pero el compila-
dor nos pennite utilizar tantas anotaciones diferentes como queramos sobre un mismo objetivo de anotacin. Observe que,
al utili zar mltiples anotaciones, no podemos emplear la mi sma anotacin dos veces.
Las anotaciones no soportan la herencia
No podemos utilizar la palabra clave extends con @interfaces. Es una pena, porque una solucin elegante seria oefinir una
anotacin @TableColumn, como hemos sugerdo anteriormente, con una anotacin anidada de tipo @SQLType. De esa
forma, podriamos heredar todos nuestros tipos SQL, como @SQLInteger y @SQLString de @: SQLType. Esto reducira
la cantidad de texto que hay que escribir y hara ms elegante la sintaxis. No parece que exista ninguna intencin de que las
3l1otaciones soporten el mecanismo de herencia en versiones futuras del lenguaje, por lo que los ejemplos anteriores pare-
cen ser lo mximo que podemos hacer teniendo en cuenta las circunstancias actuales.
Implementacin del procesador
He aqu un ejemplo de procesador de anotaciones que lee un archivo de clases, localiza sus anotaciones de base de datos y
genera el comando SQL para construir la base de datos:
//: annotations/database/TableCreator.java
/1 Procesador de anotaciones basado en el mecanismo de reflexin.
11 {Args: annotations.database.Member}
package annotations.database;
import java.lang.annotation.*;
import java.lang.reflect .*
import java.util.*
public class TableCreator
public static void main(String(] args) throws Exception
if(args.1ength < 1) {
System. out .print ln ("arguments: annotated classes
tl
);
System.exit (O)
for(String className : args)
Class<?> cl = Class. forName (className ) ;
DBTable dbTable = cI.getAnnotation(DBTable.class)
if (dbTab1e == null) {
System.out.println{
"No DBTable annotations in class " + className);
continue;
String tableName = dbTable.name() i
// Si el nombre est vaco, utilizar el nombre de la clase:
if (tableName. length () < 1)
tableName = cl . getName() . toUpperCase( ) ;
List<String> columnDefs = new ArrayList<String>() i
for(Field field : cl.getDeclaredFields() (
String columnName = null;
Annotation[) anns = field.getDeclaredAnnotations() i
if(anns.length < 1)
continue // No es una columna de la tabla de base de datos
if (anns [O) instanceof SQLlnteger)
SQLlnteger sInt = (SQLlnteger) anns[O);
// Utilizar el nombre de campo si no se especifica un nombre .
if(sInt.name() .length() < 1)
columnName field.getName( ) . toUpperCase() ;
else
columnName sInt.name() ;
columnDefs.add(columnName + " INT" +
getConstraints(sInt.constraints())) ;
if (anns (O] instanceof SQLString)
SQLString sString = (SQLString) anns[O);
// Utilizar el nombre de campo si no se especifica un nombre.
if(sString.name() .length() < 1)
columnName field.getName() . toUpperCase() i
el se
columnName sString.name()
columnDefs . add(columnName + 11 VARCHAR(" +
sString. value () + ")" +
getConstraints(sString.constraints() ;
StringBuilder createCommand = new StringBuilder(
"CREATE TABLE " + tableName + ,, ( It)
for (String columnDef : columnDefs)
createCommand. append (" \n "+ columnDef + "," );
// Eliminar coma final
String tableCreate = createCommand.substring(
0, createCornmand.length() - 1 ) + ") ";
System. out .println ("Table Creation SQL for " +
className + " is : \n" + tableCreate)
20 Anotaciones 701
702 Piensa en Java
privace static String getConstraints(Constraints con) {
String constraints = "ti;
if(!con.allowNull(
constraints += " NOT NULL";
if(con.primaryKey() )
constraints +:
if(con.unique(
constraints +=
return constraints;
/* Output:
PRlMARY KEY";
UNIQUE" ;
Table Creation SQL for annocations . database.Member is
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30));
Table Creation SQL for annotations.database . Member is
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30) ,
LASTNAME VARCHAR(50));
Table Creation SQL for annotations.database.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30) ,
LASTNAME VARCHAR(50) ,
AGE INT);
Table Creation SQL for annotations.database.Member is :
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30) ,
LASTNAME VARCHAR (50) ,
AGE INT,
HANDLE VARCHAR(30) PRlMARY KEY);
* /1/,-
El mtodo main( ) recorre sucesivamente cada uno de los nombres de clase en la lnea de comandos. Cada clase se carga
utili zando forName() y se la comprueba para ver si incluye la anotacin @DBTable utilizando getAnnotation(DBTable
.class). Si se incluye esa anotacin, se locali za el nombre de la tabla y se almacena, entonces se cargan y se comprueban
todos los campos de la clase con getDeclaredAnnotations( ). Este mtodo devuelve una matriz con todas las anotaciones
definidas para un mtodo concreto. Se utiliza el operador instanceof para detenninar que estas anotaciones son de tipo
@SQLlnteger y @SQLString, y en cada caso se crea entonces el fragmento de cadena de caracteres relevante, con el nom-
bre de la columna de la tabla. Observe que, como 110 existe posibilidad de herencia en las interfaces de anotacin, la utili-
zacin de getDeclaredAnnotations() es la nica fanna con la que podemos aproximamos al comportamiento polimrfico.
La anotacin @Constraint anidada se pasa al mtodo getConstraints( ), que construye un objeto de tipo String que con-
tiene las restricciones SQL.
Merece la pena mencionar que la tcnica mostrada anterionnente es una fonna un tanto ingenua de definir un mapeo
objeto/rel acional. Di sponer de una anotacin de tipo @DBTable, que toma el nombre de la tabla como parmetro, nos obli -
ga a recompilar el cdigo Java cada vez que queramos cambiar el nombre de la tabla, lo que puede resultar no muy conve-
niente. Hay disponibles muchos sistemas para mapear objetos sobre bases de datos relacionales, y cada vez un nmero
mayor de ellos est haciendo uso de las anotaciones.
Ejercicio 1:
Proyecto:
3
Proyecto:
(2) Implemente ms tipos SQL en el ejemplo de la base de datos.
Modifique el ejemplo de la base de datos para que se conecte con una base de datos real e interacme con
ella utili zando mBC.
Modifique el ejemplo de la base de datos para que cree archivos compatibles con XML en lugar de escri-
bi r cdigo SQL.
J Los proyectos son sugerencias que pueden utilizarse, por ejempl o. como proyectos de fin de curso. Las soluciones a los proyectos no se incluyen en la
Guia de .fOll/clones.
20 Anotaciones 703
Utilizacin de apt para procesar anotaciones
La herramienta de procesamiento de anotaciones apt es la primera versin de Sun de este tipo de herramienta. Puesto que
se trala de una versin relativamente joven, la herramienta sigue siendo un poco primitiva. pero dispone de una serie de
caractersticas que pueden faci litamos la tarea.
Como j avac, apt est diseada para ejecutarse con archivos fuente Java en lugar de con clases compiladas. De manera pre-
detenninada, apt compi la los archivos fuente una vez que ha tenninado de procesarlos. Esto es til si estamos creando auto-
mticamente nuevos archivos fuente como parte del proceso de construccin de la aplicac in. Oc hecho, apt comprueba si
existen anotaciones en los archivos fuente recin creados y los compila, todo ello en una pasada.
Cuando el procesador de anotaciones crea un nuevo archivo fuente, dicho archivo es comprobado a su vez en busca de ano-
taciones, en lo que constituye una nueva ronda (como se denomina en la documentacin) de procesamiento. La herramien-
ta continuar efectuando ronda tras ronda de procesamiento hasta que no se cree ningn archivo fueme. Entonces, compilar
todos los archivos fuente existentes.
Cada anotacin que escribamos necesitar su propio procesador, pero la herramienta apt pennite agrupar fcilmente varios
procesadores de anotacin. La herramienta nos pennite especi ticar mltiples clases que haya que procesar, lo cual resulta
ms fcil que tener que iterar manualmente a travs de una serie de clases File. Tambin podemos agregar lo que se deno-
minan procesos escucha para recibir una notificacin cuando se complete una ronda de procesamiento de anotaciones.
En el momento de escribir estas lneas, apt no est disponible como tarea Ant (consulte el suplemento en htlp://A1indVieH'.
net/Books/BetlerJavo), pero mientras tanto se puede, obviamente, ejecutar la herrami enta como tarea externa desde Ant.
Para compi lar los procesadores de anotaciones descritos en esta seccin, es necesario tener tool s.jar en la ruta de clases;
esta biblioteca tambin contiene las interfaces com.sun.rnirror.*.
apt funciona util izando una factora de procesadores de anotaciones (AnnotationProcessorFactory) para crear el tipo apro-
piado de procesador para cada anotacin que encuentre. Cuando se ejecuta apt, hay que especificar una clase factora o una
ruta de clases en la que la herramienta pueda localizar las factoras que necesite. Si no hacemos esto, apt se embarcar en
un arcano proceso de descubrimiento, cuyos detalles pueden encontrarse en la seccin Deve/oping 011 Anl1orolion Processor
de la documentacin de SUD.
Cuando creamos un procesador de anotaciones para utilizarlo con apt, no podemos emplear los mecanismos de reflexin de
Java porque estamos trabajando con cdigo fuente, no con clases compiladas.
4
La API mirror
5
resuelve este problema per-
mitindonos visualizar los mtodos, campos y tipos en el cdigo fuente no compilado.
He aqu una anotacin que puede utilizarse para extraer los mtodos pblicos de una clase y convertirlos en una interfaz:
11: annotations / Extractlnterface.java
II Procesamiento de anotaciones basado en APT.
package annotationsi
import java.lang.annotation.*
@Target(ElementType .TYPE)
@Retention(RetentionPolicy.SOURCE)
public @i nterface Extractlnterface
pUblic String value();
} 111,-
El valor de RelenlionPoli cy es SOURCE porque no tiene ningn sentido mantener esta anotacin en el archivo de clase
despus de haber extrado de sta la interfaz. La siguiente clase proporciona un mtodo pblico, que podra fonnar parte de
una interfaz:
11 : annotations/Multiplier.java
II Procesamiento de anotaciones basado en APT.
package annotations;
4 Sin embargo, utilizando la opcin no estndar-XclassesAsOecls, se puede trabajar con anOlacioncs que estn contenidas en clases compiladas.
s La palabra mirror significu espejo, as que se trata de un juego de palabras de los diseadores Java para hacer referencia en realidad al mecanismo de
reflexin.
704 Piensa en Java
@Extractlnterface (" IMul tiplier")
public class Multiplier {
public int multiply(int x, int y) {
int total = O;
for(int i = O; i < x; i++l
total = addltotal, y);
return total;
private int add(int x, int y) { return x + y; }
public static void main(String(] argsl
Multiplier m = new Multiplier();
System.out.println("ll*16 = 11 + m. multiply(ll, 16));
/* Output:
11*16 = 176
*///0-
La clase MultipLier (que slo funci ona con enteros positivos) tiene un mtodo multiply() que invoca numerosas veces el
mtodo privado add( ) para llevar a cabo la multiplicacin. El mtodo add( ) no es pblico, as que no forma parte de la
interfaz. A la anotacin se le asigna el valor de lMultiplier, que es el nombre de la interfaz que bay que crear.
Ahora necesi tamos un procesador para realizar la extraccin:
1/: annotations/lnterfaceExtractorProcessor.java
JI Procesamiento de anotaciones basado en APT.
// {Exeeo apt -faetory
JI annotations.lnterfaceExtractorProcessorFactory
JI Multiplier.java -5 .. /annotations}
package annotationsi
import com. sun.mirror.apt.*
import com.sun.mirror.declaration.*
import java.io.*;
import java.util. * ;
public class InterfaceExtractorProcessor
implements AnnotationProcessor {
private final AnnotationProcessorEnvironment env
prvate ArrayList<MethodDeclaration> interfaceMethods
new ArrayList<MethodDeclaration>{);
public InterfaceExtractorProcessor(
AnnotationProcessorEnvironment env) { this.env
public void process{)
for(TypeDeclaration typeDecl :
env }
env.getSpecifiedTypeDeclarations{))
Extractlnterface annot =
typeDecl.getAnnotation(Extractlnterface.class) ;
if(annot == null)
break;
for(MethodDeclaration m : typeDecl.getMethods())
if (m.getModifiers () .contains(Modifier.PUBLIC) &&
! (m.getModifiers{) . contains(Modifier.STATIC)))
interfaceMethods.add(m) ;
if (interfaceMethods.size() > O) {
try {
PrintWriter writer =
env.getFiler() .createSourceFile(annot.value());
writer. println ("package " +
typeDecl.getPackage() .getQualifiedName() +,,;,,);
writer .println ("public interface " +
annot. value () + " {");
for(MethodDeclaration m : interfaceMethods)
}
/// ,-
writer.print(tt public ") i
writer.print (m.getReturnType () + 11) i
wri ter. print (m . getSimpleName () + ( " ) i
int i "" O;
for(ParameterDeclaration parm
ro. getParameters (}) {
writer.print (parm.getType() + !I " +
parm.getSimpleName()) ;
if(++i < m.getParameters(} .size(})
writer.print ( " , 11 );
writer.println(") ;");
writer.println {u}") ;
writer.close() i
catch(IOException ioe)
throw new RuntimeException(ioe);
20 Anotaciones 705
El lugar donde se realiza todo el trabajo es en el mtodo process(). La clase MethodDeclaration y su mtodo
getModifiers() se usan para identificar los mtodos pblicos (pero ignorando los estticos) de la clase que se est proce-
sando. Si se encuentra algn mtodo pblico, se almacena en un contenedor ArrayList y se emplea para crear los mtodos
de una nueva definicin de interfaz en un archivo.java.
Observe que al constructor se le pasa un objeto AnnotationProcessorEnvironment. Podemos consultar este objeto para
detenninar todos los tipos (definiciones de clase) que la herramienta apt est procesando, y podemos usa:-Io para obtener un
objeto Messager y un objeto Filer. El objeto Messager nos permite emitir mensajes dirigidos al usuario, por ejemplo cual-
quier error que pueda haberse producido en el procesamiento, junto con el lugar del cdigo fuente donde se haya produci-
do. El objeto Filer es un tipo objeto PrintWriter a travs del cual se crean nuevos archivos. La principal razn de emplear
un objeto Filer, en lugar de un objeto PriotWriter simple es que pennite a apt llevar la cuenta de los nuevos archivos que
creemos, para as poder comprobar si contienen anotaciones y compi larlas, en caso necesario.
Tambin podemos ver que el mtodo createSourceFile() abre un flujo de salida ordinario con el nombre correcto para nues-
tra interfaz o clase Java. No existe ningn tipo de soporte para la creacin de estmcturas sintcticas del lenguaje Java, as
que bay que generar el cdigo fuente Java utilizando los mtodos print() y println(), un tanto primitivos. Esto quiere decir
que tenemos que asegurarnos de que los corchetes estn bien emparejados y de que el cdigo sea sintcticamente correcto.
La herramienta apt invoca al mtodo process(), porque la herramienta necesita una factora para proporcionar el procesa-
dor adecuado:
11: annotations/lnterfaceExtractorProcessorFactory.java
II Procesamiento de anotaciones basado en APT.
package annotations;
import com.sun.mirror.apt.*
import com.sun.mirror.declaration.*
import java.util.*
public class InterfaceExtractorProcessorFactory
implements AnnotationProcessorFactory {
public AnnotationProcessor getProcessorFor(
Set<AnnotationTypeDeclaration> atds,
AnnotationProcessorEnvironment env) {
return new InterfaceExtractorProcessor(env);
public Collection<String> supportedAnnotationTypes{)
return
COllections . singleton("annotations.Extractlnterface
lt
) ;
706 Piensa en Java
public COllection<String> supportedOptions()
return Collections.emptySet( ) ;
}
111 ,-
Slo hay tres mtodos en la interfaz AnnotationProcessorFactory. Como puede ver, el que proporciona el procesador es
getProccssorFor(), que toma un conjunto Set de declaraciones de tipo (las clases Java para las que se est ejecutando la
herramienta apt), y el objeto An notationProcessor Environment. que ya hemos visto cmo se pasaba al procesador. Los
otros dos mtodos, supportedAnnotationTypes( ) y supportedOptions( ), sirven para poder comprobar que disponemos
de procesadores para todas las anotaciones encontradas por apt y que soportamos todas las anotaciones especificadas en la
lnea de comandos. El mtodo get Processor For( ) es paniculamlente importante. porque si no devolvemos el nombre de
clase completo de nuestro lipo de anotacin dentro de la coleccin Stri ll g, apt emitir una advertencia infonnando de que
no existe el procesador correspondiente y tenninar su ejecucin sin hacer nada.
El procesador y la factora se encuentran en el paquete annotations, as que, para la estructura de directorios anterior. la
lnea de comandos est incrustada en el marcador de comentarios 'Exec' al principio de InterfaceExtractorProcessor.
java. Esto le dice a apt que tiene que utilizar la clase factora definida anterionnente y procesar el archivo Multiplier.java.
La opcin -s especifica que los nuevos archivos deben crearse en el directorio annotatioDs. El archivo IMultiplicr.j ava
generado, como podemos adivinar examinando las instrucciones printl n() en el procesador anterior, tiene el aspecto
siguiente:
package annotations
public interface IMultiplier
public int multiply (int x, int y)
}
Este archivo tambin ser compilado por apt, as que ver que el archivo IMultiplier.class aparece en el mismo directorio.
Ejercicio 2: (3) Aada al extractor de interfaces el soporte para la operacin de divisin.
Utilizacin del patrn de diseo Visitante con apt
El procesamiento de anotaciones puede resultar bastante complejo. El ejemplo anterior es un procesador de anotaciones rela-
tivamente simple que slo interpreta una anotacin, a pesar de lo cual requiere de una cierta complejidad para poder llevar
a cabo su tarea. Para evitar que la complejidad crezca desmesuradamente cuando tengamos ms anotaciones y ms proce-
sadores, la API mirror proporciona clases para dar soporte al patrn de diseo Visitante. Este patrn de diseo es uno de
los patrones clsicos del libro Design Pallerns de Gamma et al., y tambin puede encontrar una explicacin ms detallada
en Thinking in Patlerns.
Un Visitante recorre una estructura de datos o coleccin de objetos, realizando una operacin con cada uno. La estructura
de datos no necesita estar ordenada, y la operacin que se realice con cada objeto ser especfica del tipo de ste. Esto hace
que se desacoplen las operaciones con respecto a los propios objetos, lo que quiere decir que podemos aadir nuevas ope-
raciones sin necesidad de aadir mtodos a las definiciones de clase.
Esto hace que este patrn de diseo sea muy til para el procesamiento de anotaciones, porque una clase Java puede consi-
derarse como una coleccin de objetos TypeDeclar ation, Field DeclaratioD, Met hodDeclaration, etc. Cuando utilizamos
la herramienta apt con el patrn Visitante, proporcionamos una clase Visitor que tiene un mtodo para gestionar cada tipo
de declaracin que visitemos. De este modo, podemos implementar el comportamiento apropiado para las anotaciones aso-
ciadas con mtodos, clases, campos, etc.
He aqu de nuevo el generador de tablas SQL, pero esta vez con una factora y un procesador que hace uso del patrn de
diseo Visitante:
11 : annotations / database/ TableCreationProcessorFactory.java
II El ejemplo de la base de datos usando el patrn de diseo Visitante.
II {Exec: apt -factory
II annotations.database.TableCreationProcessorFactory
II database/Member.java -s database}
package annotations.database
impore
import com.sun.mirror.declaration.*
import com.sun.mirror.ucil.*
impore java.util.*;
impore static com.sun.mirror.ucil.DeclarationVisitors.*
public class TableCreationProcessorFactory
implements AnnotationProcessorFactory {
public AnnotationProcessor getProcessorFor{
Set<AnnotacionTypeDeclaration> atds,
AnnotationProcessorEnvironment env) {
recurn new TableCreationProcessor(env);
public Collection<String> supportedAnnotationTypes()
recurn Arrays.asLisc(
lIannotations.database.DBTable",
lIannotations.dacabase.Constraints
ll
,
"annocations.database.SQLString",
"annocacions.database.SQLlnceger") ;
public Collection<String> supportedOptions{)
return Collections.emptySet();
private static class TableCreationProcessor
implements AnnotationProcessor {
private final AnnotationProcessorEnvironment env
pri vate String sql = 1111;
public TableCreationProcessor(
AnnotationProcessorEnvironment env) {
this. env = env;
public void process()
for {TypeDeclaration typeDecl
env.getSpecifiedTypeDeclarations ())
typeDecl.accept(getDeclarationScanner(
new TableCreationVisitor(), NO_OP));
sql = sql.substring(O, sql.lengthO - 1) + ") ;";
System.out.println(lIcreation SQL is :\nll + sql);
sql = "";
private class TableCreationVisitor
extends SimpleDeclarationVisitor
public void visitClassDeclaration{
ClassDeclaration d) {
DBTable dbTable = d.getAnnotation(DBTable.class);
if(dbTable != null) (
sql += IICREATE TABLE ";
sql += (dbTable.name() .length() < 1)
? d.getSimpleName() .toUpperCase()
dbTable.name() ;
sql += 11 ( 11;
public void visitFieldDeclaration(
FieldDeclaration d)
String columnName = "";
if {d. getAnnotation (SQLlnteger. classl ! = nulll {
20 Anotaciones 707
708 Piensa en Java
SQLlnteger sInt = d.getAnnotation(
SQLlnteger.class) ;
JI Utilizar el nombre del campo si no se especifica un nombre.
i f (s lnt . name () . length ( ) < 1)
columnName d.getSimpleName() . toUpperCase() i
el se
columnName sInt.name{) i
sql += "\n "+ columnName + " INT" +
getConstraints (sInt. constraints ( + 11;
if(d.getAnnotation(SQLString.class) != null) {
SQLString sString = d.getAnnotation(
SQLString.class) ;
JI Utilizar el nombre del campo si no se especifica un nombre .
if(sString.name() .length() < 1)
columnName d.getSimpleName() ,toUpperCase(}
else
columnName sString.name() i
sql += "\n 11 + columnName + " VARCHAR(" +
sString. value () + ")" +
getConstraints (sString . constraints () ) + 11,";
private String getConstraints(Constraints con) {
String constraints = 1111;
if(!con.allowNull() )
constraints += 11 NOT NULLII;
if(con.primaryKey())
constraints += ti PRlMARY KEY";
if(con.unique())
constraints += UNIQUE";
return constraints;
La sal ida es idntica a la del ejemplo DBTable anterior.
El procesador y el visitante son clases internas en este ejemplo. Observe que el mtodo process( ) slo aade la clase visi-
tante e ini cializa la cadena SQL.
Los dos parmetros de getDeclarationScanner( ) son visitantes; el primero se utiliza antes de visitar cada declaracin y el
segundo despus. Este procesador slo necesita el visitante previo visita, as que se proporciona NO _ OP como segundo
parmetro. ste es un campo de tipo esttico de la interfaz DeclarationVisitor, que indica un objeto DeclarationVisitor
que no lleva a cabo ninguna tarea.
TableCreationVisitor amplia SimpleDeciarationVisitor, sustituyendo los dos mtodos visitClassDeciaration() y
visitFieldDeciaration(). SimpleDeciarationVisitor es un adaptador que implementa todos los mtodos de la interfaz
DeclarationVisitor, por lo que podemos concentrarnos en aquellos que necesitemos. En visitClassDeclaration(), se com-
prueba el objeto ClassDeclaration en busca de la anotacin DBTable, y en caso de encontrarla, se inicializa la primera parte
del objeto String de creacin de la cadena SQL. En visitFieldDcciaration( ), se consulta la declaracin de campo para ver
las anotaciones existentes y la informacin se extrae de forma bastante similar a como se hacia en el ejemplo original que
hemos presentado anterionnente en el captulo.
Podra parecer que esta fonna de hacer las cosas resulta ms complicada, pero pennite obtener una solucin ms escalable.
Si la complejidad del procesador de anotaciones se incrementa, escribir nuestro propio procesador autnomo, como en el
ejemplo anterior, podra llegar pronto a resultar bastante complicado.
Ejercicio 3: (2) Aada a TableCreationProcessorFactory.java soporte para ms tipos SQL.
20 Anotaciones 709
Pruebas unitarias basadas en anotaciones
Las pruebas lInilarias son la prctica de crear una o ms pmebas para cada mtodo de una clase. con el fin de comprobar
de fonna meldica las distintas partes de una clase y verificar que su comportamiento es correcto. La herramientas ms
popular de pruebas unitarias en Java se denomina JUl11; en el momento de escribir estas lneas, JUnit estaba a punto de ser
actua lizada a su versin 4, para poder incorporar anotaciones
6
. Uno de los principales problemas de las versiones de JUnit
que no incorporaba el soporte de anotaciones es la cantidad de "ceremonia" necesaria para preparar y ejecutar pruebas
JUnit. Esta complejidad se redujo a lo largo del tiempo. pero las anotaciones permitirn simplificar todava ms el proceso
de pruebas.
Con las versiones de JUnit que no disponan de soporte de anotaciones, era necesario crear una clase separada para definir
las pmebas unitarias. Con las anotaciones, podemos inc luir las pruebas unitarias dentro de la clase que hay probar, reducien-
do as al mni mo el tiempo y la complejidad de las pruebas unitarias. Este tcnica tiene la ventaja adicional de permitir com-
probar tanto los mtodos privados como los pbli cos.
Puesto que este marco de trabajo para pmebas que utilizamos como ejemplo est basado en anotaciones, lo denominaremos
@Unit. La forma ms bsica de pruebas, que es la que uti lizaremos la mayor parte del tiempo, slo necesita la anotacin
@Test para indicar lo que hay que probar. Una opcin es que los mtodos de prueba no tornen ningn argumento y devuel-
van un valor boolean para indicar el xito o el fa ll o de la pmeba. Podemos utilizar cualquier nombre que queramos para los
mtodos de prueba. Asimismo, los mtodos de prueba @Unit pueden tener cualquier tipo de acceso que deseemos, inclu-
yendo private.
Para uti lizar@Unit, todo lo que hace fa lta es importar nCl.mindview.atunit,1 marcar los mtodos y campos apropiados con
marcadores de prueba @Unit (l os cuales veremos en los siguientes ejemplos) y hacer que el sistema de construccin ejecu-
te @Unit con la clase resultante. He aqui un ejemplo simple:
11: annotations/AtUnitExamplel.java
package annotations
import net.mindview.atunit.*
import net.mindview.util.*
public class AtUnitExamplel {
publ ic String methodOne () {
return "This is methodOne"
public int methodTwo()
System. out .println ("This is methodTwo") i
return 2;
@Test boolean methodOneTest () {
return methodOne () . equals ( "This is methodOne")
@Test boolean m2 () { return methodTwo () == 2;
@Test prvate boolean m3 () { return true; }
II Muestra la salida en caso de fallo:
@Test boolean failureTest () { return false;
@Test boolean anotherDisappointment () { return falsej }
public static void main(String[) args) throws Exception
OSExecute .command (
"java net.mindview.atunit.AtUnit AtUnitExamplel" )
1* Output:
annotations.AtUnitExamplel
. methodOneTest
6 Originalmente, pens en discnar una versin avanzada de JUniC basada en el diseno mamado aqu. Sin embargo, pareec que JUnit 4 lambin incluye
muchas de las ideas que aqui ~ presentan, as que me resulta ms scncillo utilizar la herramienta disponible.
7 Esta bibliotcca es parte del cdigo del libro. disponible en wW\\'.AfilldI7f!lulel.
710 Piensa en Java
m2 This is methodTwo
m3
failureTest (failed)
anotherDisappointment (failed)
( S tests)
> 2 FAILURES <
annotations.AtUnitExamplel : failureTest
annotations.AtUnitExamplel : anotherDisappointment
*///,-
Las clases que hay que probar con @Unit deben encontrarse en paquetes.
La anotacin @Test que va antes de los mtodos methodOneTest(), m2(), m3(), failureTest() y anotherDisappoint-
JIlcnt() le dice a @Unit que ejecute estos mtodos como pruebas unitarias. Tambi n garantiza que esos mtodos no tornen
ningn argumento y devuelvan un valor boolean o void. Nuestra nica responsabilidad a la hora de escribir la prueba uni-
taria consiste en detenninar si la prueba tiene xito o falla, y devuel ve true o false, respecti vamente (para los mtodos que
devuelvan un valor boolean).
Si est famili ari zado con JUnit, tambin se habr fijado en que @Unitproporciona una salida ms informativa: puede verse
la prueba que se est ejecutando actualmente, lo que hace que la salida de dicha prueba sea ms til , y al final nos di ce las
clases y pruebas que han producido errores.
No estamos obligados a incluir los mtodos de prueba dentro de nuestras clases, si esa solucin no nos sirve. La fonna ms
fcil de crear pruebas no embebidas es mediante el mecani smo de prueba:
//: annotations/AtUnitEx ternalTest.java
// Creacin de pruebas no embebidas.
package annotations;
import net.mindview.atunit .* ;
import net.mindview. util. *
pUblic cIass AtUnitEx ternalTest extends AtUnitExamplel
@Test boolean _methodOne() {
return methodOne () . equals ("This is methodOne");
@Test boolean _methodTwo() { return methodTwo() == 2; }
pubIic static void main(String(] args) throws Exception
OSExecute.command(
"java net . mindview.atunit.AtUnit AtUnitExternalTes t
tl
) ;
}
/ * Output:
annotations.AtUnitExternalTest
methodOne
_methodTwo This i s methodTwo
OK (2 tests)
*/1/,-
Este ejemplo tambin ilustra el valor de los esquemas de denominacin flexibles Ca diferencia del requisito de JUnit que
exige que todas nuestras pruebas comiencen por la palabra "test"). Aqu, los mtodos @Test que estn probando directa-
ment e otro mtodo reciben el nombre de dicho mtodo, pero comenzando por un guin bajo (no estoy sugiriendo que este
estilo resulte ideal, sino simplemente mostrando una posibilidad).
Tambin podemos utili zar el mecanismo de composicin para crear pruebas no embebidas:
// : annotations/AtUnitCompos i tion . java
// Creacin de pr uebas no e mbebi da s.
package annotations
import net.mindview. atunit. *
import net . mindview. util .* ;
public class AtUnitComposition (
AtUnitExamplel testObject : new AtUnitExamplel() i
@Test boolean _methodOne{) {
return
testObject . methodOne{) . equals("This 15 methodOne
lt
) i
@Test boolean _methodTwo{)
r eturn testObject . methodTwo() == 2;
public static void main(String[] args) throws Exception {
OSExecute.command(
"java net.mindview. atunit.AtUnit AtUnitComposition
tl
);
/* Output:
annotations . AtUnitComposition
methodOne
methodTwo This i5 methodTwo
OK (2 tests)
* /// , -
20 Anotaciones 711
Para cada prueba se crea un nuevo miembro testObject, ya que se crea para cada prueba un obj eto AtUnitComposition.
No exi sten mtodos especial es "assert" como en JUnit, pero la segunda forma del mtodo @Test pennite devol ver void (o
boolean, si seguimos queriendo devolver true o false en este caso). Para comprobar que la prueba ha tenido xito, pode-
mos utilizar instrucciones assert de Java. Las aserciones de Java normalmente tienen que ser habilitadas con el indi cador
-ea en la linea de comandos java, pero @Unit se encarga automticamente de habilitarlas. Para indicar el fall o, podemos
incluso emplear una excepcin. Uno de los objeti vos de di seo de @Unit es imponer la menor cantidad posibl e de sintaxis
adicional , y las excepciones e instrucci ones assert de Java son lo nico necesario para informar acerca de las errores. Una
asercin fallida o una excepci n generada dentro del mtodo de prueba se tratan como una prueba fallida, pero @Unit no
se deti ene en este caso, sino que contina hasta haber ejecutado todas las pruebas.
11 : annotations/AtUnit Example2 . java
11 Se pueden ut i lizar aserciones y excepciones en las pruebas.
package annotations;
import java.io. * ;
import net.mindview.atunit .* ;
import net.mindview.util.*;
public class AtUnitExample2 {
public String methodOne () {
return "This is methodOne " ;
public int methodTwo () {
System.out.println(ItThis is methodTwo
U
);
return 2;
@Test void assertExample()
assert methodOne() . equals( "This is methodOne l! );
@Test void assertFailureExample()
assert 1 == 2 : "Wha t a surprise! ti ;
@Test void exceptionExample() t hrows IOException {
new FilelnputStream( "nofile . txt " ) ; 11 Genera excepcin
@Test boolean assert AndReturn() {
11 Asercin con mensaj e :
assert me thodTwo () == 2: "methodTwo must egual 2";
return methodOne() .equals{ "This is methodOne
lt
);
712 Piensa en Java
public static void main(String[] argsl throws Exception
OSExecute.command(
"java net.mindview.atunit.AtUnit AtUnitExample2"J;
/ * Output:
annotations.AtUnitExample2
assertExample
assertFailureExample java.lang.AssertionError: What a surprise!
Ifailedl
exceptionExample java.io.FileNotFoundException: nofile.txt (The system cannot find
the file specified)
(failedl
assertAndReturn This is methodTwo
(4 tests)
> 2 FAILURES <
annotations.AtUnitExample2: assertFailureExample
annotations.AtUnitExample2: exceptionExample
*jjj,-
He aqu un ejemplo utilizando pruebas no embebidas con aserciones, en el que se realizan algunas pruebas simples de
java.util.HashSet:
JI: annotations/HashSetTest.java
package annotations
import java.util .*;
import net.mindview.atunit.*
import net.mindview.util.*
public class HashSetTest {
HashSet<String> testObject new HashSet<String> ()
@Test void initialization () {
assert testObject.isEmpty();
@Test void _contains () {
testObject.add{"one") i
assert testObject.contains{llone");
@Test void _remove{) {
testObject.add{ "one" ) ;
testObject. remove ("one");
assert testObject.isEmpty{);
public static void main(String[) args) throws Exception
OSExecute.command(
"java net.mindview.atunit.AtUnit HashSetTest
ll
);
/* Output:
annotations.HashSetTest
initialization
remove
contains
OK (3 tests )
* jjj ,-
La solucin basada en la herencia parece ms simple. en ausencia de otras restricciones.
Ejercicio 4:
(3) Verifique que se crea un nuevo objeto testObject antes de cada prueba.
Ejercicio 5:
Ejercicio 6:
Ejercicio 7:
(l) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia.
(1 1 Pruebe LinkedList utilizando la tcnica mostrada eo HashSetTest.java.
(1) Modifique el ejercicio anterior para utilizar la solucin basada en la herencia.
20 Anotaciones 713
Para cada pmeba unitaria, @Unit crea un objeto de la clase que se est probando utilizando un constructor predetenninado.
Se invoca la prueba para dicho objeto, y a continuacin se descarta el objeto para evitar que se deslicen efectos secundarios
en otras pruebas unitarias. Esta solucin utiliza el constructor predeterminado para crear los objetos. Si no disponemos de
un constructor predetenrunado o necesitamos un mecanismo ms sofisticado de construccin de los objetos, bay que crear
un mtodos esttico para generar el objeto y asociar la anotacin @TestObjectCreate, como en el ejemplo siguiente:
11 : annotations/AtUnitExample3.java
package annotations
import net.mindview.atunit.*
import net . mindview.util.*
public class AtUnitExample3
private int n
public AtUni tExample3 (int n) { this. n n }
public int getN () { return n
public String methodOne () {
return "This is methodOne"
public int methodTwo () {
System.out.println(01This is methodTwo")
return 2;
@TestObjectCreate static AtUnitExample3 create () {
return new AtUnitExample3(47)
@Tes t boolean initializationO { return n == 47
@Test boolean methodOneTest() {
return methodOne () . equals ("This is methodOne")
@Test boolean m2 () { return methodTwo () == 2;
public static void main(String(] args) throws Exception
OSExecute.command(
"java net.mindview.atunit.AtUnit AtUnitExample3") i
1* Output:
annotations.AtUnitExample3
initialization
methodOneTest
m2 This is methodTwo
OK (3 tests )
* ///,-
El mtodo @TestObjectCreate debe ser esttico y debe devolver un objeto del tipo que estemos probando; el programa
@Unit se encargar de comprobar que esto es as.
En ocasiones, necesitamos campos adicionales para realizar las pruebas unitarias. Podemos utilizar la anotacin
@TestProperty para marcar aquellos campos que slo se utilicen en las pruebas unitarias (con el fin de poderlos eliminar
antes de entregar el producto al cliente). He aqui un ejemplo que lee valores de un objeto Striog que se descompone utili-
zando el mtodo Stri ng.split( l. Esta entrada se emplea para generar objetos de prueba:
//: annotations/AtUnitExample4.java
package annotationsi
import java.util.*
import net.mindview.atunit.*
714 Piensa en Java
import net.mindview util.*;
import static net . mindview.util.Print.*;
public class AtUnitExample4 (
static String theory = "All brontosauruses u +
"are thn at ane end, much MUCH thicker in the !I +
"middle, and then thin again at the far end.";
private String word
private Random rand = new Random(); // Semilla basada en tiempo
public AtUnitExample4 (String word) { this.word = word }
public String getWord() { return word }
public String scrambleWord () (
List<Character> chars = new ArrayList<Character>() i
for(Character e : word.toCharArray())
chars.add(c) i
Collections.shuffle(chars, rand) i
StringBuilder result = new StringBuilder();
for(char eh : chars)
result.append(ch) ;
return result.toString() i
@TestProperty sta tic List<String> input
Arrays. asList (theory. spli t (" "));
@Test Property
static Iterator<String> words = input.iterator();
@TestObjectCreate static AtUnitExample4 create () {
if(words.hasNext())
return new AtUnitExample4(words.next());
else
return null;
@Test boolean words()
print (" ,,, + getWord () + 11 1 ") ;
return getWord () . equals ( liare" ) ;
@Test boolean scramblel () {
// Cambiar a una semilla especfica para obtener resultados verificables :
rand = new Random(47);
print ( 11 1 11 + getWord () + 11 1 11) i
String scrambled = scrambleWord();
print (scrambl ed) ;
return scrambled.equals(lIlAllI);
@Test boolean scramble2()
rand = new Random(74)
print (U I 11 + getWord () + U 1 U)
String scrambled = scrambleWord();
print(scrambled) ;
return scrambled .equals(Utsaeborornussu
lt
) i
public static void main {St ring[] args) throws Exception {
System. out. println ( U starting
U
) ;
OSExecute.command(
lIjava net.mindview.atunit.AtUnit AtUnitExample4
1t
);
/ * Output:
starting
annotations.AtUnitExample4
scramblel I All '
lAl
. scramble2 'brontosauruses'
tsaeborornussu
words 'are'
OK (3 tests)
* ///,-
20 Anotaciones 715
Tambin podemos usar @TestProperty para marcar mtodos que pueden ser utilizados durante las pruebas, pero que no
sean pruebas en s mismos.
Observe que este programa depende del orden de ejecucin de las pruebas, lo cual no es, en general, una buena prctica.
Si nuestro proceso de creacin de objetos de pruebas realiza una ini ciali zacin que requiera una posterior limpieza. pode-
mos aadir opcionalmente un mtodo esttico @TestObjectCleanup para realizar la limpi eza cuando hayamos tern1inado
de usar el objeto de pmeba. En este ejemplo, @TestObjectCreatc abre un archivo para crear cada objeto de pmeba, as que
es necesario CCITar el archivo antes de descartar el objeto de prueba:
/1 : annotations/AtUnitExampleS.java
package annotations
import java.io.*;
import net.mindview.atunit.*;
import net.mindview.util.*;
public class AtUnitExampleS
private String text;
public AtUnitExampleS (String text) { this. text
public String toString () { return text; )
@TestProperty static PrintWriter output
@TestProperty static int counter
@TestObjectCreate static AtUnitExampleS create()
String id = Integer.toString(counter++);
try (
text; }
output = new PrintWriter ("Test" + id + ". txt") ;
catch (IOException e) {
throw new RuntimeException (e ) ;
return new AtUnitExampleS(id)
@TestObjectCleanup static void
cleanup (AtUni tExampleS tobj) {
System.out.println("Running cleanup") i
output. clase () ;
@Test boolean testl()
output.print(l1testl")
return true;
@Test boolean test2()
output. print (" test2")
return true;
@Test boo!ean test3()
output.print("test3") ;
return true;
public static void main(String[] args ) throws Exception
OSExecute.command{
"java net.mindview.atunit . AtUnit AtUni tExampleS " ) i
/ * Output:
716 Piensa en Java
annotations.AtUnitExampleS
testl
Running cleanup
test2
Running cleanup
test3
Running cleanup
OK (3 tests)
*///,-
Podemos ver, anal izando la sal ida, que despus de cada prueba se ejecuta automticamente el mtodo de limpi eza.
Utilizacin de @Unit con genricos
Los genri cos plant ean un problema especial , porque no resulta posibl e "probar genri camente". Debemos efectuar las prue-
bas para un parmetro de tipo especfi co o un conjunto de parmetros de tipo. La solucin es simple: heredar una clase de
prueba a partir de una versin especifi cada de la clase genrica.
He aqu una impl ementacin simple de una pila:
11: annotations/StackL.java
II Un pila construida sobre un contenedor linkedList.
package annotations
import java.util.*
public class StackL<T>
private LinkedList<T> list : new LinkedList<T>();
public void push (T v) { list. addFirst (v); }
public T top() { return list.getFirst(); }
public T pop () { return list. removeFirst ()
///,-
Para probar una versin String, hereda una clase de pmeba de StackL<String>:
11 : annotations/StackLStringTest.java
11 Aplicacin de @Unit a genricos.
package annotations;
import net.mindview.atunit.*;
import net.mindview.util.*
public class StackLStringTest extends StackL<String> {
@Test void yush () {
push("one " ) ;
assert tap() .equals("one
ll
) i
push{"two") i
assert top() .equals("two") i
@Test void yop ()
push(lIone"l
push("two") ;
assert pap () . equals (11 t wo
ll
) i
assert pop () . equals ("one") ;
@Test void _ top()
push("A"l i
pUSh("S") ;
assert top{).equals(IB");
assert top{).equals("B");
public static void main(String(] args) throws Exception {
OSExecute.command(
"java net.mindview.atunit.AtUnit StackLStringTest"l i
}
/ * Output:
annotations.StackLStringTest
yush
. J'op
. _tap
OK (3 tests)
*/ // ,-
20 Anotaciones 717
La nica desventaja potencial de la herencia es que perdemos la capacidad de acceder a los mtodos privados en la clase
que se est probando. Si esto constituye un problema, podemos definir el mtodo en cuestin como protected, o ailadir un
mtodo no privado @TestProperty que invoque al mtodo privado (el mtodo @TestProperty ser luego eliminado del
cdigo de produccin por la berramienta AlU nitRemover que se muestra ms adelante en el captulo).
Ejercicio 8: (2) Cree una clase con un mtodo privado y anada un mtodo @TestProperty no privado como se ha des-
crito anterionnente. Invoque este mtodo en su cdigo de pruebas.
Ejercicio 9: (2) Escriba pruebas @Unit bsicas para HashMap.
Ejercicio 10: (2) Seleccione un ejemplo de algn otro lugar del libro y aada pruebas @Unit.
No hace falta ningn "agrupamiento"
Una de las grandes ventajas de@UnitsobreJUnitesquenohacen falta "agrupamientos". En JUnit, necesitamos poder decir
de alguna fanna a la herramienta de pruebas unitarias qu es lo que necesitamos probar, y esto requiere la introduccin de
"agrupamientos" de pmebas, para que JUnit pueda encontrarlos y ejecutar las pruebas.
@Unit simplemente busca archivos de clase que contengan las anotaciones apropiadas, y ejecuta a continuacin los mto-
dos @Test. Uno de los principales objetivos que me plante al desarrollar el sistema de pruebas @Unit es que fuera enor-
memente transparente, para que los desarrolladores pudieran comenzar a utilizarlo simplemente alladiendo mtodos @Test,
sin ningn otro cdigo especial y sin ningn conocimiento adicional como los requeridos por JUnit y muchos otros
sistemas de pruebas unitarias. Ya es suficientemente dificil escribir pruebas sin aadir nuevos errores, como para tambin
perder el tiempo con complicaciones innecesarias, as que @Unit trata de hacer que la tarea de definir las tareas unitarias
sea trivial. De esta fonna, resulta ms probable que el diseador se anime a escribir esas pruebas.
Implementacin de @Unit
En primer lugar, necesitamos definir todos los tipos de anotacin. Se trata de marcadores simples que no tienen ningn
campo. El marcador @Test ya se ha definido al principio del captulo y aqu estn el resto de las anotaciones:
//: net/mindview/atunit/TestObjectCreate.java
// El marcador @Unit @TestObjectCreate.
package net.mindview.atunit;
import java.lang.annotation.*;
@Target(ElementType .METHOD)
@Retention(RetencionPolicy.RUNTIME)
public @interface TestObjectCreate {} ///,-
//: net/mindview/atunit/TestObjectCleanup.java
/1 El marcador @Unit @TestObjectCl eanup.
package net.mindview.atunit
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestObjectCleanup {} ///,-
//: net/mindview/atunit/TestProperty.java
718 Piensa en Java
JI El marcador @Unit @TestProperty.
package net.mindview. atunit;
impore java.lang.annotation. * ;
JI Se pueden marcar como propiedades tanto los campos como los mtodos:
@Ta rget({ElementType . FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestProperty {} 1//:-
Todas las pruebas ti enen un tipo retencin igual a RUNTlME, porque el sistema @Unit debe descubrir las pruebas en el
cdigo compil ado.
Para implementar el sistema que ejecuta las pruebas, utilizamos el mecani smo de refl exin para extraer las anotaciones. El
programa utili za esta informacin para decidir cmo constmir los obj etos de prueba y para ej ecutar las pruebas sobre ellos.
Grac ias a las anotaciones el sistema es sorprendentemente pequeo y sencillo:
JI : net /mindview/ atunit / AtUnit.java
/1 Un sistema de pruebas unitarias basadas en anotaciones.
// {RunByHand}
package net.mindview.atunit;
impert java.lang.reflect. * ;
import java.io.*;
import java.util.*;
impert net.mindv iew.util .* ;
impert static net . mindview.util.Print. *;
public class AtUnit implements ProcessFiles.Strategy
static Class<?> testClass;
static List<String> failedTests= new ArrayList<String>(};
static long testsRun = O;
static long failures = O;
public static void main(String[] args ) throws Exception {
ClassLoader.getSystemClassLoader(}
.setDefaultAssertionStatus(true) ; II Habilitar aserciones
new ProcessFiles(new AtUnit( ) , uclass" } .start(args);
if(failures O)
print ( "OK (" + testsRun + " tests)" ) ;
else {
print ( II(1I + testsRun + " tests)");
print("\n> It + failures + It FAILURE
u
+
(failures > 1 ? "Su: "") + " <");
fer(String failed : failedTests)
print (" "+ failed);
public void process(File cFile)
try {
String cName = ClassNameFinder . thisClass(
BinaryFile.read(cFile)) ;
if (! cName. contains (" . ") )
return; // Ignorar clases no empaquetadas
testClass = Class. forName (cName) ;
ca tch (Except ion e ) {
throw new RuntimeException (e ) ;
TestMethods testMethods = new TestMethods();
Method creator = null;
Method cleanup = null;
for(Method m : testCl ass.getDeclaredMethods())
testMethods . addlfTestMethod(m) ;
if (creator == null}
creator = checkForCreatorMethod{rn);
if(cleanup == null)
cleanup = checkForCleanupMethod(rn)
if(testMethods.size() > O) {
if(creator == null)
try (
if(lModifier.isPublic(testClass
. getDeclaredConstructor () . getModifiers () ) )
print (ji Error: JI + testClass +
" default constructor rnust be public")
Systern. exit (1) ;
catch (NoSucbMethodExcepton el {
II Constructor determinado sintetizado; OK
print(testClass.getName())
for(Method m
printnb ( "
try (
testMethods ) {
!I + m. getName () + ti n)
Object testObject = createTestObject{creator);
boolean success = false;
try (
if (m . getReturnType () . equals (boolean. class l )
success = (Boolean)m.invoke {testObject );
else (
m.invoke(testObjectl
success = true; II Si no falla ninguna asercin
catch(InvocationTargetException el
jI La excepcin en s est dentro de e:
print (e .getCause () ) ;
print (success ? 11 ti : "(failed)");
testsRun++
if(!success) {
failures++
failedTests.add(testClass.getName{) +
": " + m.getName())
if(cleanup != null}
cleanup. nvoke (testObject, testObject);
catch(Exception e) {
throw new RuntimeException{e) ;
static class TestMethods extends ArrayList<Method>
void addlfTestMethod(Method m) {
if(m.getAnnotation(Test.class) == null)
return;
if(! (m .getReturnType {) . equals(boolean . class) 11
m.getReturnType{) .equals{void.class)))
throw new RuntimeException ( "@Test method" +
" must return boolean or void");
rn.setAccessible(true); II En caso de que sea privado, etc.
add(m) ;
20 Anotaciones 719
720 Piensa en Java
private static Method checkForCreatorMethod(Method m)
if(m.getAnnotation(TestObjectCreate . class) == null)
return null
if (!m. getReturnType() . equals(testClass
throw new RuntimeException (U@TestObjectCreate 11 +
"must return instance of Class to be tested") i
if m.getModifiers () &
java .lang. reflect .Modifier. STATIC) < 1)
throw new RuntimeException (II@TestObjectCreate " +
"must be static.");
m. setAccessible (true) ;
return m;
private static Methad checkForCleanupMethod(Method m) {
if(m.getAnnotation(TestObjectCleanup.class) == null)
return null;
if(lm.getReturnType() .equals(void.class
throw new RuntimeException ("@TestObj ectCleanup " +
"must return void") ;
if { (m.getModifiers () &
java.lang.reflect.Modifier.STATIC) < 1)
throw new RuntimeException (u@TestObj ectCleanup " +
"must be static.");
if (m.getParameterTypes () .length == O I I
m. getParameterTypes () [O l ! = testClass)
throw new RuntimeException ( "@TestObj ectCleanup +
"must take an argument of the tested type.");
m.setAccessible{true) i
return m;
private static Object createTestObject(Method creator) {
if(creator != null) {
try {
return creator.invoke(testClassl;
catch (Exception el {
throw new RuntimeException ("Couldn ' t run " +
"@TestObject (creator) method.");
else { II Utilizar el constructor predeterminado:
try (
return testClass.newlnstance{);
catch (Exception e) {
throw new RuntimeException ( "Coulctn' t crea te a " +
"test object. Try using a TestObject method.") i
AlUnit.java utiliza la herramienta ProcessFiles de net.mindview.util. La clase AtUnit implementa ProcessFiles.Strategy,
que contiene el mtodo process(). De esta forma, se puede pasar una instancia de AlUnit al constructor ProcessFiles. El
segundo argumento del constructor le dice a ProcessFiles que busque todos los archivos que tengan la extensin "c1ass".
Si no proporcionamos un argumento de lnea de comandos, el programa recorrer el rbol de directorios actual. Tambin
podemos proporcionar mltiples argumentos que pueden ser archi vos de clase (con o sin la extensin .c1ass) o directorios.
Puesto que @Unit encuentra automticamente las clases y mtodos que son susceptibles de prueba, no hace falta ningn
mecanismo de "agrupamiento".
8
8 No est claro por qu el constructor prcdetemlinado de la clase que estemos probando debe ser pblico, pero si no lo es, la llamada a lIewl nstance() se
cuelga (sin generar una excepcin).
20 Anotaciones 721
Uno de los probl emas que AtUnit.java debe resolver cuando descubre archivos de clase es que el nombre de clase real cua-
lificado (incl uyendo el paquete) no resulta evidente a partir del nombre de archivo de clase. Para descubrir esta informacin,
debe analizarse el archivo de clase, lo cual no es trivial, aunque tampoco imposible.
9
Por tanto, lo primero que sucede cuan-
do se locali za un archivo .class es que se abre y sus datos binarios son leidos y entregados a ClassNameFinder.thisClass( ).
Aqu , nos estamos introduciendo en el campo de la "ingeniera de cdigo intennedio", porque lo que estamos haciendo es
anali zar el contenido de un archivo de clase:
ji: net/mindview/atunit/ClassNameFinder.java
package net.mindview.atunit
import java.io.*;
import java.util. *;
import net.mindview.util.*
import static net.mindview.util.Print.*
public class ClassNameFinder {
public static String thisClass(byte[] classBytes)
Map<Integer,Integer> offsetTable =
new HashMap<Integer,Integer>();
Map<Integer,String> classNameTable
new HashMap<Integer,String>();
try (
DataInputStream data = new DataInputStream(
new ByteArrayInputStream(classBytes));
int magic = data . readInt () ; // Oxcafebabe
int minorVersion = data.readShort();
int majorVersion = data.readShort();
int constant-pool_count = data.readShort();
int[] constant-pool = new int[constant_pool_count];
for (int i = 1; i < constant -poo1_ count; i++) {
int tag = data.read();
int tableSize
switchltag) (
case L II UTF
int length = data.readShort();
char{] bytes = new char[length];
for(int k = O; k < bytes.length; k++)
byteslkl = Ichar)data . readl);
String className = new String(bytes);
classNameTable.put(i, className );
break
case 5, II LONG
case 6, II DOUBLE
data.readLong(); // descartar 8 bytes
i++ // Salto especial necesario
break;
case 7, II CLASS
int offset = data.readShort();
offsetTable.put(i, offset);
break;
case 8, II STRING
data.readShort(); // descartar 2 bytes
break;
case 3,
II
INTEGER
case 4,
II
FLOAT
case 9,
II
FIELD REF
case 10,
II
METHOD REF
case 11,
II
I NTERFACE METHOD REF
-
9 Jcrcmy Meyer y yo nos pasamos la mayor parte de una jornada tTIltando de descubrir la solucin.
722 Piensa en Java
case 12 , II NAME_AND_TYPE
data.readlnt(); // descartar 4 bytes;
break;
default:
throw new RuncimeException (UBad tag n + tag);
short access_flags = data.readShort();
int this class = data.readShort();
int super_class = data.readShort()
return classNameTable.get(
offsetTable. get (this_class ) ) . replace ( '/ ' , '. ,) i
catch(Exception el {
throw new RuntimeException{e);
JI Ilustracin:
public static vOld main(String[) args) throws Exception {
if (args . length > O) (
for{String arg : args)
print (thisClass (BinaryFil e.read(new File (arg ))));
else
JI Recorrer todo el rbol:
tor (File klass : Directory. wa lk (" . ", u. * \ \ . class") )
print(thisClass(BinaryFile.read(klass))) ;
}
111,-
Aunque no podemos analizar aqu todos los detalles, cada archivo de clase se ajusta a un fonnato concreto y hemos tratado
en el ejemplo de utilizar nombres de campo significativos para los fragmentos de datos extrados del flujo de datos
ByteArraylnputStream; tambin podemos ver el tamao de cada fragmento examinando la longitud de la lectura realiza-
da en el flujo de entrada. Por ejemplo, los primeros 32 bits de cualquier archivo de clase son siempre el "nmero mgico"
oxcafebabe, JO y los dos si guientes valores short son la infonnacin de versin. La seccin de constantes contiene las cons-
tantes del programa y es, por tanto, de tamao variable; el siguiente valor short nos dice cul es el tamao para poder asig-
nar una matri z del tamao apropiado. Cada entrada de la seccin de constantes puede ser un valor de tamao fijo o variable,
as que tenemos que exami nar el marcador con el que comienza cada uno para ver qu hay que hacer con l, sa es la razn
de la instmccin switch. Aqu, no estamos tratando de analizar con precisin todos los datos del archivo de clase, sino sim-
plemente recorrer sta y extraer los fragmentos de inters. as que, como puede ver en el ejemplo, se descarta una gran can-
tidad de datos. La infom,acin acerca de las clases est almacenada en las tablas classNameTable y offsetTable. Despus
de leer la seccin de constantes, podemos encontrar la infomlacin this_class que es un ndice para la tabla offsetTablc, que
produce un ndice para la tabla classNameTable, en la que podemos leer el nombre de la clase.
Volviendo a AtUnt.java, el mtodo process( l abara di spone del nombre de la clase y puede tratar de detenninar si contie-
ne ".', lo que quiere decir que est dentro de un paquete. Las clases no incluidas en un paquete se ignoran. Si una clase se
encuentra en un paquete se utiliza el cargador de clases estndar para cargar la clase con Class.forNamc( l. Ahora podemos
analizar la clase en busca de anotaciones @Unit.
Slo necesitamos buscar tres cosas: mtodos @Test, que estn almacenados en una li sta TestMethods, y si existen mto-
dos @TestObjectCrcate y @TestObjectCleanup. Estos mtodos se descubren mediante las llamadas a mtodo asociadas
que se pueden ver en el cdigo, que buscan las correspondientes anotaciones.
Si se encuentra algn mtodo @Test, se imprime el nombre de la clase para que podamos ver lo que est sucediendo, a con-
tinuacin de lo cual se ejecuta cada prueba. Esto implica imprimir el nombre de un mtodo, luego invocar
crcateTestObjcct( l , el cual utilizara el mtodo @TestObjectCreate si existe o utilizar el constructor predeterminado si
no exi ste. Una vez creado el objeto de prueba, se invoca el mtodo de prueba para dicho objeto. Si la pmeba devuelve un
10 Hay varias leyendas relat ivas al significado de este nmero mgico, pero como Java fue creado por autnticos frikies, podemos suponer, razonablemen-
te, que tiene algo que ver con fantasas adolescentes acerca de una mujer en una cafeteria.
20 Anotaciones 723
valor boolean, se captura el resultado. Si no. presuponemos que la prueba ha tenido xito a menos que se genere una excep-
cin (que es lo que sucedera en caso de que se produzca una asercin fallida o cualquier OlTO tipo de excepcin). Si se gene-
ra una excepcin, se imprime la infom13cin de excepcin para mostrar la causa. Si se produce cualquier fallo, se incrementa
el contador de fallos y se aade el nombre de la clase y el mtodo a fa iledTests para poder incluirlos en el infonme que se
genera al final de la ejecucin.
Ejercicio 11 : (5) Aliada una anotacin @TestNote a @Unit, para que la nota asociada se visualice simplemente duran-
te las pmebas.
Eliminacin del cdigo de prueba
Aunque en muchos proyectos no pasa nada si dejamos el cdigo de prueba en el cdigo final (especialmente si definimos
todos los mtodos de prueba como private, cosa que siempre podemos hacer), en algunos casos conviene eliminar el cdi-
go de pmeba, para que el tamaiio del producto sea menor o para que ese cdigo no est al alcance del cliente.
Esto requiere prcticas de ingeniera de cdigo intermedio demasiado sofisticadas como para realizar las manualmente. Sin
embargo, la biblioteca de cdigo abierto Javassist
ll
hace posible la ingeniera de cdigo intemledio. El siguiente programa
admite un indicador -r opcional como primer argumento; si incluimos el indicador, eliminar las allmaciones @"Test, mien-
tras que si no lo incluimos se limitar a mostrar esas anotaciones. Tambin se emplea aqu ProcessFil es para recorrer los
archivos y direclOrios que hayamos elegido:
11 : net/mindview/atunit/AtUnitRemover.java
II Visualiza las anotaciones @Unit existentes en los archivos de
II clase compilados. Si el primer argumento es " _r", se eliminan
II las anotaciones @Unit.
// (Argso .. )
II {Requires: javassist.bytecode.ClassFile
II Debe instalar la biblioteca Javassist disponible en
// http, // sourceforge.net / projects / jboss/ )
package net.mindview.atunit
import javassist.*
import javassist.expr. *
import javassist.bytecode.*
import javassist.bytecode.annotation.*
import java.io.*;
import java.util.*;
import net.mindview.util.*
import static net.mindview.util.Print.*
public class AtUnitRemover
implements ProcessFiles.Strategy
private static boolean remove = false;
public static void main (String[] args ) throws Exception
if (args .length > O && args [01 . equals ( " -r" )) (
remove = true
String[ ] nargs new String{args.length - 1]
System. arraycopy (args, 1, nargs, 0, nargs.length)
args = nargs;
new ProcessFiles(
new AtUnitRemover () , "class" ) .start (args } ;
public void process(File cFile )
boolean modified = false
try {
String cName = ClassNameFinder.thisClass(
11 Gracias al Dr. Shigeru Chiba por crear eSIa biblimeca, y por toda la ayuda que me prest a la hora de desarrollar AlUnit Removcr-.j ava.
724 Piensa en Java
BinaryFile.read(cFile)) i
if (! cName. contains (n . 11) )
return; /1 Ignorar clases no empaquetadas
ClassPool cPool = ClassPool.getDefault();
CtClass ctClass = cPool.get(cName);
for{CtMethod methad : ctClass.getOeclaredMethods())
Methodlnfo mi = mechod.getMethodlnfo() i
AnnotationsAttribute attr = (AnnotationsAttribute)
mi.getAttribute(AnnotationsAttribute.visibleTag) i
)
if(attr == null) continue;
for(Annotation ann : attr.getAnnotations())
if(ann.getTypeName()
. startsWi th (ltnet. mindview. atuni t") )
print (ctClass .getName () + 11 Methad:
+ mi.getName() + " " + ann);
if (remove) {
ctClass.removeMethod(method) i
modified = true;
/1 Los campos no se eliminan en esta versin (vase el texto).
if (modifiedl
ctClass.toBytecode(new DataOutputStream(
new FileOutputStream(cFile)));
ctClass.detach() ;
catch (Exception el {
throw new RuntimeException(e} i
)
/// ,-
ClassPoo) es una especie de resumen de todas las clases del sistema que estemos modificando. Garantiza la coherencia de
todas las clases modificadas. Podemos extraer cada clase CtClass de ClassPool, de fonna similar a como el cargador de
clases y Class.forName() cargan las clases en la mquina NM.
CtClass contiene el cdigo intermedio de un objeto de clase y nos permite generar infomlacin acerca de la clase y mani-
pular el cdigo de la mi sma. Aqu, invocamos getDeclaredMethods( ) (al igual que el mecanismo de reflexin de Java) y
obtenemos un objeto Methodlnfo a partir de cada mtodo CtMethod. Con esto, podemos examinar las anotaciones. Si
algn mtodo tiene una anotacin en el paquete net.mindview.atunit, se elimina dicho mtodo.
Si la clase ha sido modificada, se sobreescribe el archivo de clase original con la nueva clase.
En el momento de escribir estas lneas, se acababa de aadir la funcionalidad de "eliminacin" de Javassist
12
, y descubri-
mos que eliminar los campos @TestProperty resulta ms complejo que eliminar los mtodos. Dado que pueden existir ope-
raciones de inicializaci n esttica que hagan referencia a esos campos, no podemos limitarnos a borrarlos. Por tanto, la
versin anterior del cdigo slo elimina los mtodos @Unit. Sin embargo, consulte el sitio web de Javassist para ver si exis-
ten actualizaciones: es posible que en el futuro se aada la funcionalidad de eliminacin de campos. Mientras tanto, obser-
ve que el mtodo de prueba externo mostrado en AtUnitExternaJTest.java permite eliminar todas las pruebas simplemente
borrando todos los archivos de clase creados por el cdigo de prueba.
Resumen
Resulta muy de agradecer que se hayan aadido las anotaciones a Java. Constituyen una forma estructurada (y con compro-
bacin de tipos) de aadir metadatos al cdigo sin hacer que ste se complique innecesariamente y resulte ilegible. Las
12 El Dr. Shigeru Chiba fue tan amable de aadir el mtodo CtClass.remo\'cMethod( ) a solicitud nuestra.
20 Anotaciones 725
anotaciones pueden ayudarnos a eliminar la tediosa tarea de escribir descriptores de implantacin y otros archivos genera-
dos. El hecho de que el marcador Javadoc @deprecatcd haya sido sustiruido por la anotacin @Deprecated es simplemen_
te una indicacin de hasta qu punto las anotaciones son mucho ms convenientes que 105 comenrarios para describir la
informacin de las clases.
Java SES slo incluye un pequeo nmero de anotaciones. Esto quiere decir que, si 110 utiliza una biblioteca de otro fabri-
cante, necesitar crear sus propias anotaciones, junto con la lgica asociada. Con la herramienta apt , podemos compilar los
archivos recin generados en un nico paso, facilitndose as el proceso de constmccin de aplicaciones. pero actualmente
la API miTror tan slo incluye la funcionalidad bsica para ayudarnos a identificar los elementos de las definiciones de cla-
ses Java. Como hemos visto, podemos utilizar Javassist para las tareas de ingeniera de cdigo intennedio, o bien podemos
escribir a mano nuestras propias herramientas de manipulacin de cdigo intennedio.
La situacin mejorar sin ninguna duda en el futuro, y los fabricantes de interfaces API y sistemas comenzarn a proporcio-
nar anotaciones como parte de sus herramientas. Como puede imaginarse al analizar el sistema @Unit, resulta bastante pre-
visible que las anotaciones provoquen cambios significativos en la forma de programar en Java.
Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico The Thinking in JlI\'ll AllllOla/ed SO/lIIiol/ CI/ide, disponible para
la venia en W'lI"'w.A1indViell.nel.
Concurrencia
Hasta este momento, hemos estado hablando de programacin secuencial. Todo lo que sucede
en un programa sucede paso a paso.
Una gran cantidad de problemas de programacin pueden resolverse utilizaodo programacin secuencial. Sin embargo, para
algunos problemas, resulta conveniente e incluso esencial ejecutar varias partes del programa en paralelo, de modo que
dichas partes parezcan estarse ejecutante concurrentemente o, si hay disponibles varios procesadores, se ejecuten realmen-
te de manera simultnea.
La programacin paralela puede mejorar enormemente la velocidad de ejecucin de los programas, proporcionar un mode-
lo ros sencillo para el diseo de ciertos tipos de programas, o ambas cosas a la vez. Sin embargo, llegar a familiarizarse
con la teora y las tcnicas de la programacin concurrente es algo si tuado a un nivel superior que las tcnicas de progra-
macin que hemos aprendido hasta ahora en el libro, y representa un tema de nivel intermedio o avanzado. Este captulo tan
slo puede proporcionar una introduccin al tema, y despus de estudiarlo ser mucho el camino que le quede para llegar a
ser un buen programador concurrente.
Como veremos, el problema real de la concurrencia es el que se presenta cuando una serie de tareas que se estn ejecutan-
do en paralelo comienzan a interferir entre s. Esto puede suceder de una maoera tan sutil y ocasional que probablemente
resulte bastante apropiado decir que la concurrencia es "tericamente determinista pero prcticamente no determinista". En
otras palabras, podemos demostrar que resulta posible escribir programas concurrentes que, con el suficiente cuidado y con
las necesarias inspecciones de cdigo, funcionen correctamente. Sin embargo, en la prctica, resulta mucho ms fcil escri-
bir programas conCWTentes que nicamente "parezcan" funcionar correctamente pero que, dadas las condiciones adecuadas,
fallarn. Es posible que estas condiciones nunca lleguen a darse o que se den de una manera tao infrecuente que jams apa-
rezcan los fallos durante las pruebas. De hecho, puede que no seamos capaces de escribir cdigo de pruebas que permita
generar las condiciones de fallo de nuestros programas concurrentes. Los fallos resultantes slo ocurrirn ocasionahnente,
y como resultado aparecern en forma de quejas de los clientes. ste es uno de los argumentos principales de estudiar el
tema de la concurrencia: si lo ignoramos, lo ms probable es que los problemas terminen por asaltamos.
La concurrencia parece estar, por tanto, rodeada de peligros, y si eso le hace sentirse un tanto atemorizado, mejor que mejor.
Aunque Java SE5 ha realizado mejoras significativas en lo que respecta a la concurrencia, siguen sin existir sistemas de pro-
teccin como la verificacin en tiempo de compilacin o las excepciones comprobadas, para nfonnamos de cundo hemos
cometido un error. Con la concurrencia, toda la responsabilidad recae sobre nosotros, y slo si somos suspicaces yagresi-
vos podremos escribir cdigo multihebra en Java que sea 10 suficientemente fiable.
Hay algunas personas que sugieren que la concurrencia es un tema demasiado avanzado como para incluirlo en un libro de
introduccin al lenguaje. Su argumento es que la concurrencia es un tema autnomo que puede tratarse independientemen-
te y que los pocos casos en los que la concurrencia aparece durante las tareas cotidianas de programacin (como por ejem-
plo, con las interfaces grficas de usuario) pueden tratarse sin necesidad de recurrir a estructuras especiales del lenguaje.
Por qu introducir un tema tao complejo si podemos evitarlo?
iOjal fuera as! Lamentablemente, la decisin de si nuestros programas Java utilizarn hebras no est en nuestras manos.
El slo hecho de que nosotros no creemos ninguna hebra no quiere decir que vayamos a ser capaces de evitar escribir cdi-
go basado en hebras. Por ejemplo, los sistemas web constituyen una de las aplicaciones Java ms comunes y la clase bsi-
ca de la biblioteca web, servlet, es inherentemente multihebra; esto resulta esencial porque los servidores web contienen a
menudo mltiples procesadores y la concurrencia es una forma ideal de emplear esos procesadores. Aunque un servlet puede
728 Piensa en Java
parecer muy simple, es necesario que entendamos los problemas de la concurrencia con el fin de utilizar los servlets apro-
piadamente. Lo mismo podramos decir de la programacin de las interfaces grficas de usuario, como veremos en el
Captulo 22, Interfaces grficas de usuario. Aunque las bibliotecas Swing y SWT disponen de mecani smos para la segnri-
dad de las hebras resulta dificil utilizar dichos mecanismos adecuadamente sin entender el tema de la concurrencia.
Java es un lenguaje multihebra y los problemas de concurrencia estn presentes, con independencia de que seamos cons-
cientes de su existencia. Como resultado, existen muchos programas Java que funcionan simplemente por accidente o que
funcionan la mayor parte de las veces y que fallan misteriosamente de vez en cuando debido a problemas de concurrencia
na localizados. En ocasiones, estos fallos son benignos, pero otras veces pueden representar la prclida de datos de gran
valor, y si no somos al menos conscientes de los problemas concurrencia, podemos tenninar asumiendo que el problema se
encuentra en algn otro lugar en vez de en nuestro software. Este tipo de problemas tambin pueden verse expuestos o
amplificados si se transfiere un programa a un sistema multiprocesador. Bsicamente, conocer el tema de la concurrencia
nos permite ser conscientes de que existe una posibilidad de que programas aparentemente correctos puedan exhibir un com-
portamiento incorrecto.
La programacin concurrente es como desembarcar en un nuevo mundo y aprender un nuevo lenguaje, o al menos IDl nuevo
conjunto de conceptos del lenguaje. Comprender la programacin concurrente tiene el mismo nivel de clificultad que com-
prender la programacin orientada a objetos. Si nos aplicamos, podemos llegar a entender el mecanismo bsico, pero gene-
ralmente es neesario un estudio profundo y un cierto nivel de prctica para llegar a dominar realmente la materia. El objetivo
de este captulo es proporcionar una panormica de los fundamentos bsicos de la concurrencia, para que se puedan enten-
der los conceptos y se puedan escribir programas multihebra de una complejidad razonable, pero tenga en cuenta que resul-
ta fcil confiarse demasiado. En cuanto comience a desarrollar soluciones de una cierta complejidad, necesitar estudiar
libros especficamente dedicados a esta materia.
Las mltiples caras de la concurrencia
Una de las razones principales por las que la programacin concurrente puede resultar confusa es que hay ms de un pro-
blema que resolver utilizando la concurrencia y ms de una tcnica para implementar la concurrencia, y no existe una clara
correspondencia entre estos dos aspectos (e incluso, a menudo, las lneas de separacin con completaruente difusas). Como
resultado, estamos obligados a tratar de entender todos los problemas y los casos especiales para poder emplear la concu-
rrencia de manera efectiva.
Los problemas que se resuelven mediante la concurrencia pueden clasificarse, de manera un tanto burda, en dos categoras:
"velocidad" y "gestionabilidad del diseo".
Ejecucin ms rpida
El tema de la velocidad parece simple a primera vista: si queremos que un programa se ejecute ms rpidamente, lo des-
componemos en fragmentos y ejecutamos cada uno de estos fragmentos en un procesador distinto. La concurrencia es una
herramienta fundamental para la programacin multiprocesador. Hoy da, como se est agotando la Ley de Moore (al menos
para los chips convencionales), las mejoras de velocidad se producen en la forma de procesadores multincleo en lugar de
mediante chips ms rpidos. Para hacer que los programas se ejecuten ms rpidamente es necesario aprender a aprovechar
dichos procesadores adicionales, y sa es una de las cosas que la concurrencia hace posible.
Si disponemos de una mquina multiprocesador se pueden distribuir mltiples tareas entre los distintos procesadores, lo que
permite incrementar enormemente el rendimiento. Esto es 10 que suele suceder con los potentes servidores web multiproce-
sador, que pueden clistribuir un gran nfunero de solicitudes de usuario entre las distintas CPU, dentro de un programa que
asigne una hebra a cada solicitud.
Sin embargo, la concurrencia puede tambin, a menudo, mejorar el rendimiento de programas que se estn ejecutando en
un nico procesador.
Esto puede parecer poco intuitivo. Si pensamos en ello, un programa concurrente que se est ejecutando en un nico proce-
sador debera tener un gasto de procesamiento administrativo mayor que si todas las partes del programa se ejecutaran
secuencialmente, debido al coste aadido del cambio de contexto (cambio de una tarea a otra). A primera vista, parece que
debera ser ms rpido ejecutar todas las partes del programa como una nica tarea, ahorrndose el coste asociado al cam-
bio de contexto.
21 Concurrencia 729
Lo que hace que la concurrencia pueda mejorar el rendimiento en estos casos es el bloqueo. Si una tarea del programa no
puede continuar con su procesamiento debido a alguna condicin que no est bajo control del programa (normalmente ope-
raciones de E/S), decimos que la tarea o la hebra se bloquea. Sin la concurrencia, todo el programa tendr que detenerse
ante esa condicin externa; sin embargo, si se ha escrito el programa utilizando concurrencia, las otras tareas del programa
pueden continuar ejecutndose cuando una tarea se bloquee, con lo que el programa continuar avanzado. Desde el punto
de vista del rendimiento, no tiene sentido utilizar la concurrencia en una mquina con un nico procesador, a menos que
alguna de las tareas pueda llegar a bloquearse.
Un ejemplo muy comn de mejora de rendimiento en los sistemas monoprocesador es la programacin dirigida por suce-
sos. De hecho, una de las razones ms imperiosas para utilizar la concurrencia es la de construir una interfaz de usuario con
una buena capacidad de respuesta. Pensemos en un programa que tenga que realizar algn tipo de operacin de larga dura-
cin y que termine, por tanto, ignorando la entrada del usuario, sin dar a ste ninguna respuesta. Si disponemos de un botn
para salir del programa, no queremos tener que consultar si ese botn se ha apretado en cada fragmento de cdigo que escri-
bamos. Si lo hiciramos, el cdigo tendra un aspecto terrible, y adems no existira ninguna garanta de que un programa-
dor no se olvidara de realizar esa comprobacin. Sin la concurrencia, la nica forma de tener tma interfaz grfi ca de usuario
con una buena respuesta es que todas las tareas comprueben peridicamente la entrada de usuario. Sin embargo, al crear una
hebra de ejecucin separada para responder a las entradas de usuario, incluso aunque esta hebra estar bloqueada la mayor
parte del tiempo, el programa permitir garantizar un cierto nivel de respuesta.
El programa necesita continuar realizando sus operaciones, y al mismo tiempo necesita tambin devolver el control a la
interfaz de usuario para poder responder a ste. Pero un mtodo convencional no puede continuar llevando a cabo sus ope-
raciones y al mismo tiempo devolver el control al resto del programa. De hecho, parece que esto fuera imposible, como si
estuviramos exigiendo a la CPU que estuviera en dos sitios a la vez; pero, sta es, la ilusin que la concurrencia permite
(en el caso de los sistemas multiprocesador se trata de algo ms que una ilusin).
Una forma muy sencilla de implementar la concurrencia es en el nivel del sistema operativo, utilizando procesos. Un pro-
ceso es un programa auto-contenido que se ejecuta en su propio espacio de direcciones. Un sistema operativo multitarea
puede ejecutar ms de un proceso (programa) simultneamente, conmutando peridicamente la CPU de un proceso a otro,
al mismo tiempo que parece que cada proceso estuviera ejecutndose por separado. Los procesos resultan muy atractivos,
porque el sistema operativo se encarga normalmente de aislar un proceso de otro de modo que no puedan interferir entre s,
lo que hace que la programacin basada en procesos sea relativamente sencilla. Por contraste, los sistemas concurrentes,
como el que se utiliza en Java, comparten recursos tales como la memoria y la E/S, por lo que la dificultad fundamental a
la hora de escribir programas multihebra es la de coordinar el uso de estos recursos entre distintas tareas dirigidas por hebras,
de modo que no haya ms de una tarea en cada momento que pueda acceder a un determinado recurso.
He aqu un ejemplo simple donde se utilizan procesos del sistema operativo: mientras yo escriba este libro, sola hacer ml-
tiples copias de seguridad redundantes del estado actual del libro. Haca una copia en un directorio local, otra en un dispo-
sitivo USB, otra en un disco Zip y otra en un sitio FTP remoto. Para automatizar este proceso, escrib un pequeo programa,
(en Python, pero los conceptos son los mismos) que comprime el libro en un archivo, incluyendo un nmero de versin en
el nombre y luego realizaba las copias. Inicialmente, realizaba todas las copias secuencialmente, esperando a que cada una
se completara antes de dar comienzo a la siguiente. Pero entonces me di cuenta de que cada operacin de copia req1?-era una
cantidad de tiempo distinta, dependiendo de la velocidad de E/S del medio. Puesto que estaba utilizando un sistema opera-
tivo multitarea, poda iniciar cada operacin de copia como UD proceso separado y dejarlas ejecutarse en paralelo, 10 que
aceleraba la ejecucin completa del programa. Mientras que uno de los procesos estaba bloqueado otro poda continuar con
su tarea.
ste es un ejemplo ideal de concurrencia. Cada tarea se ejecuta como un proceso en su propio espacio de direcciones, as
que no existe la posibilidad de interferencias entre las distintas tareas. Lo ms imponante es que no hay ninguna necesidad
de que las tareas se comuniquen entre s, porque todas ellas son completamente independientes. El sistema operativo se
encarga de todos los detalles necesarios para garanti zar que todos los archivos se copien apropiadamente. Como resultado,
no existe ningn riesgo y lo que obtenemos es un programa ms rpido sin ningn coste adicional.
Algunos autores llevan incluso a defender que los procesos son la nica solucin razonable para la concurrencia,l pero
lamentablemente existen, por regla general, limitaciones relativas al nmero de procesos y al gasto administrativo adicional
asociado con cada uno que impiden que esa solucin basada en procesos pueda aplicarse a todo el conjunto de problemas
de concurrencia.
1 Eric Raymond, por ejemplo, hace un encendida defensa de esta idea en rile Arf o/ UNIX Programming (Addison-Wesley, 2004).
730 Piensa en Java
Algunos lenguajes de programacin estn diseados para aislar las tareas concurrentes unas de otras. Estos programas se
denominan, generalmente, lenguajes funcionales, y en ellos cada llamada a funcin no produce ningn efecto secundario
(no pudiendo as interferir con otras funciones) y se la pueda ejecutar como una tarea independiente. Erlang es uno de dichos
lenguajes e incluye mecanismos seguros para que una tarea se comunique con otra. Si nos encontramos con que una parte
de nuestro programa tiene que hacer un uso intensivo de la concurrencia y tropezamos con demasiados problemas a la hora
de desarrollar esa parte, podemos considerar como posible solucin el escribir esa parte del programa en un lenguaje con-
currente dedicado, como Erlang.
Java adopt la solucin ms tradicional que consiste en aadir el soporte para hebras por encima de un lenguaje secuencial.
2
En lugar de iniciar procesos externos en un sistema operativo multitarea, el mecanismo de hebras crea las distintas tareas
dentro de un nico proceso, representado por el programa que se est ejecutando. Una de las ventajas que esta solucin pro-
porciona es la transparencia con respecto al sistema operativo, que era uno de los principales objetivos de dise.o en Java.
Por ejemplo, las versiones pre-OSX del sistema operativo Macintosh (que era objetivo relativamente importante par. l.s
primeras versiones de Java) no soportaba la multitarea. Si no se hubiera a.dido el mecanismo multihebra a Java, los pro-
gramas Java concurrentes no habran podido portarse a Macintosh ni a otras platafonnas similares, incurriendo as en el
requisito de que los programas deberan "escribirse una vez y ejecutarse en todas partes".
3
Mejora del diseo del cdigo
Un programa que use mltiples tareas en una mquina de un nico procesador seguir haciendo una nica cosa cada vez,
por lo que debera ser tericamente posible escribir el mismo programa sin utilizar tareas. Sin embargo, la concurrencia pro-
porciona un beneficio organizativo de gran importancia: el diseo del programa puede simplificarse enormemente. Algunos
tipos de problemas, como la simulacin, son difciles de resolver si no se incorpora el soporte para la concurrencia.
La mayona de las personas han tenido la oportunidad de ver algn tipo de simulacin u otro, bien en forma de juego infor-
mtico O bien como animaciones generadas por computadora en alguna pelcula. Generalmente, las simulaciones involucran
muchos elementos que interactan entre s, cada uno de los cuales tiene "su propio cerebro". Aunque es cierto que, en una
mquina de un solo procesador, cada elemento de simulacin est siendo controlado por ese nico procesador, desde el
punto de vista de la programacin resulta mucho ms fcil actuar como si cada elemento de simulacin tuviera su propio
procesador y fuera una tarea independiente.
Una simulacin de gran envergadura puede incluir tm gran nmero de tareas, lo que se corresponde con el hecho de que
cada elemento de una simulacin puede actuar de manera independiente; esto incluye no slo los elfos y los brujos sino tam-
bin las puertas o las piedras. Los sistemas multihebra tienen a menudo un lmite relativamente pequeo en cuanto al nme-
ro de hebras disponibles, estando dicho lmite, en ocasiones, en el borde de las decenas o los centenares. Este nmero puede
variar fuera del control del programa: puede depender de la plataforma, o en el caso de Java, de la versin de la mquina
JVM. En Java, podemos asumir, por regla general, que no existirn suficientes hebras disponibles como para asignar una a
cada elemento de una simulacin de gran envergadura.
Una tcnica tpica para resolver este problema consiste en utilizar lo que se denomina multihebra cooperativa. El mecanis-
mo de hebras de Java es con desalojo, lo que significa que hay un mecanismo de planificacin que proporciona franj as tem-
porales para cada hebra, interrumpiendo peridicamente a una hebra y efectuando un cambio de contexto a otra hebra, de
modo que a cada una se le asigne una cantidad de tiempo razonable como para poder realizar la tarea que tenga asignada.
En un sistema cooperativo, cada tarea cede el control voluntariamente, lo que requiere que el programador inserte a prop-
sito algn tipo de instruccin de cesin de control dentro de cada tarea. La ventaja de un sistema cooperativo es doble: el
cambio de contexto es mucho menos costoso que, normalmente, en un sistema con desalojo, y adems no existe ningn lmi-
te terico al nmero de tareas independientes que pueden ejecutarse simultneamente. Cuando estamos tratando con un gran
nmero de elementos de simulacin, sta puede ser la solucin ideaL Observe, sin embargo, que algunos sistemas coopera-
tivos no estn diseados para distribuir las tareas entre los distintos procesadores, 10 que puede resultar muy restrictivo.
En el otro extremo, la concurrencia representa un modelo muy til (porque refleja muy bien lo que sucede) cuando estarnos
trabajando con los modernos sistemas de mensajera, que involucran a muchas computadoras independientes distribuidas a
2 Se podra argumentar que tratar de agregar la concurrencia a lUl lenguaje secuencial es un enfoque condenado al fracaso, pero que cada cual saque sus
propias conclusiones.
3 Este requisito nunca ha llegado a satisfacerse del todo y Sun ya no pone tanto nfasis en l. Irnicamente, una de las razones de que este requisito no lle-
gara a satisfacerse puede ser, precisamente, los problemas relativos al sistema de hebras, y puede que se hayan solventado en Java SES.
21 Concurrencia 731
lo largo de una red. En este caso, todos los procesos se ejecutan de forma completamente independiente unos de otros y no
existe ni siquiera la posibilidad de compartir recursos. Sin embargo, seguimos teniendo que sincronizar la transparencia de
informacin entre los distintos procesos, de modo que el sistema de mensajera, entendido como un todo, no pierda infor-
macin ni introduzca infonnacin en los instantes incorrectos. Incluso si no pretende utilizar la concurrencia a menudo en
el futuro imnediato, resulta muy til entender los conceptos implicados para poder comprender las arquitecturas de mensa-
jeria, que se estn convirtiendo en la forma predominante de crear sistemas distribuidos.
La concurrencia tiene sus costes asociados, incluyendo la complejidad inherente a este tipo soluciones, pero estos costes son
ms que compensados por las mejoras en el diseo del programa, por el equilibrado de recursos y por la mayor comodidad
de los usuarios. En general, las hebras nos permiten crear un diseo con un acoplamiento ms dbil; si no fuera por ellas,
determinadas partes de nuestro cdigo se veran obligadas a prestar una atencin explcita a determinadas actividades de
cuya gestin se encargan normalmente las hebras.
Conceptos bsicos sobre hebras
La programacin concurrente permite particionar un programa en una serie de tareas separadas y que se ejecutan de forma
independiente. Usando un mecanismo multihebra, cada una de estas tareas independientes (tambin denominadas subta-
reas) se asigna a una hebra de ejecucin. Una hebra es un nico flujo de control secuencial dentro de un proceso. Un nico
proceso puede, por tanto, tener mltiples tareas que se ejecuten concurrentemente, pero a la hora de programar actuamos
como si cada tarea dispusiera del procesador para ella sola. Un mecanismo subyacente se encarga de dividir el tiempo de
procesador de manera transparente, sin que en general tengamos que prestar atencin a este mecanismo.
El modelo de hebras es una utilidad de programacin que simplifica la tarea de realizar varias operaciones al mismo tiem-
po dentro de un mismo programa: el procesador ir saltando de una tarea a otra, asignando a cada una parte de su tiempo.4
Cada tarea piensa que tiene asignado el procesador de manera continua, pero lo cierto es que el tiempo del procesador se
distribuye entre todas las tareas (excepto cuando el programa est de hecho ejecutndose sobre mltiples procesadores l. Una
de las mayores ventajas del mecanismo de hebras es que el programador puede abstraerse completamente de este nivel, de
modo que el cdigo no necesita saber si est ejecutndose sobre un nico procesador o sobre varios. De esta manera, la uti-
lizacin de hebras constituye una forma de crear programas que sean transparentemente escalables: si un programa se est
ejecutando de forma demasiado lenta, podemos acelerarlo fcilmente aadiendo ms procesadores a la computadora. Los
mecanismos multitarea y multihebra tienden a ser las formas ms razonables de utilizar los sistemas multiprocesador.
Definicin de las tareas
Una hebra se encarga de dirigir una cierta tarea, por lo que necesitamos una forma de describir dicha tarea. Para ello, se
emplea la interfaz Runnable. Para definir una tarea, basta con implementar Runnable y escribir un mtodo run( l para
hacer que la tarea realice su correspondiente trabajo.
Por ejemplo, la siguiente tarea LiftOff se encarga de mostrar una cuenta atrs antes de un lanzamiento:
11: concurrency/LiftOff.java
11 Ilustracin de la interfaz Runnable.
public class LiftOff implements Runnable
protected int countDown = la; 11 Predeterminado
private static int taskCount = O;
private final int id = taskCount++;
public LiftOff () {}
public LiftOff(int countDown)
this.countDown = countDoWTI;
public String status () {
return 11#11 + id + 11 (11 +
4 Esto es cierto cuando el sistema utiliza un mecanismo de franjas temporales (por ejemplo, Windows). Solaris utiliza un modelo de concurrencia basado
en \Ula cola FIFO; a menos que se despierte \Ula hebra de mayor prioridad, la hebra actual continuar ejecutndose hasta que se bloquee o termine. Eso
significa que otras hebras con la misma prioridad no podrn ejecutarse hasta que la hebra actual ceda el control de procesador.
732 Piensa en Java
(countDown > o ? countDown
publi c void run ()
while (countDown- - > O) {
System. out . pri nt (status () ) ;
Thr ead. yield() ;
I1Liftoff! 10) + " ) , 11 i
El identificador id distingue entre mltiples instancias de la tarea. Es de tipo final porque no se espera que cambie una vez
que ha sido inicializado.
El mtodo run( ) de una tarea suele tener algn tipo de bucle que contina ejecutndose hasta que la tarea deja de ser nece-
saria, por lo que es preciso establecer la condicin de salida de este bucle (una opcin consiste simplemente en ejecutar una
instruccin return desde run( )). A menudo, run() se implementa en la forma de un bucle infmito, lo que quiere decir que
si no aparece un factor que haga que run() termine, este mtodo continuar ejecutndose para siempre (posteriormente en
el captulo veremos cmo terminar las tareas de manera segura).
La llamada al mtodo esttico Thread.yield( ) dentro de run( ) es una sugerencia para el planificador de hebras (la parte
del mecanismo de hebras de Java que conmuta el procesador de una hebra a la siguiente). Dicha sugerencia dice: "Acabo
de finalizar las partes importantes de mi ciclo y este sera un buen momento para conmutar a otra tarea durante un rato". Es
completamente opcional, pero utilizamos dicho mtodo aqu porque tiende a producir una salida ms interesante en estos
tipos de ejemplo: tenemos ms probabilidades de ver cmo se cambia de unas tareas a otras.
En el siguiente ejemplo, el mtodo run( ) de la tarea no est dirigido por una hebra separada, sino que simplemente se le
invoca desde main() (en realidad, s que estamos usando una hebra: la que siempre se asigua a maine )):
JI: concurrencyf MainThread .java
public c lass MainThread {
public static voi d mai n( String [] args )
LiftOff launch = new LiftOff {) i
launch .run( ) i
j * OUtpu t ,
#0(9), #0(8), #0(7), #0(6) , #0 (5) , #0 (4) , #0(3 ) , #0(2 ), #0(1), #O(Liftof fl),
*j jj,-
Cuando derivamos una clase de Runnable, debe tener un mtodo run( ), pero esto no tiene nada de especial: no produce
ninguna capacidad innata de gestin de hebras. Para conseguir disponer del mecanismo de hebras tenemos que asociar expl-
citamente una tarea a una hebra.
La clase Thread
La forma tradicional de transfonnar un objeto Runnable en una tarea funcional consiste en entregrselo a un constructor
Thread (hebra). Este ejemplo muestra cmo asignar una hebra a un objeto LiftOff utilizando un objeto Thread:
ji : concurrencyj BasicThreads.java
/1 El uso ms bsico de la clase Thread .
public cIass BasicThreads {
publi c s tati c void main (String [J args) {
Thread t = new Thread( new Li ftOf f () )
t ~ t a r t ();
Syst em.out .println ("Waiting f or LiftOff") i
/ * Out put: ( 90% match)
Wait i ng for LiftOff
#0 (9), #0(8), #0(7), # 0(6) , #0 (5) , #0 (4 ) , #0(3 ), #0(2) , #0(1), #O(Liftoffl),
*jjj,-
21 Concurrencia 733
Un constructor Thread s610 necesita un objeto Runnable. Al invocar el mtodo start( ) de un objeto Thread se realizar
la inicializacin necesaria para la hebra y a continuacin se invocar el mtodo run( ) del objeto Runnable para iniciar la
tarea dentro de la nueva hebra.
An cuando start( ) parezca estar baciendo una llamada a un mtodo de larga duracin, podemos ver a la salida (el mensa-
je "Waiting for LiftOff' aparece antes de completarse la cuenta atrs) que start() vuelve rpidamente. En la prctica, hemos
hecho una llamada al mtodo LiftOff.run(), y dicho mtodo no ha finalizado todava, pero como LiftOff.run() est sien-
do ejecutado por una hebra distinta, podemos continuar realizando otras operaciones en la hebra main() (esta capacidad no
est restringida a la hebra main(): cualquier hebra puede iniciar otra hebra). As, el programa est ejecutando dos mtodos
a la vez: main( ) y LiftOff.run( ). El mtodo run( ) es el cdigo que se ejecuta "simultneamente" con las otras hebras del
programa.
Podemos aadir fcilmente ms hebras para controlar ms tareas. A continuacin podemos ver cmo todas las tareas se eje-
cutan de manera concertada:
5
1/: concurrencyjMoreBasicThreads . java
f/ Adicin de ms hebras.
public c l ass MoreBasicThreads
public s tat ic voi d main (St ring [] args )
for (int i "" O; i < Si i ++ }
new Thread(new LiftOff ()} .start(}
System.out.println(IIWaiting for LiftOff!1 ) i
/* Output, (Sample )
Waiting for LiftOff
#0 ( 9 ), #1 (9) , #2(9 ) . #3 ( 9), #4 (9 ) . #0(8), #1(8 ) . #2 ( 8),
#3(8), #4 (8 ) , #0 (7 ), #1 (7), #2 ( 7), #3(7 ), #4(7 ) , # 0( 6),
#1 ( 6 ) , #2(6 ) , #3 ( 6) , #4 ( 6), #0(5), #1 (5), #2(5), #3 ( 5 ) ,
#4(5 ) , #0(4 ) , # 1( 4), #2(4 ) , # 3(4). #4(4 ) , #0(3 ), #1 (3 ) ,
#2(3) , #3 ( 3), #4 (3), #0(2), #1(2 ) , #2(2), #3 (2), # 4(2) ,
#0 (1), #1 (1), #2(1), #3 (1), #4(1), #0 (Liftoff ! ),
#l(Liftoff!), # 2ILiftoff!), #3(Liftoff!), #4( Liftoff !),
*///,-
La salida muestra que la ejecucin de las diferentes tareas est entremezclada a medida que se conmuta de una tarea a otra.
El planificador de hebras controla esta conmutacin de forma automtica. Si tenemos mltiples procesadores en la mqui-
na, el procesador de hebras distribuir de manera transparente las hebras entre los distintos procesadores.
6
La salida de este programa ser diferente en cada ejecucin, porque el mecanismo de planificacin de hebras no es deter-
minstico. De hecho, podemos ver enormes diferencias en la salida de este programa en una versin del JDK y en la siguien-
te. Por ejemplo, una versin anterior del JDK no efectuaba demasiado a menudo la conmutacin de hebras, por lo que la
hebra 1 poda recorrer todas las pasadas del bucle hasta terminar, luego la hebra 2 completara todas las pasadas de su bucle,
etc. En la prctica, esto equivala a llamar a una rutina que realizara todos los bucles de manera consecutiva, salvo porque
el iniciar todas esas hebras resulta bastante ms costoso. Las versiones anteriores del JDK parecen exhibir un mejor
comportamiento de asignacin de franjas temporales, con lo que cada hebra parece recibir un servicio ms regular.
Generalmente, estos tipos de cambios de comportamiento en el JDK no son mencionados por Sun, as que no podemos basar
nuestros planes en ninguna previsin coherente relativa al comportamiento del mecanismo de hebras. La mejor solucin
consiste en ser lo ms conservador posible a la hora de escribir cdigo basado en hebras.
Cuando main() crea los objetos Thread, no est capturando las referencias de ninguno de ellos. Con un objeto normal, esto
hara que el objeto fuera candidato para la depuracin de memoria, pero eso no sucede as con los objetos Thread. Cada
objeto Thread "se registra" a s mismo, por lo que existe de hecho una referencia a ese objeto en algn lugar, y el depura-
dor de memoria no puede borrar el objeto hasta que la tarea salga de su mtodo run( ) y termine. Podemos ver, analizando
, En este caso, una nica hebra (main()) est creando todas las hebras LiftOff. Sin embargo, si tenemos mltiples hebras creando hebras LUtorr es posi-
ble que ms de una hebra LIftOff tenga el mismo valor id. Posteriormente en el captulo veremos cul es la razn.
6 Esto no era as en algunas de las versiones anteriores de Java.
734 Piensa en Java
la salida, que todas las tareas se ejecutan efectivamente hasta su conclusin, por lo que una hebra crea una hebra de ejecu-
cin separada que persiste despus de que se complete la llamada a start( ).
EjercIcio 1: (2) Implemente una clase Runnable. Dentro de run( ), imprima un mensaje y luego invoque yield( ).
Repita este proceso tres veces, y luego vuelva desde run( ). Ponga un mensaje de inicio en el constructor
y un mensaje de tennLnacin cuando la tarea tennine. Cree varias de estas tareas y contrlelas utilizando
hebras.
Ejercicio 2: (2) Siguiendo la fonua de generies/Fibonaeci.java, cree una tarea que genere una secuencia de n nme-
ros de Fibonacci, donde n sea un parmetro proporcionado al constructor de la tarea. Cree varias de estas
tareas y contrlelas mediante hebras.
Utilizacin de Executor
Los Ejecutores java.util.eolleurrent de Java SE5 simplifican la programacin concurrente, encargndose de gestionar los
objeto Thread por nosotros. Los ejecutores proporcionan un nivel de indireccin entre un cliente y la ejecucin de tIDa tarea,
en lugar de ejecutar una tarea directamente, hay un objeto intermedio que se encarga de ejecutar la tarea. Los ejecutores per-
miten gestionar la ejecucin de tareas asncronas sin tener que gestionar de manera explcita el ciclo de vida de las hebras.
Los ejecutores son el mtodo preferido de inicio de tareas en Java SE5/6.
Podemos utilizar un ejecutor (Exeeutor) en lugar de crear explcitamente objetos Tbread en MoreBasicThreads.java. Un
objeto LiftOff sabe cmo ejecutar una tarea especfica; al igual que el patrn de diseo Comando, expone un nico mto-
do para ser ejecutado. Un objeto ExecutorService (un objeto Exeeulor con un ciclo de vida de servicio, por ejemplo, apa-
gar) sabe cmo construir el contexto apropiado para ejecutar objetos Runnable. En el siguiente ejemplo, el objeto
CaebedTbreadPooi crea una hebra por cada tarea. Observe que se crea un objeto ExeeutorServlcc utilizando un mtodo
Exeeutors esttico que detennina el tipo de objeto Exeeutor que tiene que ser:
//: concurrencyfCa chedThreadPool.java
import java.util .concurrent.*
publ i c c lass CachedThreadPool {
public static void main (String[] args ) {
ExecutorService exec : Executors.newCachedThreadPool{);
for( int i = Oi i < 5; i++}
exec.execute(new LiftOff ()}
exec.shutdown() i
1* Outpu t, ( Sample)
#0 (9), #0 (8) , #1 (9) , #2 (9 ) , #3 (9) , # 4 (9 ) , #0 (7), #1 (8) ,
#2 (8), #3 (8) , #4 (8) , #0 (6) , #1 (7) , #2 (7), #3 (7), #4 (7) ,
#0 (5) , #1 (6) , # 2 (6), #3 (6) , #4 (6), #0 (4) , #1 (5), #2 (5) ,
#3 (5 ) , #4 (5), #0 (3), #1 ( 4 ) , #2 (4 ) , #3 ( 4 ) , #4(4 ), #0 (2 ),
#1 (3) , #2 ( 3), #3 (3) , #4 (3 ), #0 (1) , # 1 (2) , #2 ( 2), #3 ( 2).
#4 (2) , #0 ( Liftoff 1) , #1( 1 ) , #2 ( 1) , #3 ( 1 ) , #4 (1 ) ,
#1 (Liftoff 1) , #2 (Liftoffl) , #3 (Liftoffl) , #4 (Liftof fl) ,
*111,-
A menudo, puede utilizarse un nico Exeeulor para crear y gestionar todas las tareas del sistema.
La llamada a sbutdown( ) impide que se enven nuevas tareas a ese objeto Exeeutor. La hebra actual (en este caso, la que
controla main( continuar ejecutando todas las tareas enviadas antes de shuldown(). El programa finalizar en cuanto
fmalice todas las tareas del objeto Exceutor.
Podemos sustituir fcilmente el objeto CaebedThreadPool del ejemplo anterior por un tipo diferente de Exeeutor. Un obje-
to FixedTbreadPool utiliza un conjunto limitado de hebras para ejecutar las tareas indicadas:
1/: concurrency/FixedThreadPool .java
import java.util.concurrent.*
public c lass FixedThreadPool {
public static void main(String [] a rgs ) {
JI El argumento del constructor es e l nmero de tareas:
ExecutorService exec = Executors.newFixedThreadPool (5);
for (int i = O; i < Si i++}
exec.execute(new LiftOff());
exec.shutdown() i
} /* Output, (Samp1e)
#0 (9) , #0 (8) , #1 (9) , #2 (9) , #3 (9) ,
#2 (8) , #3 (8) , #4 (8) f #0 (6) , #1 (7) ,
# 0 (5) , #1 (6), #2 (6) , #3 (6) , #4 (6),
#3 (5) , #4 (5), #0 (3 ) , #1 (4), #2 (4),
#1 ( 3), #2(3 ) , #3 (3 ) , #4 (3), #0 (1 ) ,
#4 (2 ), #0 ILif t off ! ) , #1 ( 1 ) , #2 ( 1),
#4 (9) ,
#2 (7) ,
#0 (4 ) ,
#3 (4 ) ,
#1 (2 ) ,
#3 ( 1) ,
#1 ILifto ff! ) , #2 ( Lif toff!) , #3 (Liftoff! ) ,
* /// ,-
#0 (7), #1 (8),
# 3 (7), #4 (7),
#1 (5), #2 (5) ,
#4 ( 4), #0 (2),
#2 (2 ) , #3 (2 ) ,
#4 (1) ,
#4 (Liftoff! ) ,
21 Concurrencia 735
Con FixedTbreadPool, realizamos la costosa asignacin de hebras una nica vez, por adelantado, con lo que limitamos el
nmero de hebras. Esto pennite ahorrar tiempo, porque no tenemos que pagar constantemente el coste asociado a la crea-
cin de una hebra para cada tarea individual. Asimismo, en un sistema dirigido por sucesos, las rutinas de tratamiento de
sucesos que requieren hebras pueden ser servidas con la rapidez que queramos, extrayendo simplemente hebras de ese con-
junto preasignado. Con esta solucin, no podemos agotar los recursos disponibles porque el objeto FixcdThreadPool utili-
za un nmero limitado de objetos Thread.
Observe que en cualquiera de los dos tipos de conjuntos de hebras que hemos examinado, las hebras existentes se reutilizan
de manera automtica siempre que sea posible.
Aunque en este libro utilizaremos conjuntos de hebras de tipo CachedThreadPool, tambin puede considerar utilizar
FixedThreadPool en el cdigo de produccin. CachedTbre.dPool crear generalmente tantas hebras como necesite duran-
te la ejecucin de un programa y luego dejar de crear nuevas hebras a medida que vaya pudiendo reciclar las antignas, por
lo que resulta razonable elegir en primer lugar este tipo de objeto Executor. Slo si esta tcnica causa problemas necesita-
remos cambiar a un conjunto de hebras de tipo FixedTbrcadPool.
Un ejecutor SingleThreadExecntor es como FlxedThreadPool pero con un tamao de ma nica hebra
7
Esto resulta til
para cualquier cosa que queramos ejecutar de manera continua en otra hebra (una tarea de larga duracin), como por ejem-
plo una tarea que se dedique a escnchar a las conexiones socket entrantes. Tambin es til para tareas de corta duracin que
queramos ejecutar dentro de una hebra, por ejemplo, un registro de sucesos local o remoto, o tambin para una hebra que
se emplee para despachar sucesos.
Si se enva ms de una tarea a un ejecutor SingleThreadExecutor, las tareas se pondrn en cola y cada una de ellas se eje-
cutar hasta completarse antes de qne se inicie la signiente tarea, utilizando todas ellas la misma hebra. En el siguiente ejem-
plo, podemos ver cmo cada tarea se completa en el orden en que fue enviada antes de que d comienzo la siguiente. As,
un ejecutor SingleTbreadExecutor serializa las tareas que se le envan y mantiene su propia cola (oculta) de tareas pen-
dientes.
/ 1: concurrency/SingleThreadExecutor.java
i mport java.util.concurrent.*;
public class SingleThreadExecutor
public static void main (String {] args l {
ExecutorService exec =
Executors.newSingleThreadExecutor() ;
for(int i = O; i < 5; i ++}
exec.execute(new LiftOff( i
exec . shutdown () ;
1* Output:
7 Tambin ofrece una importante garanta de concurrencia que los otros tipos de ejecutores no ofrecen: no se pueden invocar conClUTcntemente dos tareas.
Esto hace que cambien los requisitos de bloqueo de las tareas (hablaremos sobre el bloqueo ms adelante en el captulo).
736
Piensa en Java
#0 (9) , #0 (8) , #0 (7) , #0 (6) , #0 ( 5) , #0 (4), #0 (3) , #0 (2) ,
#0 (1 ) , #0 (Liftoff !) , #1 (9) , #1 (8), #1 (7), #1 (6) , #1 (5),
#1 (4), #1 (3) , #1 ( 2) , #1 (1), #1 (Lif toff! ) , #2 ( 9) , #2 (8) ,
#2 (7) , #2 (6) , #2 (5) , #2 (4) , #2 (3) , #2 (2), #2 (1) ,
#2 (Lifto ff! ) , #3 (9) , #3 (8) , #3 (7), #3 (6), #3 ( 5 ), #3 (4),
# 3 (3) , #3 (2) , #3 (1), #3( Liftoff!) I #4 (9), #4 ( 8), #4 (7) ,
#4 (6) , #4 (5) , #4 (4) , #4 (3) , #4 (2 ), #4 (1), #4 (Liftoff ! ) ,
*///,-
Veamos otro ejemplo. Suponga que tenemos una serie de hebras que estn controlando tareas que utilizan el sistema de
archivos. Podemos ejecutar estas tareas con SingleThreadExecutor para garantizar que en cada momento slo haya una
tarea ejecutndose en cualquier hebra. De esta forma, no tenemos que preocuparnos de la sincronizacin en lo que respec-
ta al recurso compartido (y adems no colapsaremos el sistema de archivos). En ocasiones, una mejor solucin consiste en
sincronizarse con el recurso (de lo que hablaremos ms adelante en el captulo). Pero SingleThreadExecutor nos permite
obviar los problemas de coordinacin a la hora, por ejemplo, de construir el prototipo de un sistema. Serial izando las ta-
reas, podemos eliminar la necesidad de serializar los objetos.
Ejercicio 3:
Ejercicio 4:
(1) Repita el Ejercicio 1 utilizando los diferentes tipos de ejecutores mostrados en esta seccin.
(1) Repita el Ejercicio 2 utilizando los diferentes tipos de ejecutores mostrados en esta seccin.
Produccin de valores de retorno de las tareas
Un objeto Runnable es una tarea independiente que realiza un cierto trabajo, pero que no devuelve un valor. Si queremos
que la tarea devuelva un valor cuando finalice, podemos implementar la interfaz Callable en lugar de la interfaz Run\1able.
Callable, introducida en Java SES, es un genrico con un parmetro de tipo que represeota el valor de retorno del mtodo
call( ) (en lugar de run( , y debe invocarse utilizando un mtodo submit( ) de ExecutorService. He aqu un ejemplo sim-
ple:
JI:
import java.util.concurrent.*;
import java.util.*;
class TaskWi thResu lt i mplements Callable<String>
private int id
public TaskWithResult(int i d)
this.id = id;
public String ca11 {)
return "result of TaskWithResul t " + id;
public class CallableDemo {
public static voi d main(String(] argsl {
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String results =
new ArrayList<Future<String ();
for(int i = O; i < 10; i ++}
results.add (exec.submit (new TaskWithResul t(i ) j
for (Future<String> fs : results)
try (
II get() se bloquea hast a compl etarse:
System.out.println(fs.get(
catch(InterruptedException e) {
System.out.printl n(e) ;
returnj
catch (ExecutionException e) {
System.out.printl n(e) ;
finally {
21 Concurrencia 737
exec.shutdown() i
1*
Output:
result of TaskWi thResul t o
result of TaskWithResult 1
result of TaskWi thResul t 2
result of TaskWit hResult 3
result of TaskWithResult 4
result of TaskWithResult 5
result of TaskWithResult 6
result of TaskWithResult 7
result of TaskWithResult 8
resul t of TaskWithResu l t 9
*111 ,-
El mtodo submit( ) produce un objeto Future, parametrizado para el tipo particular de resultado devuelto por el objeto
Callable. Podemos consultar el objeto Future con isDone( ) para ver si se ha completado. Cuando la tarea se ha completa-
do y dispone de un resultado, podemos invocar get( ) para extraer ste. Tambin podemos simplemente invocar get( ) sin
comprobar isDone( ), en cuyo caso get( ) se bloquear hasta que el resultado est listo. Podemos asimismo invocar get( )
con una temporizacin, o invocar isDone( ) para ver si la tarea se ha completado, antes de tratar de llamar a get( ) para
extraer el resultado.
El mtodo sobrecargado Executors.callable( ) toma un objeto Runnable y produce un objeto Callable. ExecutorService
tiene ~ l u n o s mtodos de "invocacin" que ejecutan colecciones de objetos CaUable.
Ejercicio 5: (2) Modifique el Ejercicio 2 de modo que la tarea sea un objeto Callable que sume los valores de todos
los nmero de Fibonacci. Cree varias tareas y muestre los resultados.
Cmo dormir una tarea
Una forma simple de modificar el comportamiento de las tareas consiste en invocar sleep( ) para detener (bloquear) la eje-
cucin de dicha tarea durante un cierto tiempo. En la clase LiftOff, si sustituimos la llamada a yield( ) por una llamada a
sleep( ), obtenemos lo signiente:
JI: concurrencyjSleepi ngTask.java
// Llamada a sleep() para detenerse durante un tiempo.
import java.util. concurrent.*
public class SleepingTask extends LiftOff {
public void run() {
try (
while(count Down-- > O) {
System.out.prin t(status(
II A la antigua usanza:
II Thread.sl eep(lOO);
II Al estilo Java SEs /6 :
TimeUni t.MILLISECONDS. sleep(lOO) i
catch(InterruptedException el {
System.err.println(IlInterrupted
rl
) ;
public static void main (Stri ng[ } args) {
Executorservice exec ~ Executors.newCachedThreadPool()
for(int i = O i < Si i++)
exec. execute(new SleepingTask()) i
exec.shutdown ()
1* Output :
738 Piensa en Java
# 0 (9) , # 1 (9), #2 (9) , #3 (9) , #4 (9) , #0 (8) , #1 (8) , #2 (8) ,
# 3 ( 8) , #4 (8) , # 0 (7) , #1 (7) , #2 (7) , #3(7) , #4 (7 ) , #0 ( 6) ,
#1 ( 6 ) , #2 (6 ) , #3 ( 6) , # 4 ( 6), #0 (5 ) , #1 (5 ) , # 2 ( 5 ) , #3 (5 ) ,
#4 (5), # 0 (4) , #1 (4), #2 (4), #3 (4), #4 (4) , #0 (3 ) , #1 (3) ,
# 2 (3) , # 3 (3) , #4 (3) , #0 (2) , #1 (2), #2 (2) , #3 (2), #4 ( 2),
# 0 (1 ) , #1 (1 ) , # 2 (1) , # 3 ( 1 ), #4 (1 ) , # 0 (Lif toff! ) ,
#1 (Liftof f!) , #2 (Liftoff! ) , #3 (Liftoff!) , #4 (Liftoff! ) ,
* // /,-
La llamada a sleep( ) puede generar una excepcin InterruptedException, y como podemos ver, esta excepcin se atrapa
en run( ). Puesto que las excepciones no se propagan entre unas hebras y otras para volver a maine ), es necesario gestio-
nar de manera local dentro de cada tarea todas las excepciones que puedan generarse.
Java SES ha introducido la versin ms explicita de sleep() como parte de la clase TimeUnit, como se muestra en el ejem-
plo anterior. Esto proporciona una mayor legibilidad, al permitimos especificar las unidades del retardo asociado con
sleep(). TimeUnit tambin puede usarse para realizar conversiones, como veremos ms adelante en el captulo.
Dependiendo de la plataforma, podramos observar que las tareas se ejecutan en orden "perfectamente distribuido": cero a
cuatro y luego vuelta de nuevo a cero. Esto tiene bastante sentido, porque despus de cada instruccin de impresin cada
tarea pasa a dormir (se bloquea), 10 que permite al planificador de hebras conmutar a otra hebra distinta, que se encarga de
dirigir otra tarea. Sin embargo, el comportamiento secuencial descansa sobre el mecanismo subyacente de hebras, que es
diferente entre un sistema y otro, as que no podemos confiar en que las cosas sean siempre as. Si necesitamos controlar el
orden de ejecucin de las tareas, 10 mejor que podemos hacer es emplear controles de sincronizacin (descritos ms adelan-
te) 0, en algunos casos, no utilizar hebras en absoluto, sino en su lugar escribir nuestras propias rutinas cooperativas que se
entreguen unas a otras el control, en un orden especiflcado.
EjercIcio 6:
Prioridad
(2) Cree una tarea que duerma durante una cantidad aleatoria de tiempo comprendida entre 1 y 10 segun-
dos, y que luego muestre el tiempo durante el que ha estado dormida y salga. Cree y ejecute un cierto
nmero (indicado en la lnea de comandos) de estas tareas.
La prioridad de una hebra le indica al planificador la importancia de esa hebra. Aunque el orden en que el procesador eje-
cuta una serie de hebras es indeterminado, el planificador tender a ejecutar primero la hebra en espera que tenga la mayor
prioridad. Sin embargo, esto no significa que las hebras con una menor prioridad no se ejecuten (asi que es imposible que
se produzca un interbloqueo debido a las prioridades). Simplemente, las hebras de menor prioridad tienden a ejecutarse
menos a menudo.
La inmensa mayora de las veces, todas las hebras deberan ejecutarse con la prioridad predeterminada. Tratar de manipu-
lar las pIioridades de las hebras suele ser un error.
He aqu UD ejemplo que ilustra los niveles de prioridad. Podemos leer la prioridad de una hebra existente con getPriority( )
y cambiarla en cualquier momento con setPriority( ).
11 : concurrency/ SimplePriorities.j ava
II Muestra el uso de las prioridades de l as hebras.
import j ava .ut i l . concur rent .*;
public cla ss SimplePr iorities implements Runnable
pr ivate i nt countDown = 5;
private volatile double d II Sin optimizacin
priva te int prioritYi
public SimplePrior iti es( i nt priority) {
this . priority = prioritYi
p ublic B t r ing t oString () {
return Thre ad. currentThread () + n: I! + countDown;
publi c void r un() {
Thread.cur r entThrea d( ) .set Prior i ty{priority) i
while (true) {
/1 Una operacin costosa, interrumpible:
for(int i =; 1; i < 100000; i++} {
d +0 (Math.PI + Math.E) / (double)i;
if(i % 1000 00 O)
Thread.yield() ;
System.out.println(this) i
if{--countDown ==; O) return;
public static void main (String [] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i = O; i < 5; i++)
exec.execute(
new SimplePriorities(Thread.MIN_PRIORITY));
exec.execute(
new SimplePriorities (Thread.MAX_PRIORI'rY)) i
exec.shutdown() i
1* Output: (70% match)
Thread [pool-l-thread-6 f 10 f mainJ: 5
Thread[pool-l-thread-6,lO,main]: 4
Thread [pool-l- thread - 6 I 10 I mainJ! 3
Thread [pool-l-thread-6, 10 I mainJ: 2
Thread[pool-l-thread-6,lO,mainJ: 1
Thread [pool-l-thread-3, l,main] : 5
Thread [pool-l-thread-2, 1,mainJ : 5
Thread [pool-l-thread-l, 1, mainJ: 5
Thread [pool-l- thread - 5, 1, mainJ: 5
Thread [pool-l-thread-4, l,main] : 5
21 Concurrencia 739
toString() se sustituye para utilizar Thread.toString(), que imprime el nombre de la hebra, el nivel de prioridad y el "grupo
de hebras" al que la hebra pertenece. Podemos establecer el nombre de la hebras nosotros mismos a travs del constructor;
aqu se generan automticamente como pool-l-thread-l, pool-l-thread-2, etc. El mtodo toString( ) sustituido tambin
muestra el valor de cuenta atrs de la tarea. Observe que podemos obtener, dentro de una tarea, una referencia al objeto
Thread que est dirigiendo la tarea, invocando Thread.currentThread( ).
Podemos ver que el nivel de prioridad de la ltima hebra es el ms alto, y que el resto de las hebras tienen el nivel ms bajo.
Observe que la prioridad se fija al comienzo de run( ); fijar la prioridad en el constructor no sera adecuado, ya que el obje-
to Executor no ha comenzado la tarea en dicho instante.
Dentro de run( ), se realizan 100.000 repeticiones de un clculo en coma flotante bastante costoso, que implica la suma y
divisin de valores douhle. La variable d es de tipo volatile para tratar de garantizar que no se realicen optimizaciones del
compilador. Sin este clculo, no podramos ver el efecto de establecer los niveles de prioridad (intntelo: desactive median-
te un comentario el bucle for que contiene los clculos de tipo double). Con el clculo, podemos ver que la hebra con
MAX_PRIORITY recibe una preferencia mayor por parte del planificador de hebras (al menos, ste era el comportamien-
to en la mquina Windows XP). An cuando imprimir en la consola tambin representa un comportamiento relativamente
costoso, no es posible percibir los niveles de prioridad de esa forma, porque la impresin en la consola no puede verse inte-
rrumpida (en caso contrario, la visualizacin en la consola mostrara mensajes entremezclados al emplear hebras), mientras
que los clculos matemticos s que se pueden interrumpir. Los clculos duran lo suficiente como para que el mecanismo
de planificacin intervenga, conmute dos tareas y preste atencin a las prioridades, de modo que las hebras de alta priori-
dad obtienen preferencia. Sin embargo, para garantizar que se produzca un cambio de contexto, se invocan regulannente
instrucciones yield( ).
Aunque el JDK tiene 10 niveles de prioridad, este nmero no se corresponde excesivamente bien con los mecanismos uti-
lizados por muchos sistemas operativos. Por ejemplo, Windows tiene 7 niveles de prioridad que no son fijos, por 10 que esa
740 Piensa en Java
correspondencia es indeterminada en el caso de este sistema operativo. El sistema Solaris de Sun tiene 2
3
\ niveles. El nico
enfoque portable consiste en limitarse a utilizar MAX]RIORITY, NORM]RIORITY y MIN]RIORITY a la hora de
ajustar los niveles de prioridad.
Cesin del control
Si sabemos que ya hemos llevado a cabo lo que queramos hacer durante la pasada de un bucle en nuestro mtodo r un( ),
podemos proporcionar una indicacin al mecanismo de planificacin de hebras, en el sentido de que ya hemos realizado una
tarea suficiente y que puede permitirse a otra tarea disponer del procesador. Esta indicacin (y es una indicacin: no hay
ninguna garanta de que una implementacin concreta respete esa indicacin) toma la forma de una llamada al mtodo
yield(). Cuando invocamos yield( ), estamos sugiriendo que se pueden ejecutar otras hebras de la misma prioridad.
LiftOIT.java utiliza yield( ) para distribuir de manera adecuada el procesamiento entre las diversas tareas LiftOIT. Pruebe
a desactivar mediante comentarios la llamada a Tbread.yield( ) en LiftOff.run( ) para ver la diferencia. Sin embargo, en
general, no podemos confiar en yield( ) para ninguna tarea seria de controlo de optimizacin de la aplicacin. De hecbo,
yield( ) se emplea muy a menudo de manera incorrecta.
Hebras demonio
Una hebra "demonio" pretende proporcionar un servicio general en segundo plano mientras el programa se ejecuta, pero sin
formar parte de la esencia del programa. Por tanto, cuando todas las hebras no demonio se completan, el programa se ter-
mina, terminando tambin durante el proceso todas las hebras demonio. A la inversa, si existe alguna hebra no demonio que
todavia se est ejecutando, el prograroa no puede tenninar. Existe, por ejemplo, una hebra no demonio que ejecuta el mto-
do mai n().
/1 : concurrencyj SimpleDaemons. j ava
// La s hebras demonio no i mpi den que el programa t e r mine.
i mpor t java . u ti l. concurrent .*
impe rt stat i c net . mindview.uti l.Print.*
public c l ass Simpl eDaemons i mplements Runnable
publ ic voi d r un() {
try {
whil e (true) (
TimeUnit. MILLISECONDS.sl eep( l OO) ;
print (Thr ead. current Thread () + I! n + thi s } ;
catch( Int erruptedException e )
print ( "sl eep ( ) interrupted" );
pub l ic s t ati c void main (Stri ng[] a rgs) throws Exception
f or( i nt i = O; i < 1 0 ; i ++} {
Thr ead daemon = new Thr ead{new SimpleDaemons(
daemon .set Da emon(true ) ; /1 Hay que invocarla antes de sta rt ()
daemon . start () ;
print (Jl Al l daemons s t a rted
ll
) i
TimeUn i t. MI LLISECONDS. s leep (175 ) ;
/ * Output, (Sample )
Al l daemons started
Thread [Thread- O, S,main] Si mpleDa emons@S30daa
Thread[Thread - l,5, main] SimpleDaemons@a62fc3
ThreadtThre ad- 2,S,main] Simpl eDaemons@89 aege
Thread [Thread-3, S,main] SimpleDaemons@1270b73
Thread [Thr ead- 4,S , mai n ] Si mpleDaemons@60a e bO
Thread[Thread-S , 5, ma i n] Simpl eDaemons@16caf43
Thread[Thread-6 ,S,main] SimpleDaemons@6 6848c
Thread [Thread-7,5,mainl Simpl eDaemons@S8 13f2
Thread [Thread-8, 5,main] SimpleDaemons@ld58aae
Thread[Thread-9 ,S,mainl SimpleDaemons@83cc6 7
*///, -
Debemos definir la hebra como demonio invocando sctDacmon( ) antes de iniciarla.
21 Concurrencia 741
No hay nada que impida al programa terminar una vez que main() finaliza su trabajo, ya que lo nico que queda ejecutn-
dose son hebras demonio. Para poder ver el resultado de iniciar todas las hebras demonio, la hebra main( ) se pone breve-
mente a dormir. Sin esto, slo veramos parte de los resultados de la creacin de las hebras demonio (pruebe a realizar
llamadas a sleep() de diversas duraciones para ver este comportamiento).
SimpleDaemons.java crea objetos Tbread explcitos para poder activar el indicador que los define como hebras demonio.
Se pueden personalizar los atributos (demonio, prioridad, nombre) de las hebras creadas por objetos Executor escribiendo
una factora ThreadFactory personalizada:
/1 : net/mi ndvi ew/ util jDaemonThreadFactory.java
package net.mindview.util;
import java. util.concurrent.*i
public class DaemonThreadFactory implements ThreadFactory {
public Thread newThread{Runnable r) {
Thread t = new Thread(r) ;
t. s etDaemon (true) ;
return t;
La nica diferencia con respecto a un objeto ThreadFactory normal es que ste asigna el valor true al indicador que
identifica las hebras demonio. Ahora podemos pasar un nuevo objeto DaemonThreadFactory como argumento a
Executors.newCachedThreadPool( ):
11 : concurrency/ DaemonFromFactory.java
II Ut ilizaci6n de una fac t ora de hebras para crear demonios.
import java.util.concurrent.*;
i mpor t net . mindview.util.*;
import stat ic net.mindview.util.Print.*
public class DaemonFromFactory implements Runnable {
public void run() {
try {
while (true) {
TimeUnit .MILLISECONDS.sleep (100) :
print (Thread. c urrentThread () T 11 11 + this );
catch ( Interrupt edExcepti on e ) {
pri nt { II I nterr upted") :
public static void main{String [ ] args) throws Exception
ExecutorSe rvice exec = Executors. newCachedThreadPool{
new DaemonThreadFactory( ) );
f or(int i = O: i < 10; i++)
exec.execute(new DaemonFromFactory());
print (IIAl1 daemons started
ll
);
TimeUnit.MILLISECONDS.sleep (500); II Ejecutar durante un tiempo
1* (Ej ecutar para ver la salida) *11 1: -
742 Piensa en Java
Cada uno de los mtodos de creacin estticos ExecutorService se sobrecarga para tomar un objeto ThreadFactory que se
utilizar para crear nuevas hebras.
Podemos llevar este enfoque un paso ms all y crear una utilidad DaemonThreadPoolExecutor:
JI: net/mindview/util/DaemonThreadPoolExecutor . java
package net.mindview.util
import j ava . uti l.concurrent.*
public cla ss DaemonThreadPoolExecutor
extends ThreadPoolExecutor {
publ ic DaemonThreadPoolExecutor () {
s uper ( O, Integer.MAX_VALUE, 60L, TimeUni t . SECONDS,
new SynchronousQueue<Runnable>() ,
new DaemonThreadFact ory ()) ;
}
/// , -
Para obtener los valores para llamada al constructor de la clase base, simplemente hemos echado un vistazo al cdigo fuen-
te Executors.java.
Podemos averiguar si una hebra es de tipo demonio invocando isDaemon(). Si una hebra es un demonio, entonces todas
las hebras que cree sern tambin demonios automticamente, como demuestra el siguiente ej emplo:
JI: concurrencylDaemons.java
1/ La s hebras demonio crean otras hebras demonio.
i mport j ava.ut il. concurrent.* ;
i mport stat ic ne t .mindview. util . Pr i nt.*;
clas9 Da emon i mplement s Runnable {
pr vate Thr ead{ ] t = new Thread{ l O]
public void run() {
for (i nt i = O; i < t . lengt h; i++ )
tr i ] = new Thread(new DaemonSpawn ()) ;
t [i l . s tart ()
printnb ( nDaemonSpawn 11 + i + 1I start ed, 11 ) ;
f or (int i = O; i < t. l ength i ++ )
printnb ( "t [ 11 + i + "] . i sDaemon ( ) 11 +
t [i ) .isDaemon() + ", " ) ;
while (true)
Thread . y i eld ()
c lass Da emonSpawn i mplements Runnabl e {
publi c void run () {
whil e{ true)
Thr ead. yield ()
publi c c l ass Daemons {
publ i c static void main(String [) args) throws Exception {
Thread d = new Thread {new Daemon ( ) )
d. s etDaemon ( true) ;
d . start ()
pri ntnb(lId .isDaemon() = 11 + d.isDaemon() + ti , II};
II Permi tir que l as hebras demonio finalicen
II sus procesos de arranque:
TimeUnit.SECONDS.sl eep(l ) ;
1* Output: (Sample)
d.isDaemon() = true, DaemonSpawn o started, DaemonSpawn 1
started, DaemonSpawn 2 started, DaemonSpawn 3 started,
DaemonSpawn 4 started,
started, DaemonSpawn 7
DaemonSpawn 9 started,
t [1] . isDaemon () true,
t[3] .isDaemon() true,
t[5] . isDaemon() true,
t [7J .isDaemon() true,
t[9] . isDaemon() true,
*///,-
DaemonSpawn 5 started, DaemonSpawn 6
started, DaemonSpawn 8 started,
trOJ ,isDaemon() = true,
t [2] . isDaemon() true,
t [4] .isDaemon()
t [6] . isDaemon ()
t [8] . isDaemon ()
true,
true,
true,
21 Concurrencia 743
La hebra Daemon se configura en modo demonio. A continuacin, esa hebra inicia una serie de otras hebras (que no se defi-
nen explcitamente como demonios), para demostrar de todos modos que esas hebras sern de tipo demonio. A continua-
cin, Daemon entra en un bucle infinito que llama a yield( ) para ceder el control a los otros procesos.
Es necesario tener en cuenta que las hebras demonio terminarn sus mtodos run() sin ejecutar clusulas finaUy:
ji: concurrency/DaemonsDontRunFinally.java
ji Las hebras demonio no ejecutan la clusula finally
import java.util.concurrent.*i
import static net.mindview.util.Print.*
class ADaemon implements Runnable {
public vpid run() {
try {
print (It Starting ADaemon It) ;
TimeUnit.SECONDS.sleep(l)
catch(InterruptedException e) {
print (IIExiting via InterruptedException") i
finally {
print(ItThis should always run?lI);
public class DaemonsDontRunFinally {
public static void main(String[] args) throws Exception {
Thread t = new Thread(new ADaemon()) i
t. setDaemon (true) ;
t. start () ;
j* Output:
Starting ADaemon
*///,-
Cuando ejecutamos este programa, vemos que la clusula finaUy no se ejecuta, pero si desactivamos mediante comentarios
la llamada a setDaemon( ), la clusula fmally s que se ejecutar.
Este comportamiento es correcto, an cuando resulte algo inesperado, teniendo en cuenta las explicaciones dadas antes para
fmaUy. Los demonios se terminan "abruptamente" cuando termina la ltima de las hebras no demonio. Por tanto, en cuan-
to salimos de main( ), la mquina NM termina todos los demonios inmediatamente sin ninguna de las formalidades que
cabria esperar. Dado que no se pueden fmalizar los demonios de una manera limpia, las hebras demonio no suelen ser muy
convenientes. Generalmente, resulta mejor emplear objetos Executor no demonio, ya que todas las tareas controladas por
un objeto Executor pueden terminarse de una sola vez. Como veremos posterionnente en el captulo, esa terminacin tiene
lugar, en este caso, de una manera ordenada.
Ejercicio 7: (2) Experimente con diferentes tiempos de dormir en Daemons.java, para ver lo que sucede.
744 Piensa en Java
Ejercicio 8:
Ejercicio 9:
( 1) Modifique MoreBasicThreads.java para que todas las hebras sean de tipo demonio y el programa ter-
mine en cuanto maine ) sea capaz de terminar.
(3) Modifique SimplePriorities.java para que una factora personalizada TbreadFactory establezca las
prioridades de todas las hebras.
Variaciones de cdigo
Eu los ejemplos que hemos visto hasta ahora, todas las clases de tareas implementan la interfaz Runnable. En algrmos casos
muy simples, podemos utilizar la tcnica alternativa de heredar directamente de Thread, como en este ejemplo:
/1 : concurrency/SimpleThread . java
// Herencia directa de la clase Thread .
publ ic cIass SimpleThread extends Thread
prvate int countDown = 5i
private static i nt threadCount = O
publ i c SimpleThread( ) {
// Almacenar el nombr e de l a hebra:
super{Integer.toString(++threadCount)) ;
start () ;
public String toString()
return "#11 + getName() + 11 (JI + countDown + 11) 1 u
i
public void run() {
while (true) {
System.out.print{this) i
if{--countDown == O)
return;
publ ic static void rna i n (Stri ng [] args ) {
f or(int i = O; i < 5; i ++ }
new Simpl eThread () ;
1* Output:
#1(5), #1(4), #1(3), #1(2), #1(1 ), #2 ( 5), #2(4 ) , #2(3),
# 2(2), # 2(1) , #3(5), #3(4), #3(3), #3(2), # 3 (1), #4(5),
#4(4), #4(3), #4(2), #4(1), #5(5), # 5(4), #5(3), #5(2),
#5 (1),
*/// :-
Proporcionarnos a los objetos Thread nombres especficos invocando el constructor Thread apropiado. Este nombre se
extrae en toString() utilizando getName().
Otra estructura de cdigo que puede que se encuentre alguna vez es la del objeto Runnable auto-gestionado:
11 : concurrencyl SelfManaged. j ava
II Un objet o Runnable que contiene su propia hebra directora.
public class SelfManaged implements Runnable
private int countDown = Si
private Thread t = new Thread(this );
public SelfManaged () { t. start (); }
publ ic String toString () {
return Thread . currentThread () .getName() +
11 (I! + countDown + ") f ";
public void run ()
while (true) {
System.out.print(this) ;
if (- -countDown ~ O)
ret urn;
public static void main(St ring[] args) {
for(int i = o; i < 5i i++}
new SelfManaged();
1*
Output,
Thread- O (5) , Thread-O (4), Thread- Q(3) , Thread- O (2) ,
Thread- l (S) , Thread-l (4), Thread-l (3) , Thread- l(2) ,
Thread-2 (S) , Thread-2 (4) I Thread-2 (3), Thread-2 (2) ,
Thread- 3(S) , Thread-3 (4) , Thread- 3(3) , Thread- 3 (2),
Thread-4 (5) , Thread-4 (4) , Thread- 4 (3), Thread- 4 (2 ) ,
* /11 ,-
21 Concurrencia 745
Thread-O (1 ) ,
Thread-l (l ) ,
Thread-2 (1) ,
Thread-3 (1) ,
Thread- 4(1) ,
Esta tcnica no difiere especialmente de la de heredar de Tbread, salvo porque la sintaxis es ligeramente ms abstrusa. Sin
embargo, implementar una interfaz nos permite heredar de una clase distinta, cosa que no se puede hacer si heredamos desde
Thread.
Observe que start( ) se invoca dentro del constructor. Este ejemplo es bastante simple y resulta, por tanto, probablemente
seguro, pero hay que tener en cuenta que iniciar hebras dentro de un constructor puede resultar bastante problemtico, por-
que otra tarea podra empezar a ejecutarse antes de que el constructor se haya completado, lo que quiere decir que la tarea
pudiera ser capaz de acceder al objeto en un estado inestable. sta es otra razn adicional de preferir objetos Executor a la
creacin explcita de objetos Thread.
En ocasiones, resulta conveniente ocultar el cdigo de gestin de hebras dentro de la clase utilizando una clase interna, como
se muestra a continuacin:
JI: concurrency/ThreadVariations.java
jI Creacin de hebras con c lases internas.
import java.util.concurrent.*
i mport static net . mi ndview. util .Print .*
/1 Ut i lizacin de una clase i nterna nominada:
class InnerThreadl {
prvate int countDown = 5
private Inner inner
private class Inner extends Thread {
I nner (String name) {
super (name)
start () ;
public void run()
try {
while (true) {
print(this)
i f(--countDown
s l eep(lO) ;
O) return
catch( InterruptedException e ) {
print (11 i nterrupted" ) ;
public String toString()
return getName () + 11: 11 + countDown
public I nnerThreadl(String name) {
i nner = new Inner(name)
746 Piensa en Java
II utilizacin de una clase interna annima:
class InnerThread2 {
private int countDown = 5;
private Thread t;
public InnerThread2(String name) {
t = new Thread(name) {
public void run() {
try {
while (true) {
print(this) ;
if(--countDown
sleep (10);
O) return;
catch(InterruptedException el
print (I! sleep () interrupted 11) ;
public String toString()
}
} ;
return getName () + 11. I! + countDown;
t. start () ;
II Utilizacin de una implementacin Runnable nominada:
class InnerRunnablel {
private int countDown = 5;
private Inner inner;
private class Inner implements Runnable {
Thread t;
Inner (String name) {
t = new Thread(this, name);
t. start () ;
public void run ()
try {
while(true) {
print (this) ;
if(--countDown == O) return;
TimeUnit.MILLISECONDS.sleep(lO) ;
catch(InterruptedException e)
print (11 sleep () interrupted 11) ;
public String toString () {
return t.getName() + n: + countDown;
public InnerRunnablel(String name) {
inner = new Inner(name);
II Utilizacin de una implementacin Runnable annima:
class InnerRunnable2 {
private int countDown = 5;
private Thread ti
public InnerRunnable2(String name)
t = new Thread(new Runnable()
public void run() {
try {
while (true)
print(this) j
if(--countDown == O) return;
TimeUnit.MILLISECONDS.s!eep(lOl j
catch(InterruptedException el
print("sleep() interrupted
U
) i
public String toString () {
return Thread. currentThread () .getName() +
11. " + countDowll;
}
}, name);
t. start () i
// Un mtodo separado para ejecutar un cierto cdigo como una tarea:
class ThreadMethod {
private int countDown = 5;
private Thread t;
private String name;
public ThreadMethod(String name) { this.name
public void runTask() {
if It null) {
t = new Thread(name) {
public void run() {
try {
while (true) {
print(this) ;
if(--countDown
sleep(lO) ;
O) return
catch(InterruptedException e)
print (I! sleep () interrupted ")
public String toString()
}
} ;
return getName () + ". " + countDown
t. start ()
public class ThreadVariations {
public static void main(String[] args)
new InnerThreadl ( n InnerThreadl
ll
)
new InnerThread2 ( "InnerThread2 11) ;
new InnerRunnablel (11 InnerRunnablel
ll
) ;
new InnerRunnable2 (11 InnerRunnable2 11) ;
new ThreadMethod("ThreadMethod") .runTask();
/ * (Ej ecutar para ver la salida) * / / /:-
name; }
21 Concurrencia 747
748 Piensa en Java
InnerThreadl crea una clase interna nominada que ampla Thread y crea una instancia de esta clase interna dentro del
constructor. Esto tiene sentido si la clase interna dispone de capacidades especiales (nuevos mtodos) a los que necesitamos
acceder desde otros mtodos. Sin embargo, la mayor parte de las veces la razn para crear una hebra es nicamente utilizar
las capacidades de la clase Thread, por lo que no es necesario crear una clase interna nominada. InnerThread2 muestra
cul es la alternativa: dentro del constructor se crea illla subclase interna annima de Thread y se la generaliza a una refe-
rencia t a Thread. Si otros mtodos de la clase necesitan acceder a t, pueden hacerlo a travs de la interfaz Thread, y no
necesitan conocer el tipo exacto del objeto.
La tercera y cuarta clases del ejemplo repiten las dos primeras clases, pero en lugar de utilizar la interfaz Runnable em-
plean la clase Thread.
La clase ThreadMethod muestra la creacin de una hebra dentro de un mtodo. Si invocamos el mtodo una vez que este-
mos listos para ejecutar la hebra, el mtodo termina antes de que la hebra d comienzo. Si la hebra slo est realizando una
operacin auxiliar en lugar de alguna otra cosa ms fundamental para la clase, probablemente este enfoque resulte ms til
y apropiado que iniciar una hebra dentro del constructor de la clase.
Ejercicio 10: (4) Modifique el Ejercicio 5 de acuerdo con el ejemplo de la clase ThreadMethod, de modo que
runTask( ) tome un argumento que especifique la cantidad de nmeros de Fibonacci que hay que sumar,
y que cada vez que invoquemos runTask( ) devuelva el objeto Future producido por la llamada a
submit( ).
Terminologa
Como muestra la seccin anterior, existen diversas alternativas a la hora de implementar programas concurrentes en Java, y
dichas alternativas pueden resultar confusas. A menudo, el problema procede de la terminologa empleada a la hora de des-
cribir la tecnologa de programas concurrentes, especialmente en aquellos casos que hay implicadas hebras.
A estas alturas, deberla ya entender que existe una distincin entre la tarea que se est ejecutando y la hebra que la dirige;
esta distincin resulta especialmente clara en las bibliotecas Java, porque realmente no tenemos ningn control sobre la clase
Thread (y esta separacin es todava ms clara con los ejecutores, que se encargan de crear y gestionar las hebras por noso-
tros). Lo que hacemos es crear una tarea y asociar una hebra con cada tarea, de modo que la hebra se encargue de dirigirla.
En Java, la clase Thread no hace nada por s misma. Se limita a dirigir la tarea que se le indique. A pesar de ello, sobre los
mecanismos de gestin de hebras, suele utilizar expresiones como "la hebra realiza esta accin o esta otra". La nnpresin
que se obtiene al leer esto es que la hebra es la tarea, y cuando yo tropec con las hebras de Java, esta impresin era tan
fuerte que para m exista una relacin de tipo "es-un" muy clara, y de 10 cual yo deduca que era necesario heredar una tarea
de un objeto Thread. Aadamos a esto la inadecuada eleccin del nombre de la interfaz Runnable (ejecutable), que debe-
ra haberse denominado, mucho ms apropiadamente "Task" (tarea).
El problema es que los niveles de abstraccin estn mezclados. Conceptualmente, queremos crear una tarea que se ejecute
independientemente de otras tareas, por 10 que deberamos poder defmir una tarea y decir "ejecutar" sin preocupamos de
los detalles. Pero fsicamente las hebras pueden resultar muy costosas de crear, por 10 que es necesario conservarlas y ges-
tionarlas adecuadamente. Por tanto, tiene sentido, desde el punto de vista de la implementacin, separar las tareas de las
hebras. Adems, el mecanismo de hebras en Java est basado en la solucin de pthreads de bajo nivel proveniente de e, que
es una solucin en la que es necesario swnergirse y en la que es preciso entender todos los detalles de lo que est ocurrien-
do. Parte de esta naturaleza de bajo nivel ha terminado deslizndose en la implementacin Java, por 10 que para permane-
cer en un nivel mayor de abstraccin, es necesario ser disciplinado a la hora de escribir el cdigo (trataremos de mostrar esa
disciplina a 10 largo del captulo).
Para clarificar estas explicaciones, tratar de utilizar el trmino "tarea" cuando me refiera al trabajo que hay que realizar y
"hebra" nicamente a la hora de referirme al mecanismo especfico que se ocupa de dirigir la tarea. Por tanto, si estamos
analizando un sistema en un nivel conceptual podemos limitamos a emplear el trmino "tarea" sin necesidad de mencionar
en absoluto el mecanismo encargado de dirigir esa tarea.
Absorcin de una hebra
Una hebra puede invocar join( ) sobre otra hebra para esperar que esa segunda hebra se complete antes de que la primera
contine con su trabajo. Si una hebra invoca t.join( ) sobre otra hebra t, entonces la hebra invocante se suspende hasta que
la hebra objetivo t finalice (cuando t.isAlive() es false).
21 Concurrencia 749
Tambin podemos invocar join( ) con un argumento de fin de temporizacin (en milisegundos o en mili segundos y nanose-
gundos), de modo que si la hebra objetivo no finaliza en dicho perodo de tiempo, la llamada a joio( ) vuelve de todos
modos.
La llamada a join( ) puede abortarse invocando ioterrupt( ) sobre la hebra invocante, para lo que hace falta una clusula
try-catch.
Todas estas operaciones se ilustran en el siguiente ejemplo:
ji: concurrency/Joining.java
II Ejempl o de join().
import static net.mindview. util.Print.*
cIass Sleeper extends Thread {
prvate i nt duration
public Sleeper (String name , int sleepTime) {
super (name) ;
duration = sleepTime;
start () ;
public void run() {
try (
sleep(duration) i
cat ch(InterruptedExcepti on e) {
print(getName() + u was interrupted. 11 +
11 islnterrupted (): 11 + islnterr upted () ) ;
returnj
print (getName () + 11 has awakened") i
class Joi ner extends Thread {
private Sl eeper s l eeper
public Joiner(String name, Sleeper s l eeper) {
super (name) i
thi s.sleeper = sleeper
start () ;
public void run() {
try (
sleeper .join() i
catch(InterruptedExcept ion el
print (Ulnterrupted!1 );
print (getName () + u join completed
ll
) i
public class Joining
public s t atic void
Sleeper
main(String [J args) (
s l eepy
grumpy
Joiner
new Sleeper (USleepyl!,
new Sleeper (IIGrumpyl!,
1500 1 ,
1500 1 ;
dopey "" new Joiner (lIDopeyll, sleepy),
doc = new Joiner(!1Doc
U
, grumpy) i
grumpy.inter r upt()
1* Output ,
750 Piensa en Java
Grumpy was interrupted. islnterrupted(): false
Doc join completed
Sleepy has awakened
Dopey join completed
*111,-
Un objeto Sleeper es una hebra que pasa a dormir durante un tiempo especificado en su constructor. En run( l, la llamada
a sleep( l puede terminar cuando finaliza el tiempo especificado, pero tambin puede ser interrumpida. Dentro de la clu-
sula cateh, se informa de la interrupcin, junto con el valor de islnterrupted( l. Cuando otra hebra invoca interrupt( l sobre
esta hebra, se activa un indicador para mostrar que la hebra ha sido interrumpida. Sin embargo, este indicador se borra en
el momento de tratar la excepcin, por lo que el resultado ser siempre false dentro de la clusula eateh. El indicador se uti-
liza para otras situaciones en las que una hebra puede examinar su estado de interrupcin, de forma independiente de la
excepcin.
Un objeto Joiner es una tarea para que un objeto Sleeper se despierte invocando join( l sobre ese objeto Sleeper. En
main( l, cada objeto Sleeper tiene un objeto Joiner y podemos ver a la salida que si el objeto Sleeper es interrumpido o
finaliza normalmente, el objeto Joiner completa su tarea en conjuncin con el objeto Sleeper.
Observe que las bibliotecas java.util.eoneurrent de Java SES contienen herramientas tales como CyelieBarrier (que se
ilustra ms adelante en este captulo l que pueden ser ms apropiadas que join( l, que formaba parte de la biblioteca de hebras
original.
Creacin de interfaces de usuario de respuesta rpida
Como hemos indicado anteriormente, uno de los motivos para la utilizacin de hebras consiste en crear una interfaz de usua-
rio de rpida respuesta. Aunque no vamos a sumergimos en las interfaces grficas hasta el Captulo 22, Inteifaces grficas
de usuario, el siguiente ejemplo es un simple prototipo de una interfaz de usuario basada en consola. El ejemplo tiene dos
versiones: una que se queda bloqueada en un clculo y nunca puede leer la entrada de la consola y una segunda que inser-
ta el clculo dentro de una tarea y puede, por tanto, estar realizando a la vez el clculo y escuchando la entrada de la con-
sola.
11: concurrency/ResponsiveUI.java
II Capacidad de respuesta de la interfaz de usuario
II {RunByHand}
class UnresponsiveUI
private volatile double d = 1
public UnresponsiveUI() throws Exception
while(d> O)
d = d + (Math.PI + Math.E) I d;
System.in.read(); II Nunca llega aqu
public class ResponsiveUI extends Thread
private static volatile double d = 1
public Responsi veUI () {
setDaemon(true) ;
start () ;
public void run() {
while (true) {
d = d + (Math.PI + Math.E) I d;
public static void main(String[] args) throws Exception {
II! new UnresponsiveUI() II Hay que matar este proceso
new ResponsiveUI()
System.in.read{)
21 Concurrencia 751
System.out.println(d); jI Mostrar el progreso
UnresponsiveUI realiza un clculo dentro de un bucle while infmito, por lo que nunca, obviamente, alcanza la lnea de
entrada de datos de la consola (hemos engaado al compilador para que piense que la lnea de entrada es alcanzable utili-
zando el while condicional). Si desactivamos mediante un comentario la lnea que crea una interfaz UnresponsiveUI, ten-
dremos que matar el proceso para poder salir.
Para hacer que el programa tenga una adecuada capacidad de respuesta, hay que colocar el clculo dentro de un mtodo
run( ) para permitir que se lo pueda desalojar del procesador, y cuando pulsemos la tecla Intro, veremos que el clculo ha
estado ejecutndose en segundo plano mientras se esperaba a que se produjera la entrada del usuario.
Grupos de hebras
Un grupo de hebras mantiene una coleccin de hebras. La ventaja de los grupos de hebras puede resumirse citando las pala-
bras de Joshua Bloch,
8
el arquitecto de software que, mientras estaba en Sun, corrigi y mejor enormemente la biblioteca
de colecciones de Java en el JDK 1.2 (entre otras contribuciones):
"Los grupos de hebras podran definirse como un experimento que no tuvo xito, as que se puede sim-
plemente ignorar su existencia ".
Si el lector ha invertido tiempo y esfuerzo tratando de comprender el valor de los grupos de hebras (como es mi caso), podra
preguntarse por qu no se ha producido ningn anuncio ms oficial de Sun acerca de este tema: la misma cuestin podra
plantearse acerca de varios otros cambios que Java ha sufrido a lo largo de los aos. El economista Joseph Stiglitz laurea-
do con el Premio Nobel tiene una filosofia de la vida que pod...ra aplicarse aqu.
9
Se denomina La teora del compromiso
delegado:
"El coste de continuar con los errores es soportado por otros, mientras que el coste de admitirlos es
soportado por nosotros. "
Captura de excepciones
Debido a la naturaleza de las hebras no podemos capturar una excepcin que haya escapado de una hebra. Una vez que una
excepcin sale del mtodo run( ) de una tarea, no se propagar hacia la consola a menos que adoptemos medidas especia-
les para capturar esas excepciones "vagabundas". Antes de Java SES, se utilizaban los grupos de hebras para capturar estas
excepciones, pero con Java SES podemos resolver el problema mediante objetos Executor, por lo que deja de ser necesa-
rio saber nada acerca de los grupos de hebras (salvo para entender el cdigo heredado, vase Thinking in Java, 2" Edicin,
descargable de www.MindView.net. para conocer ms detalles sobre los grupos de hebras).
He aqu una tarea que siempre genera una excepcin que se propaga fuera de su mtodo run( ) y un mtodo maine ) que
muestra lo que sucede al ejecutarlo:
jj: concurrencyjExceptionThread.java
II {ThrowsException}
import java.util.concurrent.*;
public cIass ExceptionThread implements RunnabIe {
pubIic void run() {
throw new RuntimeException()
pubIic static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool()
exec.execute(new ExceptionThread())
8 Effective JavaTM Programming Language Guide, de Joshua Bloch (Addison-Wesley,2001), p. 21t.
9 Y en varias otras ocasiones relacionadas con la utilizacin de Java. Bueno, en realidad, por qu detener esto? En el pasado he prestado labores de con-
sultora en bastantes proyectos en los que esta filosofa era aplicable.
752 Piensa en Java
La salida es (despus de quitar algunos cualificadores para que quepa en la pgina):
j ava .lang . Runt imeException
at ExceptionThread.run(ExceptionThread.java:7)
at ThreadPoolExecutor$Worker.runTask(Unknown Souree)
at ThreadPoolExecutor$Worker.run(Unknown Souree)
at java.lang.Thread.run(Unknown Sauree)
No se pierde nada al encerrar el cuerpo del mtodo principal dentro un bloque try-catch:
JI: concurrency/NaiveExceptionHandling.java
// {ThrowSException}
import java . ut il.concurrent.*;
public class NaiveExcept ionHandli ng
public stat i c void ma i n(Stri ng [J args)
t ry {
Execut orService exec =
Executors .newCachedThreadPool (};
exec.execut e(new ExceptionThread( ) ;
catch(RuntimeException ue) {
/1 Esta i ns t ruccin NO se ejecutar !
System.out .println(lIException has be en handled! ") i
Esto produce el mismo resultado que el ejemplo anterior: una excepcin no capturada.
Para resolver el problema, cambiemos la forma en que el objeto Executor produce las hebras. Tbread.UncaugbtExcep-
tionHaodl er es una nueva interfaz en Java SES; que nos pernlite asociar IDla rutina de tratamiento de excepciones a cada
obj eto Tbread. Tbread.UncaughtExceptionHandler.nncaugbtException() se invoca automticamente cuando esa hebra
est a punto de morir debido a una excpecin no capturada. Para usarla, crearnos un nuevo tipo de factora ThreadFactory
que asocia un nuevo objeto Tbread.UncaugbtExceptionHandler a cada nuevo objeto Tbread que crea Pasamos dicha
factora al mtodo Executors que crea un nuevo objeto ExecutorService:
11 : concurrency/CaptureUnca ught Exception.java
import java.util . concurrent .*
class ExceptionThread2 implements Runnabl e
public void run () {
Thread t = Thread.currentThread() ;
Sys t em.out .println(llrun() by 11 + t)
System. out .println(
lIeh = Il + t .getUncaughtExcept ionHandl er()) i
t hrow new RuntimeException()
cIass MyUncaught ExceptionHandler implements
Thread.UncaughtExcept i onHandler {
public void uncaughtExcept ion (Thread t, Throwabl e e l {
System. out .pr i ntln ( "caught + e l;
c l ass HandlerThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
System. out.println(this + 11 creating new Thread") i
Thread t = new Thread(r)
System.out.println(lIcreated 11 + t);
t .setUncaughtExceptionHandl er {
new MyUncaughtExceptionHandler { i
System.ou t.println (
fleh = n + t . getUncaughtExceptionHandler ( }) ;
return ti
public cIass CaptureUncaughtException {
public s tatic void main(St ring[J a rgs)
ExecutorService exec = Executors .newCachedThreadPool(
new HandlerThreadFact ory(} ) j
exec .execute(new ExceptionThread2 ( i
/ * Output, (90% match )
Ha ndlerThreadFactory@de6ced creat i ng new Thread
creat ed Thread [Thread- O,5,main1
eh = MyUncaugh tExceptionHandl ey@l f b8ee3
r un() by Thr ead [Thread- O,5 ,main1
eh = MyUnc aughtExceptionHandler@l f b8ee3
caught java . lang.RuntimeException
*///, -
21 Concurrencia 753
Hemos ailadido facilidades de traza adicionales para verificar que las hebras creadas por la factorfa reciben el nuevo
objeto UncaughtExceptionHandler. Podemos ver que las excepciones no capturadas estn siendo ahora capturadas
uncaughtException.
El ejemplo anterior nos pelTIlite configurar la rutina de tratamiento caso por caso. Si sabemos que vamos a utilizar la misma
rutina de tratamiento de excepciones en todas partes, una tcnica todava ms simple consiste en defInir la rutina predeter-
minada de tratamiento de excepciones no capturadas, que configura un campo esttico dentro de la clase Thread:
1/ : concurrencyl Set tingDefaul tHandl er. j a va
import java .util . concurr ent .*
public c l ass SettingDe f aultHandler {
pub lic s tati c voi d main (St r i ng[ ] args )
Thread.setDefaul t Uncaught ExceptionHandler(
new MyUncaught Except ionHandler( )) ;
Execut orSerV1ce exec = Executors .newCachedThreadPool () ;
exec.execute(new ExceptionThread())
/* Output:
caught j ava .lang .RuntimeException
*///,-
Esta rutina de tratamiento slo se invoca si no existe una rutina de tratamiento de excepciones no capturadas para la hebra
concreta. El sistema comprueba si la hebra dispone de una rutina y, en caso contrario, mira a ver si el grupo de hebras espe-
cializa su mtodo uncaughtException(); en caso contrario, invoca la rutina predeterminada defaultUncaughtException-
Handler .
Comparticin de recursos
Podemos pensar en un programa que tenga una sola hebra como si fuera una entidad solitaria que se mueve en nuestro espa-
cio de problema y que hace una sola cosa cada vez. Puesto que s6lo hay una entidad, no tenemos que pensar en el proble-
ma de que dos entidades intenten utilizar el mismo recurso al mismo tiempo: problemas que son similares al caso de dos
personas que intentaran aparcar en el mismo sitio, que estuvieran tratando de pasar por una misma puerta al tiempo o que
estuvieran incluso intentando hablar simultneamente.
Con la concurrencia, no tienen por qu existir entidades solitarias, sino que tenemos la posibilidad de que haya dos o ms
tareas interfi riendo entre s. Si no evitamos las colisiones, podemos encontramos con que las dos tareas traten de acceder a
la misma cuenta corriente al mismo tiempo, imprimir en la misma impresora, ajustar la misma vlvula, etc.
754 Piensa en Java
Acceso inapropiado a los recursos
Consideremos el siguiente ejemplo en el que una tarea genera nmeros pares y otras tareas consumen dichos nmeros. Aqu,
el nico trabajo de las tareas consumidoras consiste en comprobar la validez de los nmeros pares.
En primer lugar, definimos EvenChecker, la tarea consumidora, ya que la vamos a reutilizar en todos los ejemplos subsi-
guientes. Para desacopl ar EvenChecker de los varios tipos de generadores con los que vamos a estar experimentando, crea-
remos W1a clase abstracta denominada IntGenerator, que contiene el mnimo nmero de mtodos necesarios que
EvenCbccker debe utilizar: dispone de un mtodo next( ) y el generador puede ser cancelado. Esta clase no implementa la
interfaz Generator, porque debe generar un valor int, y los genricos no soportan parmetros primitivos.
11: concurrency/ lntGenerator,java
publi c abstract cIass IntGenerator
pr ivate volati l e boolean canceled
public abs t ract i nt next ()
II Pe rmit i r que cancel arlo:
f alse
public void cancel () { cancel ed = true }
public boolean isCanceled() { return canceled
11 /,-
IntGenerator tiene un mtodo cancel( ) para cambiar el estado de un indicador booleano canceled, as como un mtodo
isCaneeled( ) para ver si el objeto ha sido cancelado. Puesto que el indicador canceled es de tipo boolean, es atmico, lo
que significa que las operaciones simples como la asignacin y la devolucin de un valor, tienen lugar sin que se puedan
producir interrupciones, as que es posible ver ese campo en un estado intermedio durante la realizacin de esas operacio-
nes simples. El indicador caneeled tambin es de tipo volatile, con el fin de asegurar la visibilidad. Hablaremos ms en deta-
lle de la atomicidad y la visibilidad posteriormente en el captulo.
Cualquier objeto IntGenerator puede ser probado con la siguiente clase EvenCheeker:
11: concurrency/EvenChecker.java
import java.util.concurrent.*
pUblic c l ass EvenChecker implement s Runnable {
pri vate IntGenerator generator;
private final int id:
public EvenChecker (IntGenerator g, int identl
generator = 9:
id = ident;
public voi d r un()
while(!generator.isCanceled() )
int val = generator.next()
if(val % 2 != O) {
System.out .println (va l + " not even! " ) ;
generator. cance l ( ) ; II Cancel a todos los objetos EvenChecker
II Probar cualquier tipo de IntGenerator:
pUblic stati c void test (Int Generat or gp, int count ) {
System. out .println ( "pr ess Control - C t o exit
n
) ;
ExecutorServ i ce exec : Executors.newCachedThreadPool(};
for(int i = O: i < count i++)
exec.execute(new EvenChecker(gp, i ));
exec .shutdown ()
II Valor predet e r minado de recuento :
publ ic static void tes t (IntGenerator gp) {
t est (gp, 10);
21 Concurrencia 755
Observe que en este ejemplo la clase que puede cancelarse no es de tipo Runnable. En su lugar, todas las tareas
EvenCbecker que dependen del objeto lntGenerator lo comprueban para ver si ha sido cancelado, Como podemos ver en
run(). De esta forma, las tareas que comparten un recurso comn (el objeto IntGenerator) observan dicho recurso para
ver la seal de terminacin. Esto elimina las denominadas condiciones de carrera, en las que dos o ms tareas compiten
para responder a una condicin y, por tanto, colisionan o producen de alguna otra manera resultados incoherentes. Es nece-
sario pensar con cuidado todas las posibles formas en que un sistema concurrente puede fallar y protegerse frente a ellas.
Por ejemplo, una tarea no puede depender de otra tarea porque el orden de terminacin de las tareas no est garantizado.
Aqu, haciendo que las tareas dependan de un objeto que no es una tarea, eliminamos esa potencial condicin de carrera.
El mtodo testO prepara y realiza una prueba de cualquier tipo de IntGenerator, iniciando una serie de objetos
EvenChecker que usan el mismo objeto IntGenerator. Si IntGenerator provoca un fallo, test() informar de l y volve-
r. En caso contrario, es necesario pulsar Control-C para terminar el mtodo.
Las tareas EvenChecker estn constantemente leyendo y probando los valores de su objeto IntGenerator asociado.
Observe que si generator.isCanceled( ) es true, run( ) vuelve, lo que dice al objeto Executor en EvenChecker.test( ) que
la tarea est completa. Cualquier tarea EvenCbecker puede invocar cancele ) sobre su objeto IntGcnerator asociado, lo
que har que todas las tareas EvenChecker que estn usando IntGencrator terminen de manera grcil. En secciones pos-
teriores, veremos que Java ofrece mecanismos ms generales que estos para la terminacin de las hebras.
El primer objeto IntGenerator que vamos a examinar dispone de un mtodo next( ) que produce un. serie de valores pares:
11 : con curr ency/EvenGen e r a tor.jav a
II Cuando las hebras col isionan .
pub lic cl a ss Ev enGe nerator exten ds Int Gene r ator
p r i vate int c urre nt EvenValue = O;
public int next () {
++currentEv enVal ue II Punto de p eligro !
++currentEvenValue
r eturn currentEvenValue ;
pub lic static vo i d main (St ring [) arg s ) {
EvenChecker.test(new EvenGenerat or())
/ * Output, (Samp l e )
Press Con tro l - C to exit
894 76 993 not even !
89476993 not e ve n!
*///, -
Resulta posible que Wla tarea invoque next( ) despus de que otra tarea haya realizado el primer incremento de
currentEvenValue pero no el segundo <en el lugar del cdigo que tiene el comentari o " iPunto de peligro!"). Esto hace que
el valor quede en un estado "incorrecto". Para probar que esto puede suceder, EvenChecker.test() crea un grupo de obje-
tos EvenChecker para leer continuamente la salida de un objeto EvenGenerator y ver que cada valor es par. Si no lo es,
se informa del error y el programa termina.
Este programa terminar por fallar, porque las tareas EvenChecker tienen que acceder a la informacin de EvenGenerator
mientras que esa infonnacin est en un estado "incorrecto". Sin embargo, puede que el problema no se detecte hasta que
el obj eto EvenGenerator haya completado muchos ciclos, dependiendo de las particularidades de nuestro sistema operati-
vo y de otros detalles de implementacin. Si queremos ver mucho ms rpido cmo falla el programa, podemos incluir una
llamada a yield() entre el primer y el segundo incremento. Esto es parte del problema con los programas multihebra: pue-
den parecer correctos a pesar que existe un error, siempre y cuando la probabilidad de fallo sea baj a.
Es importante comprobar que la propia operacin de incremento requiere mltiples pasos y que la tarea puede ser suspen-
dida por el mecanismo de gestin de hebras en mitad de una operacin de incremento; en otras palabras, el incremento no
es una operacin atmica en Java. As que, incluso no sera seguro realizar una operacin de incremento sin proteger la
correspondiente tarea.
756 Piensa en Java
Resolucin de la contienda por los recursos compartidos
El ejemplo anterior muestra un problema fundamental a la hora de emplear hebras: nunca sabemos cundo va a ejecutarse
una hebra. Imagine que nos encontramos ante una mesa, sentados con un tenedor y a punto de tomar el ltimo bocado de
un plato, y que al acercar nuestro tenedor a ese bocado ste se desvanece sbitamente, porque nuestra hebra fue suspendi-
da y otro comensal se adelant y se comi ese bocado. se es el problema con el que estamos tratando cuando escribimos
programas concurrentes. Para que la concurrencia funcione, necesitamos alguna forma de impedir que dos tareas accedan
al mismo recurso simultneamente, al menos durante los perodos crticos.
Evitar este tipo de colisin es cuestin, simplemente, de bloquear un recurso mientras que una tarea lo est utilizando. La
primera tarea que acceda a un recurso debe bloquearlo y entonces las otras tareas no podrn acceder a l hasta que se des-
bloquee, en cuyo momento otra tarea lo bloquear y lo usar, y as sucesivamente.
Para resolver el problema de la colisin de hebras, casi todos los esquemas de concurrencia serializan el acceso a los recur-
sos compartidos. Esto quiere decir que slo se permite que una sola tarea acceda al recurso compartido en cada momento.
Esto se suele conseguir, nonnalmente, colocando una clusula alrededor de un fragmento de cdigo, de fonna que slo se
permita que una nica tarea pase en cada momento a travs de ese cdigo. Puesto que esta clusula produce una exclusin
mutua, un nombre comn que se emplea para este tipo de mecanismo es el de mutex.
Considere el cuarto de aseo de nuestra casa; varias personas (tareas dirigidas por hebras) podran querer el uso exclusivo del
cuarto de bao (el recurso compartido). Para acceder al cuarto de bao, una persona llama a la puerta para ver si est ocu-
pado. Si no lo est, entra y bloquea la puerta. Cualquier otra tarea que quiera utilizar el cuarto de bao estar "bloqueada"
y no podr utilizarlo, as que esas tareas esperarn en la puerta hasta que el cuarto de bao quede disponible.
La analoga resulta algo menos apropiada en lo que respecta al instante en el que el cuarto de aseo queda libre y llega el
momento de proporcionar acceso a otra tarea. En realidad, no existe una cola de personas y no estamos seguros de quin
ocupar a continuacin el cuarto de bao, porque el planificador de hebras no es determinista en ese sentido. En lugar de
ello, es como si hubiera un grupo de tareas bloqueadas dando vueltas en las cercanas del cuarto de bao y cuando la tarea
que ha ocupado el cuarto de bafio lo desbloquea y sale de l, la tarea que est ms cerca de la puerta aprovechar para intro-
ducirse. Como hemos indicado anteriormente, podemos enviar sugerencias al planificador de hebras mediante yield( ) y
setPriority(), pero puede que esas sugerencias no tengan demasiado xito dependiendo de la platafonna y de la implemen-
tacin de la mquina NM.
Para impedir las colisiones relativas a los recursos, Java tiene un soporte integrado, basado en el uso de la palabra clave
synchronized. Cuando una tarea quiere ejecutar un fragmento de cdigo protegido por la palabra clave synchronized, com-
prueba si el bloqueo est disponible, lo adquiere, ejecuta el cdigo y lo libera.
El recurso compartido es, normalmente, simplemente un cierto espacio de memoria, en forma de un objeto, pero tambin
podra ser un archivo, un puerto de EIS o algo ms complejo como una impresora. Para controlar el acceso a un recurso com-
partido, primero ponemos ese recurso dentro de un objeto. Entonces cualquier mtodo que emplee el recurso puede sincro-
nizarse con synchronized. Si una tarea est enmarcada en una llamada a los mtodos synchronized, todas las dems tareas
se vern impedidas de entrara en ninguno de los mtodos synchronized hasta que la primera tarea vuelva de su llamada.
En el cdigo de produccin, ya hemos visto que conviene hacer que los elementos de datos de una cIase sean privados y, de
manera que se acceda a esa memoria nicamente a travs de mtodos. Podemos impedir las colisiones declarando dichos
mtodos como de tipo synchronized, de la forma siguiente:
synchronized void f () { /* ... * / }
synchronized void g () { /* ... * / }
Todos los objetos contienen, automticamente, un nico bloqueo (tambin denominado monitor). Cuando invocamos cual-
quier mtodo synchronized, dicho objeto se bloquea y no podr llamarse a ningn otro mtodo synchronized de ese obje-
to hasta que el primero tennine y libere el bloqueo. Para los mtodos del ejemplo anterior, si una tarea invoca f( ) para un
objeto, ninguna otra tarea podr invocar a f( ) o g( ) para el mismo objeto hasta que f() se haya completado y libere el blo-
queo. Por tanto, existe un nico bloqueo que es compartido por todos los mtodos synchronized de un objeto concreto, y
este bloqueo puede emplearse para evitar que haya ms de una tarea escribiendo en la memoria del objeto simultneamente.
Observe que resulta especialmente importante que los campos sean de tipo private a la hora de trabajar con concurrencia;
en caso contrario, la palabra clave synchronized no podr evitar que otra tarea acceda a un campo directamente, y por tanto
se produzcan colisiones.
21 Concurrencia 757
Una tarea puede adquirir el bloqueo de un objeto mltiples veces. Esto sucede si un mtodo invoca un segundo mtodo sobre
el mismo objeto, que a su vez invoque otro mtodo sobre el mismo objeto, etc. La mquina NM lleva la cuenta del nme-
ro de veces que el objeto ha sido bloqueado. Si el objeto est desbloqueado, ese valor de recuento ser igual a cero. Cuando
una tarea adquiere un bloqueo por primera vez, el valor de recuento pasa a ser uno. Cada vez que la misma tarea adquiere
otro bloqueo sobre el mismo objeto, se incrementa el valor de recuento. Naturalmente, esa adquisicin mltiple de bloqueos
slo est permitida para la tarea que haya adquirido el bloqueo en primer lugar. Cada vez que la tarea abandona un mtodo
synchronized, el valor de recuento se decrementa, hasta que llegne a valer cero, lo que hace que se libere el bloqueo com-
pletamente y que pueda ser usado por otras tareas.
Tambin existe un nico bloqueo por clase (como parte del objeto Cia de dicha clase), de modo que los mtodos synchro-
nized estticos pueden impedir que otros mtodos similares accedan simultneamente a los datos estticos de la clase.
Cundo debemos efectuar una sincronizacin? Le recomendamos que aplique la Regla de Brian de la sincronizacin:
10
Si estamos escribiendo una variable que pueda ser leda a continuacin por otra hebra, o leyendo
una variable que pueda haber sido escrita en ltimo lugar por otra hebra, es necesario utilizar la sin-
cronizacin; adems, tanto el lector como el escritor deben sincronizarse usando el mismo bloqueo
monitor.
Si tenemos ms de un mtodo en nuestra clase que trate con los datos crticos, ser necesario sincronizar todos los mtodos
relevantes. Si slo sincronizamos uno de los mtodos, entonces los otros podrn ignorar el bloqueo del objeto y podrn ser
invocados con impunidad. ste es un punto muy importante: todo mtodo que acceda a un recurso compartido crtico debe-
r estar sincronizado, porque sino el programa no funcionar.
Sincronizacin de EvenGenerator
Aadiendo synchronized a EvenGenerator.java, podemos impedir los accesos no deseados de las hebras:
11 : concurrency/SynchronizedEvenGenerator.java
II Simplif icacin de l os mutex con la palabra c l ave synchronized .
// {RunByHand}
public c lass
SynchronizedEvenGenerator extends IntGenerator
private int current EvenValue = O;
public synchronized i nt next ( ) {
++currentEvenVal ue
Thread.yield () ; II Provoca e l fal l o ms rpidamente
++current EvenValue
return currentEvenValue;
public stati c void main (String [] args l {
EvenChecker. t es t (new SynchronizedEvenGenerator( }} ;
}
/// , -
Hemos insertado a Thread.yield( ) entre dos incrementos, para generar la probabilidad de que se produzca un cambio de
contexto mientras que currentEven Value se encuentra en un estado incorrecto. Puesto que el mutex evita que haya ms
de una tarea en la seccin crtica simultneamente, esto no produce un fallo, pero invocar yield( ) es una forma muy til de
aumentar la probabilidad de fallo en caso de que ste vaya a suceder.
La primera tarea que entra en next( ) adquiere el bloqueo, y todas las tareas sucesivas que intenten adquirir el bloqueo no
podrn hacerlo hasta que la primera tarea lo libere. En dicho punto, el mecanismo de planificacin seleccionar otra tarea
que est esperando a adquirir el bloqueo. De esta forma, slo puede haber una tarea en cada momento pasando por el cdi-
go protegido por el mutex.
lO En bonor de Brian Goetz, autor de Java Concurrency hl Practice, por Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes y Doug
Lea (Addison-Wesley, 2006).
758 Piensa en Java
Ejercicio 11:
(3) eree una clase que contenga dos campos de datos y un mtodo que manipule dichos campos en un pro-
ceso multipaso, y que durante la ejecucin de dicho mtodo, dichos campos pasen por "estados incorrec-
tos" (de acuerdo con una cierta definicin que usted mi smo puede establecer), Aada mtodos para leer
los campos y cree mltiples hebras para invocar los distintos mtodos y mostrar que los datos estn visi-
bles en su "estado incorrecto". Corrija el problema empleando la palabra clave synchronized.
Uso de objetos Lock explcitos
La biblioteca java.utU.concurrent de Java SE5 tambin contiene un mecanismo explcito de mutex definido en
java.utU.concnrrent.locks. Para el objeto Lock es necesario crearlo, bloquearlo y desbloquearlo explcitamente; por tanto,
produce un cdigo menos elegante que el mecanismo integrado. Sin embargo, es ms flexible para resolver ciertos tipos de
problemas. He aqu SynchronizedEvenGenerator.java reescrito para utilizar bloqueos Lock explcitos:
ji: concurrency/ Mut exEvenGenerator .j ava
JI Prevencin de l as colisiones de hebras median te mutex.
II {RunByHand}
i mpor t java.util.concurrent.locks .*
public c l ass MutexEvenGenerator extends IntGenerator
private int currentEvenValue = O
pri vate Lock lock new Reent rantLock( )
public int next()
l ock .lock () ;
try (
++currentEvenValue
Thread.yield () /1 Provoca un fa l lo ms rpidamente
++currentEvenValue
return currentEvenValue
finally (
l ock.un lock( )
public sta ti c voi d main (String (] args) {
EvenChecker.test(new MutexEvenGener ator( i
}
111>
MutexEvenGenerator aade un mutex denominado lock que utiliza los mtodos lock( ) y unlock() para crear una seccin
crtica dentro de next( ). Cuando utilizamos objetos Lock, es importante intemalizar la estructura sintctica que aqu se
muestra: justo despus de la llamada a lock( ), hay que insertar una instruccin try-finaUy con unlock( ) en la clusula
finally, sta es la nica forma de garantizar que el bloqueo se libere siempre. Observe que la instruccin return debe estar
dentro de la clusula try para garantizar que el mtodo unlock( ) no se ejecute demasiado pronto, exponiendo los datos a la
posible manipulacin por parte de otra tarea.
Aunque la clusula try-finaUy requiere ms cdigo que utilizar la palabra clave synchronized, tambin representa una de
las ventajas de los objetos Lock explcitos. Si algo falla empleando la palabra clave synchronized, se genera una excep-
cin, pero no tenemos la posibilidad de realizar ninguna tarea de limpieza para poder mantener el sistema en un estado
correcto. Con los objetos Lock explcitos, podemos mantener el estado correcto del sistema empleando la clusula finally.
En general, cuando utilizamos synchronized, necesitaremos escribir menos cdigo y la oportunidad de que se produzcan
errores se reduce enormemente, por lo que slo utilizaremos normalmente los objetos Lock explcitos cuando estemos resol-
viendo problemas especiales. Por ej emplo, con la palabra clave synchronized, no podemos realizar intentos fallidos de
adquirir un bloqueo, ni tampoco tratar de adquirir un bloqueo durante un cierto espacio de tiempo y luego liberarlo; para
hacer estas cosas, es necesario emplear la biblioteca concurrent:
11 : concurrency/AttemptLocking.j ava
II Los bloqueos de la biblioteca concurrent nos permiten
II desistir a l intentar a dquiri r un bloqueo.
import java.util.concurrent.*
import java.util.concurrent.locks.*
public c l ase At temptLocki ng {
prvate ReentrantLock l ock = new Reent rantLock()
pub l ic void untimed () {
bool ean captured = lock.tryLock{) ;
try (
System.out .print l n ( tltryLoc k () :
fina lly (
if (captured)
l ock. unlock() i
public voi d timed () {
bool ean captured = fal s
try {
+ cap tured} ;
captured = l oc k . t r yLock (2, TimeUni t.SECONDSJ;
catch(InterruptedExcepti on e l {
throw new Runt i meException (e) ;
)
t r y (
Sy stem. ou t . println (
fl
tryLock (2 , Ti meUni t . s ECONDS) : " +
capturedJ
fina lly (
if (captured)
l ock. unlock () i
public statie void maln (String [) a r gs) {
fina l AttemptLocking al = new AttemptLocking()
al. unt irned () ; /1 True -- el bloqueo est disponible
a l .timed() // True -- e l bloqueo e s t disponible
JI Ahora c rear una t area separada para e s t able cer e l bloqueo:
new Thread () {
( setDaemon (true ) ; }
public v oid run () {
al.lock . l ock () ;
Sys tem. out. print ln ( n acquired " ) ;
)
) ,st a r t () ;
Thre ad .yield () ; II Dar una oportunidad a l a segunda tarea
al.unt imed (} II Fa1se bl oque o adquirido por l a tarea
al.timed(); II False -- bloqueo adquirido por la tarea
1* Output:
tryLock () , t r ue
t ryLock (2, Ti meUnit.SECONDS ) : true
a cquired
t ryLock () , fals e
tryLock(2 , Ti meOni t . SECONDS) : false
* /// , -
21 Concurrencia 759
Un bloqueo ReentrantLock nos permite intentar adquirir el bloqueo sin xito por 10 que si alguien ms ha adquirido el blo-
queo, podemos decidir renunciar a adquirirlo por el momento y hacer alguna otra cosa mientras en lugar de esperar a que
se libere, como podemos ver en el mtodo ulllimed( ). En tlmed( ), se realiza un intento de adquirir el bloqueo que puede
fallar despus de 2 segundos (observe el uso de la clase TimeVolt de Java SES para especificar las unidades), En main(),
se crea un objeto Thread separado como una clase annima y ste adquiere el bloqueo para que los mtodos untimed( ) y
timed( ) tengan algo con lo que trabajar,
El objeto Lock explcito tambin nos proporciona un control de granularidad masiva sobre el bloqueo y el desbloqueo de
lo que peanite el bloqueo synchronized integrado. Esto resulta til para implementar estructuras de sincronizacin especia-
lizadas, tales como por ejemplo la de bloqueo mano a mano (tambin conocida como acoplamiento de bloqueos), utilizada
760 Piensa en Java
para recorrer los nodos de una lista enlazada, el cdigo que recorre la lista debe capturar el bloqueo del nodo siguiente antes
de liberar el bloqueo del nodo actual.
Atomicidad y volatilidad
Una idea incorrecta pero que se repite a menudo en las explicaciones sobre el mecanismo de hebras de Java es "las opera-
ciones atmicas no necesitan sincronizarse". Una operacin atmica es aquella que no puede ser interrumpida por el plani-
ficador de hebras: si la operacin se inicia, entonces continuar ejecutndose hasta completarse, sin que pueda producirse
un cambio de contexto durante el proceso. Confiar en la atomicidad resulta peligroso: slo debemos tratar de emplear la ato-
micidad en lugar de la sincronizacin si somos autnticos expertos en concurrencia o si disponemos de ayuda de uno de tales
expertos. Si considera que es lo suficientemente hbil como para jugar con fuego, haga esta prueba:
La Prueba de Goetz
ll
: si eres capaz de escribir una mquina NM de altas prestaciones para un nico
procesador moderno, entonces podrs comenzar a pensar si puedes ahorrarte la sincronizacin.
12
Resulta til saber acerca de la atomicidad, y saber tambin que sta se utiliz, junto con otras tcnicas avanzadas, para
implementar algunos de los componentes ms inteligentes de la biblioteca java.util.concurrent. Pero no caiga en la tenta-
cin de depender usted mismo de la atomicidad; tenga siempre presente la Regla de Brian de la sincronizacin, presentada
anteriormente.
La atomicidad se aplica a las "operaciones simples" sobre los tipos primitivos, excepto para valores long y double. Leer y
escribir variables primitivas de long y double es, de manera garantizada, una operacin de acceso hacia y desde la memo-
ria absolutamente indivisible (atmica). Sin embargo. la mquina NM est autorizada a realizar lecturas y escrituras de
valores de 64 bits (variables long y double) como dos operaciones de 32 bits separadas, haciendo surgir la posibilidad de
que se produzca un cambio de contexto en mitad de una lectura o escritura, con lo que diferentes tareas podran ver resul-
tados incorrectos (esto se denomina en ocasiones desgajamiento de palabra. porque existe la posibilidad de ver el valor des-
pus de que slo se haya cambiado una parte del mismo). Sin embargo, s que tenemos atomicidad (para asignaciones y
devoluciones simples) si utilizamos la palabra clave volatile al definir una variable long o double (observe que volatile no
funcionaba adecuadamente antes de Java SES). Las diferentes mquinas NM son libres de proporcionar garantias ms fuer-
tes, pero tenga en cuenta que no se debe confiar en las caractersticas que sean especficas de determinadas plataformas.
Por tanto, las operaciones atmicas no son interrumpibles por el mecanismo de gestin de hebras. Los programadores exper-
tos pueden aprovechar esto para escribir cdigo libre de bloqueos, que no necesita sincronizarse. Pero incluso esto es una
simplificacin excesiva. En ocasiones, an cuando parezca que una operacin atmica debera ser segura, puede que no lo
sea. Los lectores de este libro no podrn, normalmente, pasar la Prueba de Goetz antes mencionada, por lo que no deberian
pensar en sustituir la sincronizacin por operaciones atmicas. Tratar de eliminar la sincronizacin es realmente un signo de
optimizacin prematura, que generar una gran cantidad de problemas, probablemente, sin ganar nada a cambio, o muy
poco.
En los sistema multiprocesador (que ahora estn apareciendo en la forma de procesadores multincleo: mltiples procesa-
dores en un mismo chip), la visibilidad ms que la atomicidad suele ser mucho ms importante que en los sistemas de un
solo procesador. Los cambios realizados por una tarea, incluso si son atmicos en el sentido de que no son interrumpibles,
puede que no sean visibles para otras tareas (los cambios deben estar almacenados temporalmente en una cach del proce-
sador local, por ejemplo), de modo que diferentes tareas tendrn una visin distinta del estado de la aplicacin. El mecanis-
mo de sincronizacin, por el contrario, fuerza a que los cambios realizados por una tarea en un sistema multiprocesador sean
visibles para toda la aplicacin. Sin la sincronizacin no resulta posible determinar cundo sern visibles los cambios.
La palabra clave volatile tambin asegura la visibilidad para toda la aplicacin. Si declaramos que un campo es de tipo
volatile, esto quiere decir que, tan pronto como se realice una escritura en dicho campo, todas las lecturas podrn ver el cam-
bio. Esto es cierto incluso si se utilizan cachs locales: los campos voltiles se escriben inmediatamente en la memoria prin-
cipal y las lecturas se realizan tambin en la memoria principal.
11 Llamada as por el antes mencionado Brian Goetz, un experto en concurrencia que me ha ayudado a elaborar este captulo, que est parcialmente basa-
do en algunos comentarios que me hizo.
12 Un corolario de esta prueba es: "Si alguien sugiere que la gestin de hebras es sencilla, asegrate de que esa persona no tenga bajo sus responsabilida-
des el tomar decisiones importantes acerca del proyecto. Si esa persona est en disposicin de tomar esas decisiones, tienes un problema".
21 Concurrencia 761
Es importante entender que la atomicidad y la volatibilidad son conceptos distintos. Una operacin atmica en un campo no
voltil no ser necesariamente volcada en la memoria principal, por lo que otra tarea que lea dicho campo no tendra por
qu, necesariamente, ver el nuevo valor. Si hay mltiples tareas accediendo a un campo, dicho campo debe ser de tipo vol-
til; en caso contrario, slo debera accederse al campo utilizando la sincronizacin. La sincronizacin tambin provoca el
volcado en memoria principal, por lo que si un campo est completamente protegido por bloques o mtodos sincronizados,
no es necesario defmido como de tipo voltil.
Cualquier escritura que una tarea realice ser visible para dicha tarea, por lo que no es necesario un campo voltil si ese
campo slo es consultado dentro de una tarea.
La palabra volatile no funciona cuando el valor de un campo depende de su valor anterior (por ejemplo, el incrementar un
contador), ni tampoco funciona con aquellos campos cuyos valores estn restringidos por los valores de otros campos, como
por ejemplo, los lmites inferior (Iower) y superior (upper) de una clase Range (rango) que deba obedecer la restriccin
lower <= uppor.
Nonnalmente, slo resulta seguro volatile en lugar de sYllchronized si la clase slo tiene un campo modificable. De nuevo,
nuestra primera opcin debera ser emplear la palabra clave syncbronized; ste es el enfoque ms seguro e intentar hacer
cualquier otra cosa resulta arriesgado.
Qu cosas pueden llegar a ser operaciones atmicas? La asignacin y la devolucin del valor de un campo sern normal-
mente atmicas. Sin embargo, en e++ incluso las siguientes instrucciones podran ser atmicas:
i++ JI Podra ser atmica en c++
i += 2 /1 Podra ser atmica en c++
Pero en C++, esto depende del compilador y del procesador. No podemos escribir cdigo inter-platafonna en C++ que
dependa de la atomicidad, porque C++ no tiene un modelo de memoria coherente, a diferencia de Java (en Java SE5)13
En Java) las operaciones anteriores son, sin ninguna duda, no atmicas, como se puede ver analizando las instrucciones JVM
generadas por los siguientes mtodos:
1/ : concurrency/Atomici ty . j ava
11 {Exec , javap - e Atomicity}
public c l ass Atomi city
int i
voi d El () { i ++; }
void f 2 () { i +" 3;
1* Output , (Sample)
void fl () ;
Code,
,
1,
2,
5 ,
6 ,
7,
1 0 ,
voi d f2 () ;
Cede:
,
1,
2,
5 ,
6 ,
7,
10,
*111, -
aload O
dup
getfield
iconst 1
iadd
putfield
ret urn
a load O
dup
getfield
iconst 3
i add
putfield
return
#2; IICampo i,r
#2; II Campo i,r
#2; IICampo i,r
#2; IICampo i,r
13 Esto se est tratando de remediar en el siguiente estndar e++ que se va a publicar.
762 Piensa en Java
Cada instruccin produce una operacin "get" y una operacin "puf', con instrucciones entremedias. Por tanto, entre el ins-
tante de obtener el valor y el instante de almacenar el valor corregido, otra tarea podra modificar el campo, as que las ope-
raciones no son atmicas.
Si aplicamos ciegamente la idea de le atomicidad, podemos ver que getValue( ) eu el siguieute programa se ajusta a la des-
cripcin:
JJ: concurrencyJAtomicityTest .java
import java. ut i l . concurrent.*;
publ ic c l ass AtomicityTest i mp l ements Runnable {
private int i = O;
publ ic int getValue () { return i i }
private synchronized voi d evenlncrement() i++ i++
public void run () {
whi l e (true )
evenIncrement {)
public stat ic void main {St ring[] a rgs) {
ExecutorServi ce exec = Executors.newCachedThreadPool()
AtomicityTe st at = new AtomicityTest();
exec.execute(at ) ;
while (true ) {
i nt val = at.getValue() i
if(val % 2 1= O) {
Syst em. out .print ln (val)
System.exi t (O);
/* Output, (Samp1e)
1 9158376 7
* /// , -
Sin embargo, el programa encontrar valores no pares y terminar. Aunque return i es ciertamente una operacin atmica,
la falta de sincronizacin permite que el valor se lea mientras que el objeto se encuentra en un estado intennedio inestable.
Adems, puesto que i tambin es de tipo voltil, habr problemas de visibilidad. Tanto getValue( ) como evenlncrement( )
deben ser sincronizados. S610 los expertos en concurrencia deberan intentar realizar optimizaciones en situaciones como
sta. De nuevo, recuerde siempre la Regla de Brian de la sincronizacin.
Como segundo ejemplo vamos a considerar algo todava ms simple: una clase que produce nmeros de serie. 14 Cada vez
que se llama a uextSeriaINumber( ), debe devolver un valor unvoco alllamante:
JJ : concurrencyJSerialNumberGenerator.java
public class SerialNumber Generator {
pri vate static volatile i nt serial Number = O;
public sta tic int nextSerialNumber () {
return seri a lNumber ++ /J No es seguro con l as hebras
}
/ / / o-
SerialNumberGenerator es una clase de lo ms simple que podramos imaginar, y para cualquiera que proceda de C+t o
que tenga alguna otra experiencia previa similar de baj o nivel, cabra esperar que la de incremento fuera una operacin at-
mica, porque un incremento en C++ a menudo puede implementarse como una instruccin de microprocesador (aunque no
en una fomla fiable). Sin embargo, como hemos indicado antes, una operacin incremento en Java no es
atmica e implica tanto una lectura como una escritura, por lo que existen problemas con las hebras incluso en operaciones
tan simples como sta. Como veremos, el problema aqu uo es la volati lidad, el problema real es que nextSeriaINumber()
accede a un valor compartido y mutable sin sincronizacin.
14 Ejemplo inspirado en el libro EjJective JavaTMProgramming Language Guide de Joshua Bloch (Addison Wesley, 2001), p. 190.
21 Concurrencia 763
El campo serialNumber es voltil porque es posible que cada hebra tenga una pila local y mantenga en ella copias de algu-
nas variables. Si defInimos una variable como voltil, esto le dice al compilador que no realice ninguna optimizacin que
pudiera eliminar las lecturas y escrituras que mantienen al campo en sincronizacin exacta con los datos locales almacena-
dos en las hebras. En la prctica, las lecturas y escrituras se realizan directamente en memoria y no se almacenan valores en
cach. La palabra clave volatile tambin restringe la posibilidad de que el compilador realice reordenaciones de los accesos
durante la optimizacin. Sin embargo, volatile no afecta al hecho de que la de incremento no es una operacin atmica.
Bsicamente, debemos defmir un campo como volatile si hay varias tareas que pueden acceder a la vez a dicho campo y al
menos uno de esos accesos es una escritura. Por ejemplo, un campo que se utilice como indicador para detener una tarea
debe ser declarado como de tipo volatile; en caso contrario, dicho indicador podra estar almacenado en un registro de cach,
y cuando se hicieran cambios en el indicador desde fuera de la tarea, el valor de la cach no sera modificado y la tarea nunca
sabra que debe detenerse.
Para probar SerialNumberGenerator, necesitamos un conjunto que no se quede sin memoria, por si acaso se tarda un largo
tiempo en detectar el problema. El conjunto CircularSet mostrado aqu reutiliza la memoria usada para almacenar valores
enteros, en la suposicin de que para cuando volvamos al principio del conjunto, la posibilidad de una colisin con los valo-
res sobreescritos ser mnima. Los mtodos add() y contains() estn sincronizados para impedir las colisiones entre hebras:
11: concurrency/SerialNumberChecker.java
II Determinadas operaciones que pueden parecer seguras no lo son
II cuando hay hebras presentes.
II {Args, 4}
import java.util.concurrent.*;
II Reutiliza el almacenamiento para no agotar la memoria:
class CircularSet {
private int[] array;
private int len;
private int index = Di
public CircularSet(int size)
array = new int[size];
len = size;
II Inicializar con un valor no producido por
II el generador SerialNumberGenerator:
for(int i = O; i < sizei i++)
array [i] = ~ l
public synchronized void add(int i) {
array[index] = i
II Implementar circularmente el index y reescribir los elementos
II antiguos:
index = ++index % len;
public synchronized boolean contains(int val) {
for(int i = O; i < len; i++)
if(array[i] == val) return true;
return false
public class SerialNumberChecker {
private static final int SIZE = 10;
private static CircularSet serial s =
new CircularSet(lOOO);
private static ExecutorService exec =
Executors.newCachedThreadPool()
sta tic class SerialChecker implements Runnable {
public void run()
while (true) {
764 Piensa en Java
int serial
Ser ialNumberGenerator .next8erialNumber() ;
if (serials . contains (s erial {
System. out.println(uDuplicate: n + serial) i
System.exit(O) ;
serials .add (serial l i
public s tatic void main(String( ] args } throws Exc ept i on {
for (int i = O; i < SIZE; i++)
exec.execute(new SerialChecker( i
/1 Detenerse despus de n segundos si hay un argumento:
if(args.length> 01 {
TimeUni t .SECONDS .sleep(new Integer(args [O] ;
Syst em. out .println (liNo duplicates detected
ll
) i
System.exit (O) ;
/ * OUtput, (Sampl el
Dupl icate : 84 68656
*/// , -
SerialNumberChecker contiene un conjunto esttico CircularSet que almacena todos los nmeros de serie que se han pro-
ducido y una clase SerialChecker anidada que garantiza que los nmeros de serie sean univocos. Creando mltiples tareas
para contender con el acceso a los nm.eros de serie, descubriremos que las tareas llegan a obtener un nmero de serie dupli
cado, si dejamos que el programa se ejecute el tiempo suficiente. Para resolver el problema, aada la palabra clave synchro-
nized a nextSerialNumber( ).
Las operaciones atmicas que se supone que son seguras son las de lectura y asignacin de primitivas. Sin embargo, como
hemos visto en AtomicityTest.java, sigue siendo posible utilizar una operacin atmica que acceda a nuestro objeto, mien
tras que ste se encuentre en un estado intennedio inestable. Realizar suposiciones acerca de este tema resulta muy peligro-
so. Lo mejor que puede hacerse es aplicar la Regla de Brian de la sincronizacin.
Ejercicio 12: (3) Corrija AtomicityTest.java utilizando la palabra clave synchronized. Puede demostrar que ahora es
correcto?
EjercIcio 13: (1) Corrija SeriaINumberChecker.java utilizando la palabra clave synchronized. Puede demostrar que
ahora es correcto?
Clases atmicas
Java SE5 introduce clases de variables atmicas especiales como Atomiclnteger, AtomicLong, AtomicRefel'ence, etc.,
que proporcionan una operacin condicional de actualizacin atmica de la fonna:
boolean compareAndSet(expectedValue, updateVal ue ) ;
Este ejemplo de mtodo compararia y actualizaria en una operacin atmica una detenninada variable, proporcionndose
como argumentos el valor esperado y el valor de actualizacin. Este tipo de mtodo se utiliza para realizar una optimiza-
cin avanzada, aprovechando la atomicidad del nivel de la mquina disponible en algunos procesadores modernos, por lo
que generalmente no tenemos por qu preocupamos de utilizarlos. En ocasiones, pueden resultar tiles para los programas
nonnales, pero, de nuevo, slo cuando se est intentando realizar una optimizacin. Por ej emplo, podemos escribir de nuevo
AtomicityTest.java para utilizar AtomicInteger:
jj : concurrencyjAtomi c l ntegerTest.java
i mpor t j ava.util .concurrent .*;
import java.util.concurrent.atomic.*
import java. util.*
pubI ic cI as s AtomiclntegerTest implements RunnabIe {
priva te At omiclnteger i = new Atomiclnteger(Q) i
public int getValue() { return i.get() ; }
privat e voi d evenlncremen t () { i .addAndGe t (2) i }
public void run() (
whi l e ( true)
evenlncrement() i
public static void main(Stri ng[] args)
new Ti mer() . schedule (new TimerTask ()
publ ic void run() {
System. err .prin t ln ( uAbor ting" ) ;
System.exi t (Q) i
}, 5000); /1 Terminar despus de 5 segundos
Exe cutorService exec = Executors .newCachedThreadPool();
Atomi c lntegerTest ait = new AtomiclntegerTest();
exec. execute(ait) i
while (true) (
i nt val = ait.getValue( ) ;
if (val % 2 != O) (
System.out.pr i nt ln(val ) i
System.exit(O) i
21 Concurrencia 765
Aqui hemos eliminado la palabra clave synchronized utilizando en su lugar Atomiclntcgcr. Puesto que el programa no
falla, hemos aadido un objeto Timer para abortar automticamente la ejecucin despus de 5 segundos.
He aqu MutexEvenGenerator.java reescrito para utilizar Atomiclnleger:
JI : concurrency/AtomicEvenGenerator.java
/ 1 Las c lases atmi cas son tiles e n ocasiones en los programas normales .
II {RunByHand}
import java.util .concurrent.atomic . *
public cIass AtomicEv enGenerator extends IntGenerat or
privat e Atomiclnteger currentEvenValue
new Atomiclnteger{Q) ;
public int next () (
ret urn currentEvenValue . addAndGet(2);
public static voi d main (Stri ng [J args) {
EvenCh ecker.test( new AtomicEvenGenerator());
}
111 >
De nuevo, todas las dems formas de sincronizacin se han eliminado empleando Atomclnleger.
Es necesario recalcar que las clases Atomic fueron diseadas para construr las clases de java.util.concurrent, y que slo
debemos utilizarlas en nuestro propio cdigo en circunstancias especiales, e incluso en ese caso nicamente cuando poda-
mos garantizar que no van a surgir otros posibles problemas. Generalmente, es ms seguro utilizar los bloqueos (bien la
palabra clave synchronlzed o bien objetos Lock explcitos).
Ejercicio 14: (4) Demuestre que java.util.Tltner se puede escalar para ntilizar nmeros de gran magnitud creando lffi
programa que genere muchos objetos Timer que realicen alguna tarea simple cuando se produzca el fin
de temporizacin.
Secciones crticas
En ocasiones, lo nico que queremos evitar es que mltiples hebras accedan a parte del cdigo dentro de un mtodo, en lugar
de al mtodo completo. La seccin de cdigo que queramos aislar de esta manera se denomina seccin critica y se crea uti-
766 Piensa en Java
lizando la palabra clave synchronized. Aqu, syncbronized se utiliza para especificar el objeto cuyo bloqueo se est em-
pleando para sincronizar el cdigo incluido en la sincronizacin:
s ynchr onized (syncOb j ect ) {
JI En cada momento , s610 una tarea puede
// a cceder a este cdigo
Esto se denomina tambin bloqueo sincronizado; 10 que quiere decir que antes de poder entrar en esa seccin de cdigo, hay
que adquirir el bloqueo para syncObject. Si alguna otra tarea ha adquirido ya este bloqueo, entonces no podr entrarse en
la seccin crtica hasta que dicho bloqueo se libere.
El siguiente ejemplo compara ambas tcnicas de sincronizacin, demostrando que el tiempo disponible para que otras ta-
reas accedan a ur objeto se incrementa significativamente empleando un bloque synchronized en lugar de sincronizar el
mtodo completo. Adems, muestra cmo se puede utilizar una clase no protegida en entornos multibebra si se la controla
y protege mediante otra clase:
1/ : concurrency( Cr i t icalSect ion.j ava
/1 Sincronizacin de bloque en lugar de mtodos completos. Tambin
// ilustra la protecci n de una clase no protegida mediante
JI ot ra clase protegida.
package concurrencYi
i mport java.util.concurrent .*;
import java .ut i l. concurrent,at omic,*
import java.uti l.*
class Pair { II No protegida frente a hebras
private int x , y
publ i c Pair( i nt x, int y ) {
this.x Xi
this.y = y;
public Pair () { this (O, O) ; }
public int getX () { ret urn X'
publ i c int getY() { return y
pUblic void incrementX() { x++
public void incrementY{) { y++
p ublic String t oString () {
return 'IX: u + X + lO, y: " + y;
pubIic cIass PairValuesNotEqualException
extends RuntimeException {
public PairValuesNotEqualException ()
super ("Pair values not egual: 11 + Pair. thi s ) ;
II Invariante arbitrario
publ ic void checkState{)
if(x ! = y)
ambas vari ables deben ser iguales:
throw new PairVal uesNotEqualExcept i on ( ) ;
II Proteger un objeto pair dentro de una clase protegida f rente a hebras:
abstract class PairManage r {
Atomiclnteger c heckCounter = new AtomicInt eger (O);
protected Pair p = new pair()
private List <Pair> storage =
Col lections.synchronizedList(new ArrayLi st<Pair>());
publ ic synchronized Pair getPair () {
/1 Hacer una copia para que e l ori g inal est seguro:
return new Pair (p . getX (), p.gety(;
JI Asumimos que sta es una de larga duracin
protected void store(Pair p) {
storage . add (p) ;
t ry {
TimeUni t .MILLI SECONDS.sleep(SO) ;
} catch (InterruptedException ignore) {}
public abstract void increment()
JI Sincronizar e l mtodo completo:
c I ass PairManager l extends PairManager {
public s ynchronized void incr ement() {
p. incremen tX () ;
p. i ncr ement y () ;
store(getPair (
JI Uti lizar una seccin crtica:
cIass PairManager2 extends PairManager
public voi d increment() {
pair temp
synchronized(this)
p. incrementX () ;
p.incrementy( } ;
temp = getPair()
s t ore (temp ) ;
cIass PairManipulator mplements Runnable {
pr vate PairManager pm;
public PairManipulator(PairManager pm)
this .pm = pm,
publi c void run ()
whi le (true)
pm. increment () ;
public String toString()
return "Pair: I! + pm.getPair() +
1I c hec kCount er = I! + pm. c heckCounter . get () ;
}
c l ass pairChecker i mplement s Runnable {
private PairManager pm;
public PairChecker(PairManager pm) {
this.pm = pm;
public voi d run ()
while (true) {
pm.checkCounter.incrementAndGet() ;
pm.getPair() .checkState() j
21 Concurrencia 767
768 Piensa en Java
publ ic clasa CriticalSection {
II Probar las dos tcnicas:
s t atic void
testApproaches(PairManager pmanl, PairManager pman2) {
ExecutorService exec Executors .newCachedThreadPool ();
Pai r Manipulator
pm1 = new PairManipulator(pmanl),
pm2 new PairManipulator (pman2 l ;
pairChecker
pcheckl new Pa irChecker(pmanl ),
pcheck2 new PairChecker {pman2)
exec.execut e (pml ) ;
exec.execuce(pm2) ;
exec.execute(pcheck1) ;
exec.execute(pcheck2) ;
t ry (
TimeUn i t .MILLISECONDS.sleep (500) i
catch(InterruptedExcepti on el {
System.out.println( nSl eep interrupted
n
) i
System. out. println (npm1: 11 .,. pro1 .,. n \npm2 : n .,. pm2);
System.exit(O) i
public static void main (String [] args) {
PairManager
pman1 new PairManager1() ,
pman2 new ParManager2 () ;
tes t Approaches(pmanl, pman2);
1* Out put: (Sample)
prol: par: x: 15, y: 15 checkCounter
pm2 : pair: x: 16, y: 16 checkCounter
*jjj, -
272565
3956974
Como se indica, Pair no es seguro de cara a las hebras, porque su invariante (o porque su invariante es arbitrario) requiere
que ambas variables mantengan los mismos valores. Adems, como hemos visto anteriOlTI1ente en el captulo, las operacio-
nes de incremento no son seguras respecto a las hebras, y como ninguno de los mtodos est sincronizado, no podemos con-
fiar en que un objeto Pair pennanezca sin corromperse dentro de un programa con hebras.
Imagine que alguien nos entrega la clase Pair no protegida frente a hebras, y que necesitamos utilizarla en un entorno de
hebras. Para hacer esto, creamos la clase PairManager, que almacena un objeto Pair y controla todo el acceso al mismo.
Observe que los nicos mtodos pblicos son getPair( l, que est sincronizado y el mtodo abstracto increment( l. La sin-
cronizacin de increment( l se gestionar cuando se lo implemente.
La estructura de PairManager, en la que la funcionalidad implementada en la clase base utiliza uno o ms mtodos abs-
tractos definidos en las clases derivadas se denomina Mtodo de plantillas en la jerga de los Patrones de diseo.
15
Los patro-
nes de di seo nos penniten encapsular los cambios que nuestro cdigo pueda sufrir; aqu, la parte que cambia es el mtodo
illcrement( l. En PairManagerl todo el mtodo increment( l est sincronizado, mientras que en PairManager2 slo se
sincroniza parte del mtodo increment( l utilizando un bloque syncbronized. Observe que la palabra clave syncbronized
no fonna parte de la signatura del mtodo y que, por tanto, puede ser aadida al sustituirlo en una clase derivada.
El mtodo store( l aade un objeto Pair a un contenedor ArrayList sincronizado, por lo que esta operacin es segura con
respecto a las hebras. Por tanto, no es necesario protegerla, y se la coloca fuera del bloque syncbronized en PairManager2.
15 Vese Design Pattems, por Gamma el al. (Addison-Wesley, 1995).
21 Concurrencia 769
PairManipulator se crea para probar los dos diferentes tipos de objetos PairManager, invocando increment() en una
tarea mientras se ejecuta la comprobacin PairChecker desde otra tarea. Para ver con qu frecuencia es capaz de ejecutar
la proeba, PairChecker incrementa checkCounter cada vez que tiene xito. En main(), se crean dos objetos Pair-
Manipulator y se les permite ejecutarse durante un tiempo, despus de lo cual se muestran los resultados de cada objeto
PairManipulator.
Aunque probablemente pueda percibir una gran variacin a la salida entre una ejecucin del programa y la siguiente, en
general ver que PairManagerl.increment() no permite a PairChecker unos accesos tan frecuentes como a Pair-
Manager2.increment( ), que tiene el bloque synchronized y goza, por taoto, de un mayor tiempo con los bloqueos libera-
dos. sta es, normalmente, la razn para utilizar un bloque syncbronized en lugar de la sincronizacin del mtodo completo:
para permitir que las otras tareas puedan acceder ms frecuentemente (siempre y cuando sea seguro hacerlo).
Tambin se pueden usar objetos Lock explcitos para crear secciones crticas:
JI: concurrency/Explicit CriticalSection.java
JI USO de objetos Lock expl citos para crear secci ones crticas.
package concurrencYi
import java.ut il.concur rent.locks.*;
// Si ncronizar e l mtodo completo:
c l ass Explic itPairManagerl extends Pai rManager
pr ivate Lock l ock = new Reent ran tLock ()
public synchronized void increment ( ) {
lock .lock () ;
try (
p.incrementx()
p.incrementY()
store {ge t Pair ()) i
Einally (
lock.unl ock () ;
// Usar una seccin crtica:
cIas s ExpIicit PairManager2 extends PairManager
pri vat e Lock Iock = new ReentrantLock(l
pubI ic void i ncrement () {
pair t empi
lock .lock () ;
try (
p. incrementX () i
p. incrementY () ;
temp = getPair() i
finally (
l ock . unlock( ) ;
store (temp )
public cIass ExplicitCritica lSection {
publ i c stat ic void main( String [] args ) throws Exception {
PairManager
pmanl = new ExplicitPairManagerl () ,
pman2 = new Explicit Pa i rManager2 ()
Critic alSect ion.testApproaches (pmanl, pman2 ) ;
/* Output, (Sampl e)
pm1: pai r : x: 15, y : 15 checkCounter
pm2: pair: x : 16, y: 16 checkCounter
*///,-
174035
26 08588
770 Piensa en Java
Este programa reutiliza la mayor parte de CriticalSection.java y crea nuevos tipos de PairManager que utilizan objetos
Lock explcitos. ExplcitPairManager2 muestra la creacin de una seccin critica mediante un objeto Lock; la llamada a
store( ) se encuentra fuera de la seccin crtica.
Sincronizacin sobre otros objetos
A un bloque synchronized hay que proporcionarle un objeto con el que sincronizarse, y normalmente el objeto ms apro-
piado para este propsito es el objeto actual para el que est siendo invocado el mtodo: synchronized(this), que es la tc-
nica usada en PairManager2. De esta fonna, cuando se adquiere el bloqueo para el bloque synchronized, no se pueden
invocar otros mtodos synchronized y secciones crticas del objeto. Por tanto, el efecto de la seccin crtica, al sincronizar-
se sobre this, consiste simplemente en reducir el mbito de sincronizacin.
En ocasiones, es necesario sincronizarse con otro objeto, pero si hacemos esto debemos garantizar que todas las tareas rele-
vantes se sincronicen con el mismo objeto. El siguiente ejemplo demuestra que dos tareas pueden entrar en un objeto si los
mtodos de dicho objeto se sincronizan con bloqueos diferentes:
JI : concurrency/SyncObject.java
II Sincronizacin con otro objeto.
i mport static net .mindview.ut i l.Print .*
cIass DualSynch {
private Object syncObject = new Object ();
pubIi c synchronized void f () {
for(int i = O; i < 5; i++ ) {
print (n f () ") ;
Thread. yield () i
public void 9 () {
synchronized (syncObject )
for(int i = O; i < S; i++ )
print (119 () 11) i
Thread. yield 1) ;
public class SyncObject {
public stat i c void main(String[] args) {
final DualSynch ds = new DualSynch() i
new Threadl) {
public void run() {
dS.f() ;
)
) . start 1) ;
ds.g l) ;
) /* Output, ISample)
gl)
fl)
g il
fl)
g ()
f ()
gl )
fl)
g()
f()
*///,-
21 Concurrencia 771
DuaISync.f() se sincroniza con Ihis (sincronizando el mtodo completo), y g() tiene un bloque synchronized que se sin-
croniza con syncObject. As, las dos sincronizaciones son independientes. Esto se iluslra en main() creando un objeto
Thread que invoca f( ). La hebra en main( ) se utiliza para llamar a g( ). Podemos ver, analizando la salida, que ambos
mtodos se ejecutan al mismo tiempo, as que ninguno de ellos est bloqueado por la sincronizacin del otro.
Ejercicio 15: (1) Cree una clase con tres mtodos que contengan secciones crticas, y que todas se sincronicen con el
mismo objeto. Cree mltiples tareas para demostrar que slo uno de estos mtodos puede ejecutarse cada
vez. Ahora, modifique los mtodos de modo que cada uno de ellos se sincronice con un objeto distinto y
muestre que los tres mtodos pueden ejecutarse simultneamente.
Ejercicio 16: (1) Modifique el Ejercicio 15 para usar objetos Lock explcitos.
Almacenamiento local de las hebras
Una segunda forma de evitar que las tareas colisionen o accedan a recursos compartidos consiste en eliminar la comparti-
cin de variables. El almacenamiento local de las hebras es un mecanismo que crea automticamente un espacio de alma-
cenamiento distinto para la misma varable, para cada hebra diferente que est utilizando un objeto. As, si tenemos cinco
hebras utilizando un objeto con una varable x, el almacenamiento local de las hebras genera cinco espacios diferentes de
almacenamiento para x. Bsicamente, este mecanismo nos pennite asociar un cierto estado con cada hebra.
La creacin y gestin del almacenamiento local de la hebra son realizados por la clase java.Iang.ThreadLocal, como puede
verse aqu:
jI : concurrency/ThreadLocalVariableHolder.java
II Asignaci n automti ca de su propio espacio
JI de almacenami ento a cada hebra.
i mport java. ut i l.concurrent.*i
i mport java . util.*i
c l ass Accessor implements Runnable {
private final int id
public Acces sor (int idn) { id "" idn
public void run() {
while(IThread.currentThread () .islnterrupt ed(
ThreadLocaI Variabl eHolder.increment ()
System.out.println(this) i
Thread. yield () ;
public String toString () {
return n#" """ id + 11: n +
ThreadLocaIVariableHolder.get()
public class ThreadLocalVariableHolder {
private static ThreadLocal <Integer> value
new ThreadLocal<Integer>{) {
private Random rand = new Random(47);
protec ted synchronized I nteger initialVal ue()
}
} ;
return rand . nextlnt (l OOOO)
public static void i ncrement ()
value. s et {val ue.getO + 1) i
public static int get() return value.get(); }
public stat i c void main{String[J args) throws Exception
ExecutorService exec = Executors.newCachedThreadPool()
772 Piensa en Java
/ *
#0,
#1,
#2,
#3,
#4,
#0,
#1,
#2,
#3,
#4,
for(int i = O; i < 5; i++}
exec.execute(new Ac cessor(i});
TimeUnit.SECONDS.sleep(3) II Ejecutar durante un tiempo
exec.shutdownNow() II Todos los objetos AcceS$ors terminarn
Output: (Sample)
9259
556
6694
1862
962
9260
557
6695
1863
963
*///, -
Los objetos ThreadLocal normalmente se almacenan como campos estticos. Cuando creamos el objeto TbreadLocal, slo
podemos acceder al contenido del objeto con los mtodos get() y set( ). El mtodo get( ) devuelve una copia del objeto aso-
ciado con dicha hebra, mientras que set( ) inserta su argumento en el objeto almacenado para dicha hebra, devolviendo el
objeto anterior que estuviera almacenado. Los mtodos increment() y get() ilustran este mecanismo en TbreadLocal-
VariableHolder. Observe que incremcnt( ) y get( ) no estn sincronizados, porque ThreadLocal garantiza que no se pro-
duzca ninguna condicin de carrera.
Cuando ejecute este programa, podr ver que a cada hebra individual se le asigna su propio espacio de ahnacenamiento, ya
que cada una de ellas mantiene su propio valor de recuento, an cuando slo existe un objeto ThreadLocalVariableHolder.
Terminacin de tareas
En algunos de los ejemplos anteriores, los mtodos cancel( ) e isCanceled( ) se colocan en una clase que es visible para
todas las tareas. Las tareas comprueban isCanceled( ) para determinar cundo deben fmalizar. Esta solucin resulta bastan-
te razonable. Sin embargo. en algunas situaciones, la tarea debe terminarse de manera ms abrupta. En esta seccin, vere-
mos qu problemas existen con respecto a dicha finalizacin.
En primer lugar, veamos un ejemplo que no slo ilustra el problema de la terminacin, sino que tambin es un ejemplo adi-
cional de la comparticin de recursos.
El jardn ornamental
En esta simulacin, el comit director del jardn quiere saber cuntas personas entran el jardn cada da a travs de las dis-
tintas puertas. Cada puerta tiene un tomo o algn otro tipo de contador, y despus de incrementar el contador del tomo, se
incrementa una cuenta compartida que representa el nmero total de personas que hay en el jardn.
11: concurrencylOrnamentalGarden.java
i mport java.util. concurrent.*;
import java .ut i l. *
i mpo r t static net.mindview.ut i l .Print.*
clas s Count {
private int c ount = Di
private Random rand = new Random (47);
II Elimi nar la palabra clave synchron ized para ver cmo falla el recuento:
public synchronized int increment () {
int temp = count
if(rand.nextBool ean( )) II Seguir el control la mitad de l tiempo
Thread.yield()
return (count = ++temp)
public synchronized int value() { return count }
class Entrance implements Runnable {
prvate static Count count = new Count();
prvate static List<Entrance> entrances =
new ArrayList<Entrance>() i
prvate int number = O;
ji No necesita sincronizacin para leer:
prvate final int id;
prvate static volatile boolean canceled = false;
// Operacin atmica sobre un campo voltil:
public static void cancel () { canceled = true; }
public Entrance(int id) {
this.id = id:
// Mantener esta tarea en una lista. Tambin impide
jI que se aplique la depuracin de memoria a las tareas muertas:
entrances.add(this) i
public void run() {
while ( ! canceled) {
synchronized(this)
++number;
print (this + !I Total: 11 + count. increment () ) ;
try {
TimeUnit.MILLISECONDS.sleep(lOO) ;
catch(InterruptedException e) {
print (" sleep interrupted!l);
print("Stopping + this);
public synchronized int getValue() { return number;
public String toString()
return "Entrance " + id + !I: !I + getValue () ;
public static int getTotalCount()
return count.value();
public static int sumEntrances{)
int sum = O;
for(Entrance entrance : entrances)
sum += entrance.getValue{);
return sum;
public class OrnamentalGarden {
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
for{int i = O; i < 5; i++)
exec.execute(new Entrance(i));
21 Concurrencia 773
/1 Ejecutar durante un tiempo, luego detenerse y recopilar los datos:
TimeUnit.SECONDS.sleep(3) ;
Entrance.cancel() i
exec.shutdown{) i
774 Piensa en Java
if(! exec.awaitTermination(250, TimeUnit.MILLISECONDS))
pri nt (1' Sorne tasks were not terminated! n) ;
print ("Total : 11 + Entrance .getTotal Count ( ) ) i
print("Sum of Entrances: 11 + Entrance . s umEntrances( ) ;
/ *
Output: (Sample)
Entrance
O, 1 Total: 1
Entrance 2, 1 Total: 3
Entrance
1, 1 To tal: 2
Entrance 4, 1 Total : 5
Entrance 3, 1 Total : 4
Bn trance 2, 2 Total: 6
Entrance 4, 2 Tot a l : 7
Entrance O, 2 Total: 8
Entrance 3, 29 Total : 143
Entrance O, 29 Total: 144
Entrance 4, 29 Tot al: 145
Entrance 2,
30 Total: 147
Entrance 1, 30 Total : 146
Entrance O, 30 Tota l: 149
Entrance 3, 30 Tot al : 148
Entra nce 4, 30 Total : 15 0
Stopping Entrance 2,
30
Stopping Entrance 1, 30
Stopping Entrance O, 30
Stoppi ng Entrance 3, 30
Stopping Entrance 4, 30
Total: 1 50
Sum oE Entrances: 150
* /// ,-
Un nico objeto Couot mantiene la cuenta maestra de los visitantes del jardn y se almacena como campo esttico en la
clase Entrance. Count.increment( ) y Couot. value( ) estn sincrooizados para controlar el acceso al campo count. El
mtodo incremente ) utiliza un objeto Random para ejecutar el mtodo yield( ) aproximadanaente la mitad de las veces,
entre la operacio de extraer count para almacenarlo en temp y la de incrementar y almacenar de nuevo temp en conn!. Si
desactivamos mediante un comentario la palabra clave syochronized en increment(), el programa deja de funcionar por-
que habr mltiples tareas accediendo a couot modificndolo simultneamente (el mtodo yield( ) hace que el problema se
produzca ms rpidamente).
Cada tarea Entrance (que representa una de las entradas del jardn) mantiene un valor localnumber que contiene el nme-
ro de visitantes que han pasado a travs de esa entrada concreta. Esto proporciona una forma de verificar el objeto count,
para comprobar que est registrando el nmero correcto de visitantes. Entrance.run() simplemente incrementa number y
el objeto count y pasa a dormir durante 100 milisegundos.
Puesto que Entranee.caneelOO es un indicador booleano voltil, que slo se lee y se configura (y que nunca es ledo en com-
binacin con otros campos), seria posible no sincronizar el acceso al mismo. Pero, siempre que tenga alguna duda acerca de
si sincronizar algo o DO, lo mejor es utilizar synchronized.
Este programa hace un esfuerzo especial para [malizar todas las cosas de una manera estable. En parte, la razn de hacer
esto es ilustrar lo cuidadoso que hay que ser a la hora de terminar un programa multibebra; por otro lado, pretendemos con
eUo ilustrar el valor de interrupt( ), del que hablaremos en breve.
Despus de 3 segundos, maine ) enva el mensaje esttico cancele ) a Entranee, de modo que Uama a shutdown( ) para el
objeto exec, y luego invoca a awaiffermioation( ) sobre exec. ExecutorServce.awaitTermination( ) espera a que cada
tarea se complete y si todo se completa antes de que finalice la temporizacin devuelve true, en caso contrario, devuelve
false para indicar que no se han completado todas las tareas. Aunque esto hace que cada tarea salga de su mtodo run( ) y
terminen por tanto como tarea, los objetos Entrance seguirn siendo vlidos, porque en el constructor cada objeto Entrance
est almacenado en un contenedor esttico List<Entrance> denom..inado entran ces. Por tanto, sumEntrances( ) seguir
funcionando con objetos Entranee vlidos.
21 Concurrencia 775
A medida que se ejecuta este programa, podemos ver cmo se muestran el recuento total y el recuento correspondiente a
cada entrada a medida que las personas pasan por los tomos. Si eliminamos la declaracin synchronized de Count.incre-
ment( ), observaremos que el nmero total de personas no es el esperado. El nmero de personas contabilizadas por cada
tomo ser distinto del valor almacenado en couot. Mientras utilicemos el mutex para sincronizar el acceso a Count, las
cosas sucedern correctamente. Recuerde que Count.increment( ) exagera la probabilidad de fallos, utilizando temp y
yield( ). En los problemas reales de los programas multihebra, la posibilidad de fallo puede ser estadsticamente pequea,
as que podemos caer fcilmente en la trampa de pensar que las cosas estn funcionando correctamente. Al igual que en el
ejemplo anterior, resulta posible que existan problemas ocultos que no se nos hayan ocurrido, por 10 que debe tratar de ser
lo ms diligente posible a la hora de revisar el cdigo concurrente.
Ejercicio 17: (2) Cree un contador de radiaciones que pueda tener cualquier nmero de sensores remotos.
Terminacin durante el bloqueo
El mtodo Entrance.run( ) del ejemplo anterior iocluye una llamada a sleep( ) dentro de su bucle. Sabemos que sleep( )
termina por despertarse y que la tarea alcanzar la parte superior del bucle donde tendr la oportuoidad de salir del mismo,
comprobando el indicador cancelled. Sin embargo, sleep( ) es simplemente una de las situaciones en las que la ejecucin
de una tarea puede quedar bloqueada, y existen ocasiones en las que es necesario terminar una tarea bloqueada.
Estados de las hebras
Una hebra puede estar en uno de cuatro estados:
1. Nueva: una hebra permanece en este estado slo momentneamente, mientras se la est creando, se asignan los
recursos del sistema necesarios y se realiza la inicializacin. Despus de esto, la hebra pasa a ser elegible para
recibir tiempo de procesador. El planificador har entonces que la hebra efecte una transicin a los estados eje-
cutable o bloqueado.
2. Ejecutable: significa que una hebra puede ejecutarse cuando el mecanismo de gestin de franjas temporales tenga
ciclos de procesador para esa hebra. As, la hebra puede o no estarse ejecutando en cualquier momento dado, pero
no hay nada que la impida ejecutarse si el planificador as 10 decide. Por tanto, la hebra no est ni muerta ni blo-
queada.
3. Bloqueada: la hebra puede ejecutarse pero hay algo que 10 impide. Mientras que una hebra se encuentra en el
estado bloqueado, el planificador simplemente la ignorar y no la asignar tiempo de procesador, Hasta que una
hebra no vuelva a entrar en el estado ejecutable, no realizar ninguna operacin.
4. Muerta: una hebra en el estado muerto o terminado ya no es tenida en cuenta por el planificador y no puede reci-
bir tiempo de procesador. Su tarea se ha completado y la hebra ya no es ejecutable. Una forma de que una tarea
muera es volviendo de su mtodo run( ), pero la hebra de una tarea tambin puede interrumpirse como veremos
en breve.
Formas de bloquearse
Una tarea puede llegar a estar bloqueada por las signientes razones:
Hemos mandado la tarea a dormir invocando sleep(milliseconds), en cuyo caso no podr ejecutarse durante el
tiempo especificado.
Hemos suspendido la ejecucin de la hebra con wait(). No volver a ser ejecutable de nuevo hasta que la hebra
reciba el mensaje notify( ) o notifyAll( ) (o los mensajes equivalentes signal( ) o signalAll() para las herramien-
tas de la biblioteca de Java SE5 java.util.concurrent). Examinaremos este caso en una seccin posterior.
La tarea est esperando a que se complete una operacin de E/S.
La tarea est tratando de invocar un mtodo synchronized sobre otro objeto y el bloqueo no est disponible por-
que ya ha sido adquirido por otra tarea.
En los programas antignos, puede que tambin vea que se usan los mtodos suspende ) y resume( ) para bloquear y des-
bloquear las hebras, pero estos mtodos son desaconsejados en los programas Java modernos (porque son proclives a los
776 Piensa en Java
interbloqueos), as que no los examinaremos en el libro. Tambin se desaconseja el mtodo stop( ), porque no libera los blo-
queos que la hebra haya adquirido, y si los objetos estn en un estado incoherente ("daados"), otras tareas podran consul-
tarlos y modificarlos en dicho estado. Los problemas resultantes pueden ser muy sutiles y dificiles de detectar.
El problema que ahora debemos examinar es el siguiente: en ocasiones, queremos terminar una tarea que se encuentra en el
estado bloqueado. Si no podemos esperar a que la hebra alcance un punto en el cdigo en el que ella misma pueda compro-
bar un valor de estado y decidir tenninar por cuenta propia, tenernos que forzar a que la tarea salga de su estado bloqueado.
Interrupcin
Como puede imaginarse, resulta mucho ms complicado salir en mitad de un mtodo Runnable.run( ) que esperar a que
ese mtodo alcance un pWlto donde se compruebe un indicador de "cancelacin", o algn otro lugar en el que el programa-
dor est listo para abandonar el mtodo. Cuando salimos abruptamente de una tarea bloqueada, puede que tengamos que
efectuar tareas de limpieza de los recursos. Debido a esto, salir en mitad del mtodo run( ) de una tarea se parece ms a
generar una excepcin que a ninguna otra cosa, por lo que en las hebras Java se utilizan las excepciones para este tipo de
interrupcin
16
(esto significa que estamos caminando sobre el filo que separa el uso adecuado e inadecuado de las excep-
ciones, porque quiere decir que a menudo se las emplea para control del flujo del ejecucin). Para volver a un estado acep-
table conocido cuando se termina una tarea de esta forma, es necesario analizar con cuidado los caminos de ejecucin del
cdigo y escribir la clusula catch para limpiar adecuadamente las cosas.
Para poder tenninar una tarea bloqueada, la clase Thread contiene el mtodo interrupt( ). Este mtodo activa el indicador
de estado interrumpido para la hebra. Una hebra que tenga activado el indicador de estado interrumpido generar una inte-
rrupcin InterruptedException si ya est bloqueada o si se trata de realizar una operacin de bloqueo. El indicador de esta-
do interrumpido ser reinicializado cuando se genere la excepcin o si la tarea llama a Thread.interrupted( ). Como puede
ver, Thread.interrupted() proporciona una segunda forma de abandonar el bucle run( ), sin generar una excepcin.
Para invocar interrupt( ), es necesario disponer de un objeto Thread. Puede que el lector haya observado que la nueva
biblioteca concurrent parece evitar la manipulacin directa de objetos Thread y trata en su lugar de realizar todas las ta-
reas a travs de objetos Executors. Si llamamos a shutdownNow( ) sobre un objeto Executor, se generar una llamada
interrupt( ) dirigida a cada una de las hebras que el ejecutor haya iniciado. Esto tiene bastante sentido, porque norrnahnen-
te querremos terminar de una sola vez todas las tareas para Wl ejecutor concreto, cuando hayamos finalizado parte de un
programa o el programa completo. Sin embargo, hay veces en las que puede que slo queramos interrumpir una nica tarea.
Si estamos usando ejecutores, podemos obtener informacin sobre el contexto de una tarea en el momento de iniciarla lla-
mando a submit() en lugar de a execute(). submit() devuelve un genrico Future<?>, con un parmetro no especificado
(porque nunca se va a llamar get( ) para ese parmetro); el motivo de guardar este tipo de objeto Future es que se puede
invocar cancel( ) sobre el objeto y usarlo para interrumpir una tarea completa. Si pasamos el valor true a cancel( ), esta
hebra tendr permiso para invocar interrupt( ) sobre dicha hebra, con el fin de detenerla; por tanto cancel( ) es una forma
de interrumpir hebras individuales iniciadas mediante un ejecutor.
He aqu un ejemplo que muestra los fundamentos bsicos de utilizacin de interrupt( ) empleando ejecutores:
11: concurrency/Interrupting.java
II Interrupcin de una hebra bloqueada.
import java.util.concurrent.*
import java.io.*
import static net.mindview.util.Print.*;
class SleepBlocked implements Runnable {
public void run (l {
try {
TimeUnit.SECONDS.sleep(lOQl i
catch(InterruptedException el
print (n InterruptedException]l 1 i
16 Sin embargo, las excepciones nunca se generan asncronamente. Por tanto, no hay ningn riesgo de que algo se interrumpa en mitad de la ejecucin de
una instruccin o de una llamada a mtodo. Y, siempre que utilicemos la estructura al utilizar objetos mutex (en lugar de la palabra clave
synchronized), esos mutex sern liberados automticamente si se genera una excepcin.
pri nt ("Exiting SleepBlocked.run()11) i
class I OBlocked implements Runnable {
priva te InputStream in;
public I OBl ocked (InputStream is) { i n
public void run() {
try {
print ( IIWaiting for read {) : 11 ) ;
in.readO;
is; }
catch( I OException e l {
if (Thread.currentThread () .islnterrupted (
print (" I nterrupted frem b l ocked l / O" ) ;
el se {
t hrow new RuntimeException{e ) ;
print{"Exiting I OBl ocked.runO 11) i
cl ase SynchronizedBlocked impl ements Runnable
public synchronized void f () {
whi l e{true) // Nunca libera el bloqueo
Thread .yield() ;
public SynchronizedBlocked()
new Thread () {
public void run ()
f(); JI Bl oqueo adquirido por esta hebra
)
) . start () ;
public void run ()
pri nt ( "Trying ta call f () 11) i
f () ;
print ( !lExiting SynchronizedBlocked. run () ") ;
public c l ass Interrupting {
private static ExecutorService exec
Executors.newCachedThreadPool{) i
s tati c void test{Runnable r) throws InterruptedException{
Fut ure<?> f = exec . submit (r);
Ti meUnit.MILLI SBCONDS.sleep (lOO) ;
print ( II Interrupting " + r.getClass () .getName());
f .cancel(true) II Interrumpe si se est ejecutando
print ( " Interrupt sent to " + r. getCl ass () . get Name () ) ;
public static void main (String [} args) throws Exception
test(new SleepBlocked());
test(new IOBlocked (System.in)) i
t e st(new SynchronizedBlocked());
TimeUnit .SECONDS.sleep(3) ;
print( IIAbort ing with System. exit(Ol 11) i
21 Concurrencia 777
System.exit(O) i II ... puesto que l as 2 ltimas interrupciones fallaron
778 Piensa en Java
1* Ou tpu t: (95% match)
Interrupting SleepBl ocked
Inte rruptedException
Exiting SleepBlocked.run()
Int errupt sent to SleepBlocked
Waiting for read() :
Interrupting IOBlocked
Interrupt sent ta IOBlocked
Trying to call fl)
Interrupting SynchronizedBlocked
Interrupt sent ta SynchronizedBlocked
Abor t ing wit h System.exit(O)
*/// , .
Cada tarea representa un tipo diferente de bloqueo. SleepBlock es un ejemplo de bloqueo intel11lDlpible, mientras que
IOBlocked y SynchronizedBlocked son bloqueos no interrumpibles. 17 El programa demuestra que las operaciones de E/S
y de espera sobre un bloqueo synchronized no sean interrumpibles, cosa que tambin puede deducirse examinando el cdi-
go: no se requiere ninguna rutina de tratamiento de InterruptedException ni para la E/S ni para los intentos de invocar un
mtodo sincronizado.
Las dos primeras clases son bastante simples: el mtodo run( ) llama a sleep( ) en la primera clase y a read( ) en la segun-
da. Sin embargo, para ilustrar SynchronizedBlocked, debemos primero adquirir el bloqueo. Esto se lleva a cabo en el cons-
tructor creando una instancia de una clase Thread annima que adquiere el bloqueo del objeto invocando f() (la hebra debe
ser diferente de aquella que est dirigiendo run( ) para SynchronizedBlock, porque una misma hebra si que puede adqui-
rir mltiples veces un bloqueo sobre un objeto). Dado que fO nunca vuelve, ese bloqueo nunca es liberado.
SynchronizedBlock.TUn( ) trata de invocar f( ) Y se queda bloqueado esperando a que el bloqueo se libere.
Podemos ver, analizando la salida, que se puede interrumpir una llamada a sleep( ) (o cualquier llamada que requiera que
capturemos InterruptedException). Sin embargo, no se puede interrumpir una tarea que est tratando de adquirir un blo-
queo sincronizado o que est tratando de efectuar una operacin de E/S. Esto es un poco desconcertante, especiahnente si
estamos creando una tarea que realice operaciones de E/S, porque significa que la E/S tiene el potencial de bloquear el pro-
grama multihebra. Obviamente esto constituira un autntico problema, especialmente para programas basados en la Web.
Una solucin un tanto drstica pero en ocasiones bastante efectiva para este problema, consiste en cerrar el recurso subya-
cente que hace que la tarea est bloqueada:
JI : concurrencyj CloseResource. j ava
// I nterrupci n de una tarea bloqueada
/ / cerrando el recurso subyacente.
/ / {RunByHand}
import java.net.*
import java. util.concurrent.*;
import java.io.*;
import static net .mindview.ut il.print. *
public c l ass CloseResource {
publi c static void main (String [J args ) throws Exception {
ExecutorServi ce exec = Executors.newCachedThreadPool () ;
ServerSocke t server = new ServerSocket(808 0)
I nput Stream socket lnput =
new Socket ( lIlocalhost
ll
, 8080) . get InputSt r eam() ;
exec .execute {new IOBlocked (s ocket l nput ))
exec.execute {new IOBlocked (System.in);
TimeUnit.MILLISECONDS.sleep{lOO) i
print ( IIShutting down all threads!!) i
exec.shutdownNow() ;
17 Algunas versiones del JDK tambin proporcionaban soporte para la instruccin InterruptedIOException. Sin embargo, este soporte s610 estaba par-
cialmente implementado, y nicamente en algunas plataformas. Si se genera esta excepcin hace que los objetos de EIS sean inutilizables. Resulta poco
probable que las futuras versiones sigan proporcionando soporte para esta excepcin.
TimeUni t.SBCONDS .sl eep ( l )
print ("Cl osing 11 + socket l nput . getClass () . getName () ) i
socketlnput.close() ; 1/ Libera la hebr a bloqueada
TimeUnit .SECONDS.sleep (l) ;
print ( "Closing 11 + System. in.getClass () .getName () ) i
System. in.close(); /1 Libera la hebra b l oqueada
1* Output , (85% match)
Waiting for read() :
Waiting for read() :
Shutting down all threads
Cl os1ng java.net.SocketlnputStream
Interrupted fram blocked l /O
Exi ting IOBlocked.run ()
Closing j ava. 10.Buf feredlnput Stream
Exiting IOBl ocked .run {}
* /11 , -
21 Concurrencia 779
Despus de invocar shutdownNow(), los retardos utilizados antes de llamar a close() para los dos flujos de datos de entra-
da permiten resaltar que las tareas se desbloquean una vez que el recurso subyacente se ha cerrado. Resulta interesante
observar que la interrupcin aparece cuando estarnos cerrando un objeto Socket pero no al cerrar System.in.
Afortunadamente, las clases nio presentadas en el Captulo 18, Entrada/salida, permiten una interrupcin ms civilizada de
las interrupciones de E/S. Los canales nio bloqueados responden automticamente a las interrupciones:
11: cencurrency/NIOInterruption. java
II Interrupc in de un canal NIO bloqueado.
impert java.net.*;
impert java . nio. *;
i mpert java.nio.channel s.*;
impert java.util.eoneurrent.*;
impert java . io .*
i mpert static net. mindview. ut il.Pr i nt.*
class NI OBl ocked implements Runnabl e {
private final SocketChannel sc;
publie NIOBlocked (SocketChannel se) { this. se
public void run() {
try {
)
print (IIWaiting for read () in " + this ) ;
sc .read(ByteBuffer.allocate(l) i
catch (ClosedByInterruptException el {
print ( !I ClosedByInterruptExceptien") ;
catch{AsynchronousCloseExcept i on el {
print ( lIAsynchronousCloseException 11 l ;
catch(IOExcept i on e ) {
throw new RuntimeExcept i on (e) i
pr int ( "Exiting NIOBl ocked.run() 11 + t hi s);
public class NIOlnterruption {
se; )
public static void main(String[] args) throws Exception {
ExecutorService exec = Exeeutors.newCachedThreadPeol();
ServerSocket server = new ServerSocket(80BO)
InetSocketAddress isa =
new InetSocketAddress(lI localhost", 8080)
SecketChannel sel SocketChannel .open(isal
SocketChannel sc2 = SocketChannel.open( i sal
780 Piensa en Java
Future<?> t = exec.submit(new NIOBlocked(scl));
exec.execute(new NIOBlocked(sc2))
exec.shutdown()
TimeUnit.SECONDS.sleep(l)
II Producir una interrupcin mediante cancel:
f. cancel (true) ;
TimeUnit.SECONDS.sleep(l)
II Liberar el bloqueo cerrando un canal:
sc2. close ()
1* Output: (Sample)
Waiting tor read() in NIOBlocked@7a84e4
Waiting tor read() in NIOBlocked@15c7850
ClosedBylnterruptException
Exiting NIOBlocked.run() NIOBlocked@15c7850
AsynchronousCloseException
Exiting NIOBlocked.run() NIOBlocked@7a84e4
*///,-
Como se muestra, tambin podemos cerrar el canal subyacente para liberar el bloqueo, aunque esto slo debera ser nece-
sario en raras ocasiones. Observe que utilizando execute() para iniciar ambas tareas e invocar e.shutdownNow() permite
tenninar fcilmente cualquier cosa. La captura del objeto Future en el ejemplo anterior slo era necesaria para enviar la
interrupcin a una hebra y no a la otra.
18
Ejercicio 18: (2) Cree una clase que no sea de tipo tarea con un mtodo que llame a sleep() durante un intervalo de larga
duracin. Cree una tarea que llame al mtodo contenido en la clase que haya definido. En main( ), inicie
la tarea y luego llame a interrupt( ) para terminarla. Asegrese que la tarea termina de manera segura.
Ejercicio 19: (4) Modifique OrnamentalGarden.java para que utilice interrupt().
Ejercicio 20: (1) Modifique CachedThreadPool.java para que todas las tareas reciban una interrupcin (con inte-
rrnpt()) antes de completarse.
Tareas bloqueadas por un mutex
Como vimos en Interrupting.java, si tratamos de llamar a un mtodo sincronizado sobre un objeto cuyo bloqueo ya haya
sido adquirido, la tarea llamante ser suspendida (bloqueada) hasta que el objeto est disponible. El siguiente ejemplo mues-
tra cmo puede una misma tarea adquirir mltiples veces el mismo mutex:
jj: concurrencyjMultiLock.java
jj Una hebra puede volver a adquirir el mismo bloqueo.
import static net.mindview.util.Print.*
public class MultiLock {
public synchronized void fl(int count)
if (count-- > O) {
print (nfl () calling f2 () with count 11 + count);
f2 (count) ;
public synchronized void f2(int count) {
if (count-- > O) {
print(l1f2() calling fl() with count 11 + count);
fl (count) ;
public static void main(String[] args) throws Exception
final MultiLock multiLock = new MultiLock()
new Thread () {
18 Ervin Varga me ayud en la investigaciones relacionadas con esta seccin.
21 Concurrencia 781
public voi d run () (
multiLock ,fl(lO) j
}
}.start ();
1*
Output :
n () cal ling f2 () with count 9
f2 () cal ling n () wi th count 8
n() call ing f2 O with count 7
f2 () call i ng no wi th count 6
n I) cal ling f 2 1) wi th count 5
f2 O calling n () with count 4
n () cal ling f2 O with count 3
f21) call i ng no with count 2
n() call ing f2 () with count 1
f2 () ca lling fll) wi th c o unt o
*111 : -
En main( ), se crea no objeto Thread para invocar O(); luego O( ) y f2() se llaman el uno al otro hasta que el valor de
couut pasa a ser cero. Puesto que la tarea ya ha adquirido el bloqueo del objeto multiLock dentro de la primera llamada a
f1( ), esa misma tarea lo estar volviendo a adquirir en la llamada a n( ), y as sucesivamente. Esto tiene sentido, porque
una tarea debe ser capaz de invocar otros mtodos sincronizados contenidos dentro del mismo objeto, ya que dicha tarea ya
posee el bloqueo.
Corno hemos observado anteriormente al hablar de la E/S ininterrumpible, cada vez que noa tarea pueda bloquearse de tal
forma que no pueda ser interrumpida, existir la posibilidad de que el programa se quede bloqueado. Una de las caracters-
ticas aladidas a las bibliotecas de concurrencia de Java SE5 es la posibilidad de que las tareas bloqueadas en bloqueos de
tipo ReentrantLock sean interrumpidas, a diferencia de las tareas bloqueadas en mtodos sincronizados o secciones crticas:
/1: concurrency/Interrupting2.java
II Interrupc in de una tarea bloqueado con un bloqueo Reentrant Lock.
import java . util . concurrent. * ;
i mport java . util.concurrent . locks . *;
import static net.mindvi ew.uti l. Print.*;
class BlockedMutex {
pri vate Lock lock = new ReentrantLock();
public BlockedMutex() (
II Adquirirl o d i rectamente, para demostrar la i nterrupcin
II de la tarea bloqueada en un bloqueo Reent rant Lock:
lock.lock() i
public void f 1)
t ry (
II Esto no estar nunca disponible para una segunda tarea
l ock. l ockI nterruptibly( ) ; II Llamada especial
print ("lock acquired in f () ") ;
catch(InterruptedExcept ion el {
print(IIInterrupted from lock acquisition in f() 11) i
class Blocked2 i mplements Runnable {
BlockedMutex blocked = new BlockedMut ex() ;
public void run() (
print ( "Wa i ting for f ( ) in Bl ockedMutex" ) i
blocked. f () i
print (II Broken out of blocked call
1l
);
782 Piensa en Java
public class Interrupting2
public static void main(string [] args) throws Exception {
Thread t = new Thread (new Blocked2 (
t.startO ;
TimeUni t .SECONDS.sleep(l) j
System.out.println(I!Issuing t.int errupt() n);
t.in terrupt{};
/* Output:
Waiting for f() in BlockedMutex
I ssuing t .interrupt ()
I nterrupted f rem lock acquisition in f ( )
Broken out of blocked call
*111,-
La clase BlockedMutex tiene un constructor que adquiere el propio bloqueo del objeto y nunca lo libera. Por esa razn, si
tratamos de invocar f() desde una segunda tarea (diferente de la que haya creado el objeto BlockedMutex), siempre nos
quedaremos bloqueados, porque el objeto Mutex no puede ser adquirido. En Blocked2, el mtodo run( ) se detendr en la
llamada a blocked.f( ). Cuando ejecutamos el programa, vemos que, a diferencia de una llamada de E/S, interrupt( ) per-
mite salir de una llamada que est bloqueada por un mutex.
19
Comprobacin de la existencia de una interrupcin
Observe que cuando invocamos interrupt( ) para una hebra, el nico instante en que se produce la interrupcin es cuando
la tarea entra, o se encuentra ya dentro, de una operacin bloqueante (excepto, como hemos visto, en el caso de los mto-
dos sincronizados bloqueados O la E/S ininterrumpibles, en cuyo caso no hay nada que podamos hacer). Pero qu sucede
si hemos escrito. cdigo que pueda o no hacer esa llamada bloqueante dependiendo de las condiciones en las que se la eje-
cute? Si slo podemos salir generando una excepcin en una llamada bloqueante, no siempre seremos capaces de abando-
nar el bucle run( ). Por tanto, si invocamos interrupt( ) para detener una tarea, la tarea necesita una segunda forma de salir
en caso de que el bucle ruo( ) no est realizando ninguna llamada bloqueante.
Esta oportunidad se presenta gracias al estado interrnmpido, que es fijado por la llamada a interrupt( ). Comprobamos si
la tarea est en estado interrumpido llamando a interrupted( ). Esto no slo nos dice si se ha llamado a interrupt( ), sino
que tambin borra el estado interrumpido. Borrar el estado interrumpido garantiza que el sistema no nos notifique dos veces
que se ha interrumpido una tarea. La notificacin la recibiremos a travs de una nica excepcin Interrupted-Exception o
una nica comprobacin con xito del mtodo Tbread.lnterrupted( ). Si queremos comprobar de nuevo si hemos sido inte-
rrumpidos, podemos ahnacenar el resultado al invocar Tbread.interrupted( ).
El siguiente ejemplo muestra la sintaxis tpica que se usara en el mtodo run( ) para gestionar ambas posibilidades (blo-
queada y no bloqueada) cuando est activado el estado interrumpido:
/1 : concurrency/lnte rruptingldiom.java
/1 Sintaxis general para interrumpir una tarea.
II {Args, l1oo}
import j ava.util.concurrent .*
import static net.mindview.ut i l.Print.*
c lass NeedsCleanup {
prvate final int id;
public NeedsCleanup(int ident )
id = ident
print ("NeedsCleanup + id) i
public void cleanup()
print ( 11 Cleaning up " + id) i
19 Observe que, aunque resulta poco probable, la llamada a tinte .... upt() podra llegar a suceder antes que La llamada a blocked.f( ).
class Blocked3 implements Runnable
private volatile double d = 0.0;
public void run() {
try {
while(!Thread.interrupted())
// punto1
NeedsCleanup nl = new NeedsCleanup(I);
II Iniciar try-finally inmediatamente despus de la definicin
II de nI, para garantizar una limpieza apropiada de nI:
try {
print (nSleeping") i
TimeUnit.SECONDS.sleep(l) ;
/ / Punto2
NeedsCleanup n2 = new NeedsCleanup(2) i
II Garantizar una limpieza apropiada de n2:
try {
}
print ("Calculating
ll
) i
II Una operacin no bloqueante de larga duracin:
for(int i = 1; i < 2500000; i++)
d = d + (Math.PI + Math.E) / d;
print (11 Finished time-consuming operation") j
finally {
n2. cleanup () ;
finally {
nl.cleanup() i
print ("Exiting via while () test 11) ;
catch(InterruptedException e) {
print (IIExiting via InterruptedException");
public class Interruptingldiom {
public static void main(String[] args) throws Exception
if (args .length ! = 1) {
print (nusage: java InterruptingIdiom delay-in-mS")
System.exit (1) i
Thread t = new Thread(new Blocked3()}
t. start () i
TimeUnit.MILLISECONDS.sleep(new Integer(args[O])) i
t. interrupt ()
1* Output: (Sample)
NeedsCleanup 1
Sleeping
NeedsCleanup 2
Calculating
Finished time-consuming operation
Cleaning up 2
Cleaning up 1
NeedsCleanup 1
Sleeping
Cleaning up 1
Exiting via InterruptedException
*///,-
21 Concurrencia 783
784 Piensa en Java
La clase NeedsCte.nup enfatiza la necesidad de efectuar una limpieza apropiada de los recursos si se abandona el bucle
mediante una excepcin. Observe que todos los recursos de NeedsCte.nup creados en Btocked3.run() deben ir seguidos
inmediatamente de clusulas try-fmally para garantizar que siempre se invoque el mtodo eleanup().
Debe proporcionar al programa un argumento de lnea de comandos que es el retardo en milisegundos antes de que se invo-
que interrupt(). Utilizando diferentes retardos, podemos salir de Blocked3.run( ) en diferentes puntos del bucle: en la Ha-
mada sleep( ) bloqueante y en el clculo matemtico no bloqueante. Como vemos, si se invoca interrupt( ) despus del
comentario "punt02" (durante la operacin no bloqueante), se completa primero el bucle, despus se destruyen todos los
objetos locales y finalmente se sale del bucle por la parte superior gracias a la instruccin while. Sin embargo, si se invoca
interrupt( ) entre "punto 1" Y "punt02" (despus de la instruccin while pero antes o durante la operacin bloqueante
sleep( , la tarea sale a travs de la excepcin InterruptedException, la primera vez que se intente una operacin bloquean-
te. En ese caso, slo se limpian los objetos NeedsCleanup que hayan sido creados hasta el punto en que se genera la excep-
cin, y tenemos la oportunidad de crear cualquier otra tarea de limpieza dentro de la clusula catch.
Una clase diseada para responder a una interrupcin deber establecer una poltica para garantizar que permanezca en un
estado coherente. Esto significa, generalmente, que la creacin de todos los objetos que requieran limpieza deber ir segui-
da por clusulas try-finally de modo que esa limpieza tenga lugar independientemente de Cmo se salga del bucle run( ).
El cdigo de este tipo puede funcionar bien, aunque hay que sealar que, debido a la falta de llamadas automticas a des-
tructores en Java, depende de que el programador de clientes describa las clusulas try-finaUy apropiadas.
Cooperacin entre tareas
Como hemos visto, cuando se utilizan hebras para ejecutar ms de lUla tarea simultneamente, podemos evitar que unas
tareas interfieran con tos recursos de otras utilizando un bloqueo (mutex) para sincronizar el comportamiento de las dos ta-
reas. En otras palabras, si dos tareas est interfiriendo en lo que respecta a un recurso compartido (normalmente la memo-
ra), utilizamos un mutex para permitir que slo una tarea acceda en cada momento a dicho recurso.
Con ese problema resuelto, el siguiente paso consiste en aprender lo que hay que hacer para que las tareas puedan cooperar
entre s, de modo que mltiples tareas puedan trabajar juntas para resolver un cierto problema. Ahora, la cuestin no es qu
interferencias se producen entre unas tareas y otras, sino cmo trabajar al unsono, ya que partes de un cierto problema e e ~
rn ser resueltas antes de que se puedan resolver otras partes. Esto se parece bastante a la planificacin de proyectos: pri-
mero hay que hacer los cimientos de la casa, pero mientras, se pueden ir haciendo los perfiles de aluminio o fabricando los
ladrillos, y estas dos tareas tienen que estar finalizadas antes de que el resto de la casa pueda completarse. Asimismo, la fon-
tanera deber estar terminada antes de hacer las paredes, las paredes debern haber sido acabadas antes de poder finalizar
los interiores, etc. Algunas de estas tareas pueden hacerse en paralelo, pero ciertos pasos requieren que determinadas tareas
previas se compieten antes de poder continuar.
La cuestin clave cuando hay una serie de tareas cooperando es la negociacin que se produce entre dichas tareas. Para lle-
var a cabo esa negociacin, utilizamos la misma base: el mutex, que en este caso garantiza que slo haya una tarea que pueda
responder a una seal. Esto elimina cualquier posible condicin de carrera. Adems del mutex, tenemos que aadir una
forma de que una tarea suspenda su ejecucin hasta que un cierto estado externo cambie (por ejemplo, "la fontanera ha sido
acabada"), lo que indicar que ser el momento de que dicha tarea contine. En esta seccin, vamos a examinar el tema de
la negociacin entre tareas, que se puede implementar utilizando los mtodos wait() y notifyAII() de Object. La bibliote-
ca de concurrencia de Java SES tambin proporciona objetos Condition con mtodos await() y signal(). Veremos los pro-
blemas que pueden surgir, junto con sus correspondientes soluciones.
wait() y notify AUO
wait( ) nos permite esperar a que se produzca un cambio en cierta condicin que est fuera del control del mtodo actual.
A menudo, esta condicin ser modificada por otra tarea. Lo que no queremos es permanecer inactivos dentro de un bucle
mientras comprobamos la condicin de la tarea; este tipo de espera se denomina espera activa, y representa usualmente un
mal uso de los ciclos de procesador. Por ello, el mtodo wait( ) suspende la tarea mientras espera a que el mundo exterior
cambie, y slo cuando tiene lugar una llamada a notify() o notifyAll( ) (que sugieren que puede haber ocurrido un cierto
suceso de inters) se despertar la tarea y comprobar si se han producido cambios. De este modo, wait() proporciona una
fonna de sincronizar las actividades entre las tareas.
21 Concurrencia 785
Es importante comprender que sleep( ) no libera el bloqueo del objeto cuando se lo iovoca, pero tampoco 10 hace yield( ).
Por otro lado, cuando una tarea entra en una llamada a wait( ) dentro de un mtodo, se suspende la ejecucin de esa hebra
y se libera el bloqueo sobre ese objeto. Puesto que wait( ) libera el bloqueo, quiere decir que ese bloqueo podr ser adqui-
rido por otra tarea, por lo que durante mla espera con wait() podrn iovocarse otros mtodos sincronizados en el (abara des-
bloqueado) objeto. Esto resulta esencial, porque esos otros mtodos son normalmente los que provocan el cambio que hacen
que sea interesante que se vuelva a despertar la tarea suspendida. As, cuando llamamos a. wait( ), estamos diciendo: "he
hecho todo lo que puedo por ahora, as que voy a esperar aqu, pero quiero pennitir que otras operaciones sincronizadas pue-
dan tener lugar, si es que pueden".
Existen dos formas de wait(). Una versin toma un argumento en milisegundos que tiene el mismo significado que sleep():
"efecta una pausa durante este perodo de tiempo". Pero, a diferencia de sleep( ), con wait(pausa):
1. El bloqueo del objeto se libera durante la ejecucin de wait().
2. Tambin se puede salir de la llamada a wait( ) debido a la recepcin de notify( ) o notify All( ), adems de per-
mitir que la temporizacin fmalice.
La segunda forma de wait( ) ms comn no toma ningn argmnento. Este tipo de wait( ) continuar indefinidamente hasta
que la hebra reciba un mensaje notify() o notlfyAll().
Una aspecto bastante distintivo de wait(), Dotify() y notifyAll() es que estos mtodos forman parte de la clase base Object
y no de Thread. Aunque esto parece algo extrao a primera vista (tener algo exclusivo del mecanismo de hebras como parte
de la clase base universal), resulta esencial porque estos mtodos manipulan el bloqueo que tambin forma parte de todos
los objetos. Como resultado, podemos incluir una llamada a wait( ) dentro de cualquier mtodo sincronizado, independien-
temente de si dicha clase ampla a Thread o implementa Runnable. De hecho, el nico lugar en que se puede llamar a
wait(), notify() o notifyAll() es dentro de un mtodo o bloque synchronized, (sleep() puede invocarse dentro de mto-
dos no sincronizados ya que no manipula el bloqueo). Si iovocamos cualquiera de estos mtodos dentro de un mtodo que
no sea de tipo synchronized, el programa se podr compilar, pero al ejecutarlo se obtendr una excepcin IIIegalMonitor-
StateException con el poco intuitivo mensaje de "current thread not owner" Oa hebra actual no es la propietaria). Este men-
saje qniere decir que la tarea qne est invocando wait( ), notify( ) o notifyAll( ) debe "poseer" (adquirir) el bloqueo del
objeto antes de poder invocar ninguno de sus mtodos.
Podemos pedir a otro objeto que realice una operacin que manipula su propio bloqueo. Para hacer esto, tenemos primero
que capturar el bloqueo de ese objeto. Por ejemplo, si queremos enviar notifyAll() a un objeto x, deberemos hacerlo den-
tro de UD bloque sincronizado que adquiere el bloqueo para x:
synchronized(x) {
x .notifyAll () ;
Examinemos un ejemplo simple. WaxOMatic.java tiene dos procesos: uno para aplicar cera a un objeto coche (represen-
tado por un objeto Car) y otro para pulirlo. La tarea de pulido no puede llevar a cabo su trabajo hasta que haya finalizado
la tarea de aplicacin de la cera y la tarea de aplicacin de cera hasta que la tarea de pulido haya finalizado, antes de poner
otra capa de cera. Tanto WaxOn como WaxOffutilizan el objeto Car, que emplea wait() y notifyAlI() para suspender y
reiniciar las tareas mientras stas estn esperando a que una condicin cambie:
ji : concurrency/ waxomatic/ WaxOMa t ic.java
JI Cooperacin bsica entre t a r eas.
package concurrency.waxomatic;
import java.util.concurrent. *;
import static net.mindview. util.Pr int .*;
class Car (
private boolean waxOn = fal se;
public s ynchronized void waxed( )
waxOn = true: /1 Listo para pulido
notifyAll () ;
public synchronized void buffed() {
waxOn = false; /1 Listo para otra capa de cera
786 Piensa en Java
notifyAll () ;
publi c synchronized voi d waitForWaxing()
throws InterruptedException {
while(waxOn == fal se)
wait () ;
publ i c synchronized void waitForBuffing ( )
throws I nterruptedException {
whil e (waxOn == true)
wait () ;
class WaxOn impl ement s Runnable {
private Car car
publ ic WaxOn(Car e l { car = c;
public void run() {
try {
wh i l e ( !Thr ead .int e rrupted ()
printnb (IrWax On! Ir);
TimeUni t .MILLI SECONDS.s leep(2 00l i
car. waxed ( ) ;
car.wai tFor Buff i ng () ;
catch(Int erruptedExcepti on el {
print ( IIExiting via interrupt " ) ;
print (IIEnding Wax On task" ) ;
class waxOff implements Runnabl e {
pri vate Car car;
public WaxOf f (Car e } { car = c
publi c void run () {
try {
while (-! Thread . interr upted () )
car . wai tForWaxing() ;
printnb ( "Wax Off ! 11);
TimeUnit.MILLISECONDS.sleep(200) ;
car . buff ed () ;
catch(InterruptedException el {
print(IIExiting via interr upt ll l ;
print (!l Ending Wax Of f task" ) i
public cIass WaxOMatic {
publ ic static void main(String[] args) throws Exeeption {
Car car = new Car ( )
ExecutorService exec = Executors.newCachedThreadPool( ) ;
exec.execute(new WaxOff(car)) i
exec.execute (new WaxOn (car );
TimeUni t . SECONDS.sleep (S) II Ejecutar durant e cierto tiempo ...
exec.shutdownNow(); II Int errumpir todas las tareas
} 1* Output, (95% match)
Wax On! Wax Off! Wax Gn! Wax Off! Wax On! Wax Off! Wax On !
Wax Off! Wax On! Wax Off! Wax On! Wax Off! Wax Gn! Wax Off!
Wax On! Wax Off! Wax Gn! Wax Off! Wax On! Wax Off! Wax On!
Wax Off! Wax On! Wax Off! Wax Gn! Exiting via interrupt
Ending Wax On task
Exiting via interrupt
Ending Wax Off task
*//1, -
21 Concurrencia 787
Aqu, Car tiene un nico campo booleano waxOn, que indica el estado del proceso de aplicacin-pulido.
En waitForWaxing( ), se comprueba el indicador waxOn y, si es false, se suspende la tarea lIamante invocando wait( ). Es
importante que esto tenga lugar dentro un mtodo sincronizado, en el que la tarea haya adquirido el bloqueo. Cuando invo-
camos wait( ), la hebra se suspende y el bloqueo se libera. Resulta esencial que se libere el bloqueo, porque para cambiar
con seguridad el estado del objeto (por ejemplo, para cambiar waxOn a true, que es algo que tiene que ocurrir para que l.
tarea suspendida pueda continuar), dicho bloqueo debe estar disponible para que lo adquiera alguna otra tarea. En este ejem-
plo, cuando otra tarea invoca waxed() para indicar que es el momento de hacer algo, hay que adquirir el bloqueo para poder
cambiar waxOn a true. Despus, waxed( ) invoca a notify AU( ), que despierta a la tarea que haba sido suspendida en la
llamada a wait(). Para que la tarea pueda despertarse de una llamada a wait( ), deber primero readquirir el bloqueo que
liber en el momento de entrara en wait( ). La tarea no se despertar hasta que dicho bloqueo est disponible
2o
WaxOn.run( ) representa el primer paso dentro de un proceso de aplicacin de la cera al coche, as que lleva a cabo su ope-
racin: una llamada a sleep() para simular el tiempo necesario para la aplicacin de la cera. A continuacin, le dice al coche
que la aplicacin de la cera se ha completado y llama a waitForBuffing(), que suspende esta tarea con una llamada a wait( )
hasta que la tarea WaxOff llama a buffed() para el coche, cambiando el estado y llamando a notifyAll(). WaxOff.run(),
por otro lado, entra inmediatamente en waitForWaxing( ) y se suspende, por tanto, hasta que la cera haya sido aplicada por
WaxOn y se invoque a waxed(). Cuando se ejecuta este programa, podemos ver cmo este proceso en dos pasos se repite
continuamente a medida que las dos tareas se ceden la una a la otra el control. Despus de cinco segundos, interrupt( )
detiene ambas hebras; cuando se invoca shutdownNow() para un objeto ExecutorService, ste invoca a interrupt() para
todas las tareas que est controlando.
El ejemplo anterior resalta el hecho de que hay que rodear una llamada a wait( ) con un bucle while que compruebe la con-
dicin o condiciones de inters. Esto es importante porque:
Puede que tengamos mltiples tareas que estn esperando a un determinado bloqueo por la misma razn, y la pri-
mera tarea que se despierte puede cambiar la situacin (incluso si no hacemos esto, alguien podra heredar de
nuestra clase y hacerlo). En este caso, dicha tarea debera ser suspendida de nuevo hasta que su condicin de inte-
rs cambiara.
En el momento en que esta tarea se despierte de su llamada a wait( ), es posible que alguna otra tarea haya cam-
biado las cosas de modo que esta tarea sea incapaz de realizar su operacin en este momento, o no le interesa rea-
lizarla. De nuevo, deberla volver a ser suspendida invocando de nuevo a wait( ).
Tambin es posible que las tareas estuvieran esperando el bloqueo del objeto por razones distintas (en cuyo caso,
es necesario utilizar notifyAll( . En este caso, necesitarnos comprobar si se nos ha despertado por la razn
correcta y, en caso contrario, volver a invocar wait( ).
Por !auto, resulta esencial que comprobemos nuestra condicin de inters concreta y que volvamos a wait( ) si dicha con-
dicin no se cumple. La estructura sintctica para hacer esto es un bucle while.
Ejercicio 21: (2) Cree dos clases Runnable, una con un mtodo run() que se inicie e invoque wait( ). La segunda clase
debe capturar las referencias del objeto Runoable. Su mtodo run( ) debera invocar notify AllO para la
20 En algunas plataformas, existe una tercera forma de salir de una llamada a wait( ): el denominado despertar espreo. Un despertar espreo significa,
esencialmente, que una hebra puede abandonar el bloqueo prematuramente (mientras est esperando de acuerdo con un semforo o con una variable de
condicin) sin que ello venga desencadenado por un mensaje a notify() o notifyAII() (o sus equivalentes para los nuevos objetos Condition). La hebra
simplemente se despierta aparentemente por s misma. Estos despertares espreos existen porque la implementacin de hebras POS IX, o sus equivalentes,
no es siempre tan sencilla como debera ser en algunas platafonnas. Pennitir estos despertares espreos hace que la tarea de construir una biblioteca como
pthreads sea ms fcil en esas platafonnas.
788 Piensa en Java
primera tarea despus de que haya pasado lli1 cierto nmero de segundos, de modo que la primera tarea
pueda mostrar un mensaje. Pruebe las dos clases utilizando lli1 objeto Executor.
Ejercicio 22: (4) Cree un ejemplo de espera activa. Una tarea debe donnir durante un cierto tiempo y luego asignar el
valor true a un indicador. La segunda tarea deber comprobar dicho indicador dentro de un bucle while
(sta es la espera activa) y cuando el indicador sea true, deber asignarl e de nuevo el valor false e infor-
mar del cambio a travs de la consola. Observe cunto tiempo desperdicia el programa dentro de la espe-
ra activa, y cree una segunda versin del programa que emplee wait( ) en lugar de esa espera activa.
Seales perdidas
Cuando se coordinan dos hebras utilizando notlfy( )/wait() o notifyAIJ( )/wait(), resulta posible perder una seal. Suponga
que Ti es una hebra que notifica a TI, y que las dos hebras se implementan utilizando el siguiente enfoque (incorrecto):
Tl,
synchronized(sharedMonitor ) {
<condi cin de confi guraci 6n para T2>
sharedMoni tor .notify() ;
T2,
while(someCondition)
11 Punto 1
synchronized(sharedMonitor)
sharedMonitor .wait() ;
La <condicin de configuracin pora T2> es una accin destinada a impedir que T2 invoque wait( ), si es que no lo ha
hecho ya.
Suponga que T2 evala someCondilion y encuentra que esa condicin es verdadera. En Punto 1, el planifi cador de hebras
podra conmutar a Tl. TI ejecutara su configuracin, y entonces invocara notify( ). Cuando T2 contine ejecutndose, es
demasiado tarde para que T2 se d cuenta de que la condicin se ha modificado mientras tanto, por lo que entrar ciega-
mente en wait(). El mensaje notify() se perder y T2 esperar indefinidamente a recibir una seal que ya haba sido envia-
da, lo que producir un interbloqueo.
La solucin consiste en impedf la condicin de carrera que afecta a la variable someCollditioD. He aqu la tcnica correc-
ta para T2:
synchronized(sharedMonitor)
while(someCondition)
sharedMonitor.wait() ;
Ahora, si se ejecuta primero TI, cuando el control vuelve a T2 ste podr ver que la condicin se ha modificado, y no entra-
r en wait(). A la inversa, si se ejecuta primero TI, entrar en wait( ) y ser posteriormente despertado por TI . De este
modo, no puede perderse ninguna seal.
notifyO y notifyAIIO
Puesto que tcnicamente podra darse el caso de que hubiera ms de una tarea esperando con wait( ) con un nico objeto
Car, resulta ms seguro invocar notifyAIJ() que simplemente notify(). Sin embargo, la estructura del programa anterior es
tal que slo habr una tarea esperando con wait(), por lo que podemos perfectamente usar notify() en lugar de notifyAll().
Utilizar notify() en lugar de notifyAll( ) es una optimizacin. Slo se despertar con notify( ) a una de las tareas de las
muchas posibles que estn esperando con bloqueo, as que si tratamos de utilizar notify( ) debernos estar seguros de que se
despertar la tarea correcta. Adems, todas la tareas debern estar esperando por la misma condicin si queremos utilizar
ootify(), porque si hubiera tareas que estuvieran esperando por condiciones diferentes, no sabramos si se despertar la tarea
correcta. Si empleamos notify( ), slo una tarea deber aprovecharse cuando se modifique la condicin. Finalmente, estas
21 Concurrencia 789
restricciones deben cumplirse para todas las subclases posibles. Si no se puede cumplir alguna de estas reglas, es necesario
emplear notifyAll() en lugar de notlfy().
Una de las afirmaciones confusas que a menudo se hacen a la hora de explicar el mecanismo de herramientas de Java es que
notifyAll() despierta "a todas las tareas en espera". Quiere esto decir que cualquier tarea que se encuentre esperando con
wait( ) en cualquier lugar del programa, ser despertada por cualquier llamada a notifyAU()? En el siguiente ejemplo, el
cdigo asociado con Task2 demuestra que esto no es as; de hecho, cuando se invoque notifyAll() para un cierto bloqueo
slo se despertarn las tareas que estn esperando por ese bloqueo concreto:
ji: concurrency/NotifyVsNotifyAll.java
import java.util.concurrent.*
import j ava. uti l.*
class Blocker {
synchronized void waitingCall() {
try {
while ( !Thread.interrupted ()) {
wait {) ;
System.out.print(Thread.currentThread() + n ") i
catch{InterruptedException el
JI OK salir de esta forma
synchronized void prod () { notify() }
synchronized void prodAll () { notifyAll () ;
clas s Task implements Runnable {
static Blocker blocker = new Blocker();
public void run( ) { blocker.waitingCal l()
class Task2 implements Runnable {
II Un objeto Blocker separado:
static Blocker bl ocker = new Blocker( )
public void run() { blocker.waitingCall()
publ ic class NotifyVsNotifyAll {
public static void main(String[] args) t hrows Exception {
ExecutorService exec = Executors.newCachedThreadPool( );
for(int i = O; i < Si i++)
exec.execute(new Task(
exec.execute {new Task2{
Timer timer = new Timer()
timer.scheduleAtFixedRate(new TimerTask() {
bool ean prod = true ;
public void run () {
}
if (prod) {
System.out .print(lI\nnotifyO u) i
Task.blocker. prod() i
prod = false
el se {
System.out.print(" \ nnotifyAll ( ) n);
Task .blocker.prodAl l{) i
prod = true;
}, 400, 400} II Ejecutar cada 4 segundos
790 Piensa en Java
TimeUnit.SECONDS.sleep(S) i /1 Ejecutar durante un tiempo ...
timer.cancel() ;
System.out .println ("\nTimer canceled");
TimeUnit.MILLISECONDS.sleep(500) ;
System.out.print (UTask2.blocker.prodAll () 11);
Task2.blocker.prodAll() j
TimeUnit.MILLISECONDS.sleep(500) ;
System.out.println(u\nShutting down
ll
);
exec.shutdownNow(); JI Interrumpir todas las tareas
/* Output, (Samplel
notify() Thread [pool-l-thread-l,5,mainJ
notifyAll() Thread[pool-l-thread-l,5,main] Thread [pool-l-
thread-5,5,mainJ Thread [pool-l-thread-4, S,mainJ
Thread [pool-l-thread-3, S,main] Thread [pool-l-thread-2, S,main]
notify() Thread[pool-l-thread-l,5,mainJ
notifyAll() Thread[pool-l-thread-l,5,main] Thread [pool-l-
thread-2,5,mainJ Thread [pool-l-thread-3, 5,mainJ
Thread [pool-l-thread-4,5,mainJ Thread [pool-l-thread-5, 5,mainJ
notify() Thread[pool-l-thread-l,5,main]
notifyAll() Thread[pool-l-thread-l,5,mainJ Thread [pool-l-
thread-5,5,main] Thread [pool-l-thread-4, 5,main]
Thread [pool-l-thread-3, 5,mainJ Thread [pool-l-thread-2, 5,mainJ
notify() Thread[pool-l-thread-l,5,mainJ
notifyAll() Thread[pool-l-thread-l,5,mainJ Thread [pool-l-
thread-2,5,mainJ Thread [pool-l-thread-3, 5,main]
Thread [pool-l-thread-4, 5,main] Thread [pool-l-thread-5, 5,mainJ
notify() Thread[pool-l-thread-l,5,mainJ
notifyAll() Thread[pool-l-thread-l,5,main] Thread [pool-l-
thread-5,5,mainJ Thread [pool-l-thread-4, 5,mainJ
Thread [pool-l-thread-3, 5,main] Thread [pool-l-thread-2, 5,mainJ
notify() Thread[pool-l-thread-l,5,mainJ
notifyAll() Thread[pool-l-thread-l,5,main] Thread [pool-l-
thread-2,5,main] Thread [pool-l-thread-3, 5,mainJ
Thread [pool-l-thread-4,5,mainJ Thread [pool-l-thread-5, 5,main)
Timer canceled
Task2.blocker.prodAll() Thread[pool-l-thread-6,5,mainJ
Shutting down
*///,-
Task y Task2 tienen, cada uno de ellos, su propio objeto Blocker, de modo que cada objeto Task se bloquea sobre
Task.blocker, y cada objeto Task2 se bloquea sobre Task2.blocker. En main( ), se configura un objeto java.util.Timer
para ejecutar su mtodo run( ) cada 4/10 segundos y ese mtodo rnn( ) llama alternativamente a los mtodos notify( ) y
notifyAII() sobre Task.blocker, a travs de los mtodos "prod".
Analizando la salida, podemos ver que, aunque existe un objeto Task2 que no est bloqueado sobre Task2.blocker, ningu-
na de las llamadas notify() o notifyAII() sobre Task.blocker hace que el objeto Task2 se despierte. De forma similar, al
final de main( ), se invoca cancel( ) para timer y, aun cuando se cancela el temporizador, las primeras cinco tareas siguen
ejecutndose y siguen bloqueadas en sus llamadas a Task.blocker.waitingCall( ). La salida correspondiente a la llamada
Task2.blocker.prodAlI( ) no incluye ninguna de las tareas que est esperando por el bloqueo de Task.blocker.
Esto tambin tiene sentido si examinamos prod( ) y prodAll( ) en Blocker. Estos mtodos son sincronizados, lo que quie-
re decir que tienen su propio bloqueo, de manera que cuando invocan notify() o notifyAll(), resulta lgico que slo estn
invocando dichos mtodos para ese bloqueo. y que slo despierten a las tareas que estn esperando por ese bloqueo con-
creto.
Blocker.waitingCall() es lo suficientemente simple como para que podamos escribir for(;;) en lugar de
while(!Thread.interrupted( )), y conseguir el mismo efecto en este caso, porque en este ejemplo no hay diferencia entre
abandonar el bucle con una excepcin y abandonarlo comprobando el indicador interrupted( ); en ambos casos se ejecuta
el mismo cdigo. Sin embargo, por cuestin de estilo, este ejemplo comprueba interrnpted( ), porque existen dos formas
21 Concurrencia 791
distintas de salir del bucle. Si decidimos posteriormente aadir ms cdigo al bucle, correramos el riesgo de introducir un
error si no se cubren ambas fannas de salir del bucle.
Ejercicio 23: (7) Demuestre que WaxOMatic.java funciona adecuadamente cuando se emplea notify( ) en lugar de
notifyAll( ).
Productores y consumidores
Considere un restaurante que tiene un cocinero y un camarero. El camarero tiene que esperar a que el cocinero prepare un
plato. Cuando el cocinero tiene un plato preparado, se lo notifica al camarero, que toma el plato y lo entrega al cliente y
vuelve a quedar en espera. ste es un ejemplo de cooperacin entre tareas: el cocinero representa al productor y el camare-
ro representa al consumidor. Ambas tareas deben negociar a medida que se producen y consumen los platos y el sistema
tiene que ser capaz de terminar de una manera ordenada. He aqu este ejemplo modelado en cdigo Java:
ji: concurrency/Restaurant.java
// La tcnica productor-consumidor para cooperacin entre tareas.
import java.util.concurrent.*;
import static net.mindview.util.Print.*;
class Meal {
private final int orderNum
public Meal(int orderNum) { this.orderNum
public String toString () { return uMeal 11
class WaitPerson implements Runnable {
private Restaurant restaurant
orderNum }
+ orderNum }
public WaitPerson(Restaurant r) {restaurant r}
public void run() {
try (
while(!Thread.interrupted())
synchronized(this) {
while(restaurant.meal == null)
wait() II ... para que el cocinero prepare un plato.
print (UWai tperson got 11 + restaurant. meal)
synchronized (restaurant. chef) {
restaurant.meal = null
restaurant.chef.notifyAII() II Listo para otro
catch(InterruptedException e) {
print ("Wai tPerson interrupted");
class Chef implements Runnable {
private Restaurant restaurant;
private int count = O
public Chef(Restaurant r) {restaurant r}
public void run() {
try (
while(!Thread.interrupted())
synchronized (this) {
while(restaurant.meal != null)
wait() II ... para que se lleven el plato
792 Piensa en Java
if (++count == 10) {
print ("Out of food, closing")
restaurant.exec.shutdownNow() ;
printnb ( 11 Order up! ");
synchronized(restaurant.waitPerson)
restaurant.meal = new Meal(count)
restaurant.waitPerson.notifyAll() ;
TimeUnit.MILLISECONDS.sleep(lOO) ;
catch(InterruptedException e)
print("Chef interrupted!1}
public cIass Restaurant {
Meal meal;
ExecutorService exec = Executors.newCachedThreadPool();
WaitPerson waitPerson = new WaitPerson(this} ;
Chef chef = new Chef(this)
public Restaurant() {
exec.execute(chef)
exec.execute(waitPerson) ;
pubIic static void main(String[] args) {
new Restaurant(}
/* Output:
Order upl Waitperson got Meal
Order up! Waitperson got Meal
Order up! Waitperson got Meal
Order up! Waitperson got Meal
Order up! Waitperson got Meal
Order up! Waitperson got Meal
Order up! Waitperson got Meal
Order up! Waitperson got Meal
Order up! Waitperson got Meal
Out of food, closing
WaitPerson interrupted
Order up! Chef interrupted
*///,-
1
2
3
4
5
6
7
8
9
El objeto Restaurant es el punto focal tanto para el camarero (WaitPerson) como para el cocinero (Chef). Ambos deben
saber para qu objeto Restaurant estn trabajando, porque ambos deben colocar o tomar los platos en el "mostrador" del
mismo restamante, restanrant.meal. En rnn( ), el objeto WaitPerson entra en modo wait( ), detenindose esta tarea hasta
que sta es despertada mediante un mensaje notifyAll() procedente del objeto Chef. Puesto que esto es un programa muy
simple, sabemos que slo habr una tarea esperando por el bloqueo correspondiente a WaitPerson: la propia tarea
WaitPerson. Por esta razn, seria tericamente posible invocar notify() en lugar de notifyAll(). Sin embargo, en situacio-
nes ms complejas, puede que haya mltiples tareas esperando por el bloqueo concreto de un objeto, as que no sabremos
qu tarea debe ser despertada. Por tanto, resulta ms seguro invocar notifyAll(), que despierta a todas las tareas que estn
esperando por ese bloqueo. Cada tarea deber entonces decidir si la notificacin es relevante.
Una vez que el objeto Chef entrega un plato (un objeto Meal) y notifica al objeto WaitPerson, el Chef espera hasta que
WaitPerson toma el plato y lo notifica al Chef, que entonces puede producir el siguiente objeto Meal.
Observe que la llamada a wait( ) est encerrada dentro de una instruccin while( ) que comprueba esa misma condicin por
la que se est esperando. Esto parece un poco extrao a primera vista: si estamos esperando un pedido, una vez que desper-
tamos, ese pedido tendr que estar disponible, verdad? Como hemos indicado anteriormente, el problema es que en una
21 Concurrencia 793
aplicacin concurrente, alguna otra tarea podra interferir y hacer el pedido mientras que el objeto WaitPerson se est
despertando. El nico enfoque seguro consiste en utilizar siempre la siguiente sintaxis para una llamada a wait() (emplean-
do, por supuesto, la adecuada sincronizacin y preparando el programa para que no exista la posibilidad de que se pierdan
seales):
while(noSeCumpleCondicin)
wait () ;
Esto garantiza que la condicin se habr cumplido antes de salir del bucle de espera y que si se nos ha notificado algo que
no afecta a la condicin (como puede ocurrir con notifyAll( , o la condicin cambia antes de que salgamos del todo del
bucle de espera, estar garantizado que volveremos a entrar en espera.
Observe que la llamada a notifyAU( ) tiene que capturar priroero el bloqueo sobre waitPerson. La llamada wait( ) en
WaitPerson.run( ) libera automticamente el bloqueo, as que esto resulta posible. Dado que el bloqueo deber haber sido
adquirido para poder invocar notifyAU(), estar garantizado que no puedan interferir dos tareas que estn tratando de invo-
car notify AU( ) sobre un mismo objeto.
Ambos mtodos mn() estn diseados para poder efectuar una terminacin ordenada encerrando el mtodo run( ) comple-
to dentro del bloque try. La clusula eateh se cierra justo antes de la llave de cierre del mtodo run(), por lo que si la tarea
recibe una excepcin InterruptedException, terminar inmediatamente despus de capturar la excepcin.
En Chef, observe que despus de invocar shutdownNow(), podramos simplemente volver (con retum) de run(), yeso
es lo que haremos normalmente. Sin embargo, resulta un poco ms interesante hacerlo de la forma en que se lleva a cabo
en el ejemplo. Recuerde que shutdownNow( ) enVa una notificacin ioterrupt( ) a todas las tareas que hayan iniciado el
objeto ExecutorService. Pero en el caso de Chef, la tarea no se termina inmediatamente despus de recibir la notificacin
interrupt( ), porque la interrupcin slo genera InterruptedException cuando la tarea intenta iniciar lma operacin blo-
queante (interrumpible). Por tanto, primero veremos que se muestra el mensaje "Order up!" y luego se genera Interrupted-
Exception cuando el objeto Chef trata de invocar sleep(). Si eliminamos la llamada a sleep(), la tarea alcanzar la parte
superior del bucle run( ) y saldr de la comprobacin Thread.interrupted( ), sin generar una excepcin.
El ejemplo anterior slo tiene un lugar cuando una tarea pueda almacenar un objeto de modo que otra tarea pueda utilizar
ese objeto posteriormente. Sin embargo, en una implementacin tpica productor-consumidor, se utilizara una cola de tipo
FIFO para almacenar los objetos que estn siendo producidos y consumidos. Aprenderemos ms acerca de dichas colas pos-
teriormente en el captulo.
Ejercicio 24: (1) Resuelva un problema de un nico productor y un nico consumidor utilizando wa!t() y DotifyAlI().
El productor no debe desbordar el buffer del receptor, lo que podria ocurrir si el productor fuera ms rpi-
do que el consumidor. Si el consumidor es ms rpido que el productor, entonces no deber leer los mis-
mos datos ms de una vez. No realice ninguna suposicin acerca de las velocidades relativas del productor
y el consumidor.
Ejercicio 25: (1) En la clase Chef de Restaul'ant.java, vuelva del mtodo run() despus de invocar shutdownNow()
y observe la diferencia de comportamiento.
Ejercicio 26: (8) Aada una clase BusBoy (ayudante) a Restaurant.java. Despus de entregar un plato, WaitPerson
debe notificar a BusBoy que tiene que efectuar la limpieza.
Utilizacin de objetos Lock y Condition explicitos
Existen berranlientas adicionales ex.plcitas dentro de la biblioteca java.ntil.coneurrellt de Java SES que puede utilizarse
para reescribir WaxOMatic.java. La clase bsica que utiliza un mutex y permite la suspensin de tareas es Condition, y
puede suspender una tarea llamando a await() sobre un objeto Condition. Cuando tiene lugar un cambio de estado exter-
no que pudiera implicar que una tarea puede continuar con su procesamiento, enviamos una notificacin a la tarea invocan-
do el mtodo sigllal( ), para despertar a una sola tarea, o signalAlI( ), para despertar a todas las tareas que se hayan
suspendido a s mismas para esperar por ese objeto Condition (al igual que sucede con notifyAII(), signalAJJ() es la tc-
nica ms segura).
He aqu un programa WaxOMatic.java reescrito para incluir un objeto Condition que se utiliza para suspender una tarea
dentro de waltForWaxing( ) o waitForBuffing( ):
794 Piensa en Java
ji: concurrency/waxomat i c2;WaxOMat i c2 . java
// Ut i lizaci n de objetos Lock y Condit i on.
package concurrency.waxomatic2
i mport java.uti l .concurrent.*;
import j ava.util.concurrent.locks.*
i mport stat i c net . mindview.util . Print.*
c Iass Car
pr vate Lock lock = new ReentrantLock () i
pri vate Condit ion condition = l ock.newCondition();
pr vate boolean waxOn = fal se
public void waxed()
l ock .lock () ;
t ry (
waxOn = true; 1/ Listo par a pulir
condi t i on . signalAll () j
finally (
l ock . unlock()
publ ic void buffed( )
lock .lock () ;
t ry (
waxOn = fal se; ji Listo para otra capa de cera
condition.signalAll{) ;
finally (
loc k . unl ock() ;
public void waitFor Waxing () t hrows I n terruptedException {
lock . l ock() ;
t r y (
while(waxOn == falsel
condi tion.await()
finally (
lock .unl ock() ;
public voi d waitForBuffing() throws InterruptedException{
lock .lock () ;
try (
while(waxOn true)
condi t ion.awai t ()
finally (
l ock. unl ock( ) ;
class WaxOn i mplement s Runnable {
priva te Car ear i
public WaxOn (Car e) { car = c
public voi d run () {
try (
while( !Thread .interrupted{))
printnb ( nWax On ! U);
TimeUni t .MILLISECONDS.sl eep(200) i
ear.waxed{) ;
car.waitForBuffing( ) ;
catch{InterruptedException e ) {
print (" Exi ting va interrupt II) i
print ( 11 Ending Wax On task 11 ) i
class WaxOff implements Runnable {
private Car car
public WaxOff(Car el { car = C
public voi d run ( ) {
try {
while {!Thread.interrupted (
car.waitForWaxing()
pri ntnb ( "Wax Off! " ) ;
TimeUnit.MILLISECONDS.sleep( 200 ) ;
car.buffed () ;
catch(InterruptedException e ) {
print (11 Exi t ing via interrupt n) i
p rint (IIEnding Wax Of f task" ) i
public clase WaxOMatic2 {
public static void main(String (} args) throws Exception {
Car car = new Car ( )
Execut orService exec = Execut ors.newCachedThreadPool ()
exec.execute (new WaxOff (car
exec.execute (new WaxOn (car
TimeUnit,SECONDS .sleep (5 ) i
exec.shutdownNow() i
/ * Output, (90% match)
Wax On! Wax Off ! Wax On ! Wax Off! Wax On! Wax Off ! Wax On !
Wax Of f! Wax On! Wax Of f! Wax On ! Wax Off ! Wax Onl Wax Off!
Wax On! Wax Off ! Wax On ! Wax Off! Wax On! Wax Off! Wax On!
Wax Off! Wax On! Wax Off! Wax On! Exi ting via interrupt
Ending Wax Off task
Exi t ing via interrupt
Ending Wax On task
* / //,-
21 Concurrencia 795
En el constructor de Car, un nico objeto Lock produce un objeto Condition que se utiliza para gestionar la comunicacin
inter-tareas. Sin embargo, el objeto Condition no contiene ninguna informacin acerca del estado del proceso, as que es
necesario gestionar informacin adicional que indique este estado; esa informacin es el campo waxOn de tipo booleano
Cada llamada a lock() debe ir seguida inmediatamente de una clusula try-finally para garantizar que el desbloqueo se pro-
duzca en todos los casos. Al igual que sucede con las versiones integradas, una tarea debe poseer el bloqueo antes de poder
invocar a await( ), sigoal( ) o signalAlI( ).
Observe que esta solucin es ms compleja que la antenor y que esa complejidad no nos proporciona ninguna ventaja
adicional en este caso. Los objetos Lock y Condition slo son necesarios para otros problemas de gestin de hebras ms
complicados.
Ejercicio 27: (2) Modifique Restaurant.java para utilizar objetos Lock y Condition explcitos.
796 Piensa en Java
Productores-consumidores y colas
Los mtodos wait( ) y notifyAll( ) resuelven el problema de la cooperacin entre tareas a bastante bajo nivel, efectuando
una negociacin para cada iteracin. En muchos casos, podemos movernos un nivel de abstraccin hacia arriba y resolver
los problemas de la cooperacin entre tareas utilizando una cola sincronizada, que slo pennite que una tarea inserte o eli-
mine un elemento cada vez. Este tipo de funcionalidad la proporciona la interfaz java.util.concurrent.BlockingQueue, que
tiene varias implementaciones estndar. Normalmente utilizaremos LinkedBlockingQueue, que es una cola no limitada; la
cola ArrayBlockingQueue tiene un tamao fijo, por lo que slo se puede introducir en ella un cierto nmero de elementos
antes de que se bloquee.
Estas colas tambin suspenden una tarea consumidora si dicha tarea trata de extraer un objeto de la cola estando sta vaca;
la tarea se reanudar cuando haya ms elementos disponibles. Las colas bloqueantes como stas pueden resolver un gran
nmero de problemas de una forma mucho ms simple y ms fiable que wait() y notifyAll( ).
He aql una prueba simple que serializa la ejecucin de objetos LiftOff. El consumidor es LiftOffRunner, que extrae cada
objeto LiftOrf de la cola bloqueante BlockingQueue y lo ejecuta directamente <es decir, utiliza su propia hebra invocando
explcitamente run() en lugar de iniciar una nueva hebra para cada tarea).
JI : concurrencyjTestBlockingQueues.java
/ / {RunByHand}
import java.util.concurrent.*
i mport java.io.*;
import static net.mindview.util.Print.*
c lass LiftOffRunner implements Runnable {
pri vate rocketsi
public queue) {
rockets = queue;
public void add (Lif tOff lo) {
try {
rockets.put (lo) i
catch (InterruptedException e l {
print(fllnterrupted during put () II ) i
public void run(} {
try {
while ( ! Thread. interrupted () )
Li ftOff rocket = rocket s .take() i
rocket.run() II Utilizar esta hebra
catch( InterruptedException el
print ( "Waking fram take (l n) i
print (IfExiting LiftOffRunnerl! ) ;
publ ic class TestBlock i ngQueues
stat i c void getkey () {
try {
II Compensar las diferencias ent r e Windows/Linux en cuanto
I I a la longitud del resultado producido por la tecla Intro:
new BufferedReader(
new InputStrearnReader(System.in)) .readLine() i
catch(java.io.IOException el {
throw new RuntirneException(e) i
static void getkey{String message) {
prin"t (message ) i
getkeyO;
stat ic void
test(Stri ng msg, BlockingQueue<LiftOff> queue) {
print (msg) ;
LiftOffRunner runner = new LiftOffRunner(queue)
Thread t = new Thread{runner) i
t.startO;
for(int i = O; i < S; i++ )
r unner.add {new LiftOff {S)
getkey(uPress 'Enter
'
( " + msg + ,,) n);
t.interr upt() ;
prin t ("Fi nished " + msg + " test");
public s tatic void main(String(] args)
test ("LinkedBlockingQueue", / / Tamao i l imi t ado
new LinkedBlockingQueue<LiftOff>()} i
test ("ArrayBlockingQueue
ll
, JI Tamao fijo
new ArrayBlockingQueue<LiftOff >(3));
test ( 11 Synch:tonousQueue ", / / Tamao igual a 1
new SynchronousQueue<LiftOff>()) i
21 Concurrencia 797
Las tareas son introducidas en la cola BlockingQueue por maln( ) y son extradas de BlockingQueue por el objeto
LiftOflRunner. Observe que LiftOffRunner puede ignorar los problemas de sincronizacin porque stos son resueltos por
la cola BlockingQueue.
Ejercicio 28: (3) Modifique TestBlockingQueues.j ava aadiendo una nueva tarea que introduzca objetos LiftOff en la
col. BlockingQueue, en lugar de hacerlo en main( ).
Ejemplo de uso de BlockingQueue
Como ejemplo de utilizacin de las colas de tipo BlockingQueue, considere una mquina que tiene tres tareas: una para
hacer una tostada, otra para untarla de mantequilla y otra para poner mennelada sobre la tostada ya untada con mantequi-
lla. Podemos ir haciendo pasar la tostada por las distintas colas BlockingQueue que sirven de comunicacin entre los pro-
cesos:
/1: concurrency/ToastOMatic.java
/1 Una tostadora basada en colas.
i mport java.util .concurrent.*;
import java.util .*;
import static net.mindview. util.Print.*:
c I ass Toast {
}
pub l ic e nurn Status { DRY, BOTTERED, JAMMED }
private Statu s status Status.DRY;
private final int id;
publ ic Toast(int i dn) { id = idn; }
publ i c void butter( ) { status = Status . BUTTERBD;
public void jamO { status = Status.JAMMED; }
public Status getStatus () { return status; }
public int getldO { return id; }
public String toString () {
return UToast " + id + n. !I + status
798 Piensa en Java
class ToastQueue extends LinkedBlockingQueue<Toast> {}
class Toaster i mplements Runnable
prvate ToastQueue toastQueue
prvate int count = O;
private Random rand = new Random( 47) i
public Toaster (ToastQueue tq} { toastQueue
publ ic void run( ) {
try (
while(!Thread.interrupted(
TimeUnit.MILLISECONDS.sleep(
100 rand.next lnt(500 i
/1 Hacer t ostada
Toast t = new Toast(countTT) i
print (ti;
/1 Insertar en la cola
toastQueue.put(tl
catch(InterruptedException e)
print (nToaster int errupted 11) i
print ( flToaster off
n
);
/1 Untar de mantequilla la tostada:
c lass Bu t terer implements Runnabl e {
prvate ToastQueue dryQueue, butteredQueue
tq; )
public Butterer(ToastQueue dry, ToastQueue but tered)
dryQueue = dry;
but teredQueue = but tered
public void run ()
try (
while(!Thread.interrupted{))
II Se b l oquea hasta que haya otra tostada dispon ible:
Toast t = dryQueue.take{);
t .butter ()
print (ti;
butteredQueue.put(t l ;
catch(Int erruptedException el {
print("Butterer interrupted
ll
) i
print ( lIButterer off" ) ;
II Poner mermelada sobre la tostada untada de mantequilla:
c l ass Jammer implements Runnable {
prvate Toas tQueue butteredQueue, f inishedQueue;
public Jammer(ToastQueue buttered, ToastQueue finishedl
butteredQueue butt ered
finishedQueue = finished;
public void run ()
try (
while( !Thread .interrupted()) {
II Se bloquea hasta que haya ot ra t ostada disponible:
Toas t t : butteredQueue.take( } i
t. jam!) ;
print!t) ;
f i nishedQueue . put( t l ;
catch (I nterruptedException el
print (" Jammer interrupted
IT
) i
print(IlJammer o ff
ll
) i
JI Consumir la tostada:
class Eater implements Runnable
private ToastQueue finishedQueue;
private i nt count er = O;
public Eater(ToastQueue fi nished )
fi nishedQueue = fini shed
publ ic void run ()
try {
whi le ( !Thread.int errupted ()}
// Se bl oquea hasta que haya otra tos tada disponible:
Toas t t = finishedQueue .take ();
/1 Verif i car que l a tostada se ha preparado correctamente
ji y que todas l as t ostadas l l evan mermelada:
if(t.getld ll ! " counter++ 11
t.getStatus() ! = Toast . Status.JAMMED)
print(II: Error: 11 + tl i
System.exi t (1 );
el se
print("Chomp! n + t ) ;
catch(InterruptedException e )
print (n Eater interrupted");
print (IIEater of f" )
publi c c lass ToastOMatic {
public stat ic void main(String[] args) throws Exception
ToastQueue dryQueue = new ToastQueue( ) ,
but teredQueue = new ToastQueue( ),
finishedQueue = new ToastQueue()
ExecutorService exec : Executors.newCachedThreadPool() i
exec. execute(new Toaster(dryQueue));
exec.execute (new But terer(dryQueue, butteredQueue) ) ;
exec.execute(new Jarnmer(butteredQueue , f inishedQueue})
exec.execute(new Eater(finishedQueue i
TimeUnit . SECONDS .sleep(S) i
exec. shutdownNow () i
/ * (Ejecutar para ver la salida ) */// : -
21 Concurrencia 799
Toas! es un excelente ejemplo de la ventaja de emplear valores enum. Observe que no hay ninguna sincronizacin explci-
ta (utilizando objetos Lock o la palabra clave synchronized), porque la sincronizacin es gestionada de manera implcita
por la colas (que se sincronizan internamente) y por el diseo del sistema: cada objeto Toas! slo es manipulado por una
nica tarea cada vez. Puesto que las colas son bloqueantes, los procesos se suspenden y se reanudan automticamente.
800 Piensa en Java
Podemos ver que BlockingQueue puede simplificar el problema enormemente. El acoplamiento entre las clases que exis-
tira con instrucciones wait( ) y notifyAll( ) explcitas se elimina, porque cada clase se comunica slo con sus colas
BlockingQueue.
Ejercicio 29: (8) Modifique ToastOMatic.java para fabrcar bocadillos de mantequilla de cacabuete y mermelada, uti-
lizando dos lneas de fabricacin separadas (una para el pan con mantequilla, otra para el pan con merme-
lada y luego mezclando las dos lneas).
Utilizacin de canalizaciones para la E/S entre tareas
A menudo, resulta til que las tareas se comuniquen entre s utilizando mecanismos de E/S. Las bibliotecas de gestin de
hebras pueden proporcionar soporte para la E/S inter-tareas en la forma de canalizaciones (pipes). Estas canalizaciones exis-
ten en la biblioteca E/S de Java en forma de las clases PipedWriter (que permite a una tarea escribir en una canalizacin)
y PipedReader (que permite a otra tarea distinta leer de la misma canalizacin). Podramos considerar esto como una
variante del problema productor-consumidor, siendo la canalizacin una solucin prediseada. La canalizacin es bsi-
camente una cola bloqueante, y exista ya como solucin en las versiones de Java anteriores a la introduccin de
BlockingQueue.
He aqu un ejemplo simple en el que dos tareas utilizan una canalizacin para comunicarse:
/1: eoncurrency/PipedIO.java
/1 Utilizacin de canalizaciones para la E/S inter-tareas
import java.util.concurrent.*
import java.io.*;
import java.util.*
import static net.mindview.util.Print.*
class Sender implements Runnable {
private Random rand = new Random(47)
private PipedWriter out = new PipedWriter();
public PipedWri ter getPipedWriter () { return out }
public void run() {
try {
while (true)
for(char c = 'A' c <= 'z' c++) {
out. write (e) ;
TimeUnit.MILLISECONDS.sleep(rand.nextlnt(500))
catch(IOException e) {
print (e + " Sender write exception") i
eatch(InterruptedException e) {
print (e + " Sender sleep interrupted");
class Receiver implements Runnable {
private PipedReader in;
public Receiver(Sender sender) throws IOException
in = new PipedReader(sender.getPipedWriter())
public void run ()
try {
while(true) {
1/ Se bloquea hasta que haya caracteres:
printnb(rrRead: rr + (char)in.read() + n, 11);
catch(IOException e)
print(e + rr Receiver read exceptionrr);
public c l ass PipedIO {
public stat ic void main(String[] args) throws Exception {
Sender sender = new Sender()
Receiver receiver = new Receiver (sender )
Execut orService exec = Executors.newCachedThreadPool(}
exec.execute( sender) ;
exec. execute(receiver) ;
TimeUnit .SECONDS,s leep (4 ) ;
exec.shutdownNow() ;
/ * Output, ( 65\ match)
Read: A, Read: B, Read : e, Read : DI Read: E, Read: F, Read:
G, Read: H, Read: I, Read: J, Read: K, Read: L, Read: M,
java.lang.lnterruptedException: sleep interrupted Sender
sleep i n terrupted
java.io.lnterruptedIOException Receiver r ead exception
* /// , -
21 Concurrencia 801
Sender y Receiver representan tareas que necesitan comunicarse entre s. Sender crea tUl objeto escritor PipedWriter, que
es un objeto autnomo, pero dentro de Reeeiver la creacin del objeto lector PlpedReader debe asociarse con un objeto
PipedWriter dentro del constructor. Sender pone datos sobre el objeto Writer y duerme una cantidad aleatoria de tiempo.
Sin embargo, Receiver no tiene ningn mtodo sleep( ) o wait(). Sin embargo, cuando invoca el mtodo de lectura read( ),
la canalizacin se bloquea automticamente si no exi sten ms datos.
Observe que los objetos sender y recelver se inician en maln(), despus de que los objetos hayan sido completamente cons-
truidos. Si no comenzamos con objetos completamente construidos, la canalizacin puede presentar un comportamiento
incoherente en las distintas plataformas (observe que las colas de tipo BIockingQueue son ms robustas y fciles de usar).
Una diferencia importante entre un objeto PipedReader y la E/S normal es la que podemos ver cuando se invoca
sbutdownNow(): el lector PipedReader es interrumpible, mientras que si cambiramos, por ejemplo, la llamada in.read( )
por System.ln.read( ), la interrupcin interrupt( ) no conseguira salir de la llamada a read( ).
Ejercicio 30: (1) Modifique PipedIO.java para utilizar una cola BlockingQueue en lugar de una canalizacin.
Interbloqueo
A estas alturas ya sabemos que un objeto puede tener mtodos sincronizados u otras formas de bloqueo que impidan a las
tareas acceder a dicho objeto hasta que se libere el mutex. Tambin hemos visto que las tareas pueden bloquearse. Por tanto,
es posible que una tarea se quede esperando por otra tarea, que a su vez espere por otra tarea, etc., hasta que la cadena se
cierre de nuevo con una tarea que est esperando por la primera. En esta situacin, tendramos un ciclo continuo de tareas
esperando unas por otras y ninguna de ellas podra continuar con su procesamiento. Esta situacin se denomina interblo-
queo (dead/ock).21
Si tratamos de ejecutar un programa y se produce directamente un interbloqueo, podemos intentar localizar inmediatamen-
te el error. El problema real se produce cuando nuestro programa parece estar funcionamiento correctamente pero tiene la
posibilidad oculta de interbloquearse. En este caso, puede que no tengamos ninguna indicacin de que ese interbloqueo es
posible, as que el error estar latente en el programa hasta que se presente de manera inesperada cuando un cliente 10 eje-
cute (en una forma que casi siempre ser muy dificil de reproducir). Por tanto, la prevencin del interbloqueo mediante un
diseo cuidadoso del programa es una parte critica del desarrollo de sistemas concurrentes.
El problema de la cena de los filsofos, inventado por Edsger Dijkstra, es una ilustracin clsica del interbloqueo. La des-
cripcin bsica especifica cinco filsofos (aunque el ejemplo mostrado aql pennitira cualquier nmero de ellos). Estos
2] Tambin podemos tener lo que se denomina bloqueo activo (liveloc:k) cuando hay dos tareas que son capaces de cambiar su estado (no estn bloquea-
das), pero nunca consiguen realizar ningilO progreso til.
802 Piensa en Java
filsofos pasan parte de su tiempo pensando y parte de su tiempo comiendo. Mientras estn pensando, no necesitan ningn
recurso compartido, pero todos ellos comen usando un nmero limitado de utensilios. En la descripcin original del proble-
ma, los utensilios eran tenedores, y se requieren dos tenedores para tomar espagueti de una fuente situada en el centro de la
mesa, aunque parece que tiene ms sentido decir que esos utensilios sean palillos. Claramente, cada filsofo necesitar dos
palillos para poder comer.
Introducimos una dificultad en el problema: como filsofos, tienen muy poco dinero, as que slo pueden permitirse com-
prar cinco palillos (o ms generalmente, el mismo nmero de palillos que de filsofos). Esos palillos estn espaciados alre-
dedor de la mesa entre los filsofos. Cuando un filsofo quiere comer, debe tomar el palillo situado a su izquierda y el
situado a su derecha. Si alguno de los filsofos sentado a su lado est usando uno de los palillos que necesita, nuestro fil-
sofo deber esperar hasta que los palillos necesarios estn disponibles.
ji : concurrency/Chopstick. j ava
JI Palillos para la cena de los filsofos.
public class Chopstick {
private bool ean taken = false
public s ynchronized
void take() t hrows InterruptedException
whi l e (taken)
wait () i
taken = true
public synchronized voi d drop() {
taken = false;
notifyAll () ;
}
/// , -
No puede haber dos filsofos (objeto Philosopher) que tomen (con el mtodo take()) el mismo palillo (objeto Chopstick)
al mismo tiempo. Adems, si el objeto Chopstick ya ha sido tomado por un objeto Phllosopher, otro filsofo podr espe-
rar (wait()) hasta que el objeto Chopstick quede disponible cuando su propietario actual invoque drop() (soltar palillo).
Cuando una tarea Philosopher invoca take( ), esa tarea espera que el indicador taken (ocupado) valga false (es decir, hasta
que el objeto Pbllosopher que actualmente posee el objeto Chopstick lo libere). Entonces, la tarea asigna al indicador taken
el valor true para indicar que el nuevo objeto Philosopher posee ahora el objeto Cbopstick Cuando este objeto
Pbilosopher haya tenninado de usar el objeto Chopstick, invocar drope ) para cambiar el indicador y llamar a
notify AIl( } para notificar a todos los dems objetos Philosopher que puedan estar esperando a utilizar el objeto Chopstick
ji : concurrencyjPhilosopher. j ava
/1 Un fil sof o comensal
import java.util.concurrent.*
import java.util.*;
import static net.mindview.util,Print.*
public class Philosopher i mplement s Runnable
private Chopstick 1ef t;
private Chopstick right
private final int id;
privat e fina l int ponderFactorj
private Random rand = new Random(47)
pri vate void pause() throW9 InterruptedExcept ion
if (ponderFactor == O) return;
Time Unit.MILLISECONDS.sleep (
rand.nextlnt (ponderFactor * 250 )) ;
publi c Philosopher(Chopstick 1eft, Chopstick right,
int ident , int ponder ) {
this.left : left
this.right = right;
id =o ident
ponderFactor = ponder;
public void run() {
try (
while(!Thread.interrupted())
print{this + I! 11 + "thinking") i
pause() i
JI El filsofo tiene hambre
print{this + 11 + !1grabbing right") i
right.take() ;
print(this + + "grabbing 1eft") i
left. take () ;
print (this + + Ueating
H
) i
pause () i
right.drop() i
left . drop () ;
catch(InterruptedException el {
print (this + JI n + lIexiting via interrupt
ll
) i
public String toString () { return nphilosopher 11 + id; }
111,-
21 Concurrencia 803
En Philosopher.run( l, cada objeto Philosopher simplemente piensa y come de manera continua. El mtodo pause( l efec-
ta una llamada a sleeps( l durante un perodo aleatorio si el factor ponderFactor es distinto de cero. Utilizando esto, vemos
que el filsofo est pensando durante un intervalo de tiempo aleatorio y que luego intenta tomar los palillos izquierdo y dere-
cho, comiendo durante un intervalo de tiempo aleatorio, y luego volviendo a repetir el ciclo.
Ahora podemos escribir una versin del programa, versin que estar sometida al problema del interbloqueo:
JI: concurrency/DeadlockingDiningPhilosophers.java
/1 Ilustra cmo puede haber interbloqueos ocultos en un programa.
II {Args, O 5 timeout}
import java.util.concurrent.*;
public class DeadlockingDiningPhilosophers
public static void main(String[] args) throws Exception {
int ponder = 5;
if(args.length > O)
ponder = Integer.parselnt (args [O] );
int size = 5;
if(args.length > 1)
size = Integer.parselnt (args [1] );
ExecutorService exec = Executors.newCachedThreadPool();
Chopstick[] sticks = new Chopstick[size];
for(int i = O; i < size; i++)
sticks[iJ = new Chopstick() i
for(int i = O; i < size; i++)
exec.execute(new Philosopher(
sticks [i] I sticks [(i+1) % size] J i J ponder));
if (args .length == 3 && args [2] . equals ("timeout
l1
) }
TimeUnit.SECONDS.sleep(5) ;
el se (
System.out.println("Press 1 Enter
1
to quit
l1
);
System.in.read() ;
exec.shutdownNow() ;
/* (Ej ecutar para ver la salida) * / / /:-
804 Piensa en Java
Podr observar que si los objetos Philosopher pasan demasiado poco tiempo pensando, todos ellos competirn por los obje_
tos Chopstick cuando traten de comer, de modo que el interbloqueo se producir mucho ms rpidamente.
El primer argumento de la lnea de comandos ajusta el factor ponder, que afecta al intervalo de tiempo que cada objeto
Philosopber dedica a pensar. Si tenemos muchos objetos Philosopber o stos pasan una gran cantidad de tiempo pensan-
do, puede que nunca lleguemos a ver un problema de interbloqueo, aunque ste seguir siendo posible. Un argumento de la
lnea de comandos igual a cero tiende a hacer que el programa se interbloquee muy rpidamente.
Observe que los objetos Chopstick no necesitan identificadores internos; se los identifica por su posicin dentro de la matriz
stick,. A cada constructor Philosopber se le proporciona una referencia a sendos objetos Cbopstick izquierdo y derecho.
Cada objeto Philosopber excepto el ltimo se inicializa situando dicho objeto Pbilosopher entre el siguiente par de obje-
tos Chopstick. Al ltimo objeto Philosopher se le asigna el objeto Chopstick situado en la posicin cero como palillo dere-
cho, con lo que se completa la mesa redonda. Esto se debe a que el ltimo filsofo est situado justo alIado del primero y
ambos comparten ese palillo nmero cero. Ahora, ser posible que todos los objetos Pbilosopher traten de comer, quedan-
do todos ellos a la espera de que el obj eto Pbilosopher situado a continuacin de ellos libere su objeto Chopstick. Esto har
que el programa se interbloquee.
Si nuestros filsofos invierten ms tiempo pensando que comiendo, tendrn una posibilidad mucho menor de requerir los
recursos compartidos (los palillos), con lo que podramos quedarnos convencidos de que el programa no sufre interbloqueos
(utilizando un valor de ponder distinto de cero o un gran nmero de objetos Philosopber), aunque en realidad no es as.
Este ejemplo es interesante precisamente porque ilustra que un programa puede parecer estar ejecutndose correctamente y
sin embargo ser capaz de interbloquearse.
Para corregir el problema, es necesario entender que el interbloqueo puede producirse si se cumplen simultneamente cua-
tro condiciones:
1. Exclusin mutua. Al menos uno de los recursos utilizados por las tareas no debe ser compartible. En este caso,
un objeto Chopstick slo puede ser utilizado por un objeto Philosopher cada vez.
2. Al menos una tarea debe poseer un recurso y estar esperando a adquirir otro recurso que actualmente es propie-
dad de otra tarea. Es decir, para que el interbloqueo se produzca, un objeto Philosopher deber poseer un objeto
Chopstick y estar esperando a adquirir otro.
3. Los recursos no pueden ser desalojados de una tarea. La liberacin de recursos por parte de tareas slo puede pro-
ducirse como un suceso normal. En otras palabras, nuestros filsofos son muy educados y no arrebatan los pali-
llos a los otros filsofos.
4. Puede producJrse una espera circular, segn la cual una tarea estar esperando por un recurso que posee otra tarea,
que a su vez estar esperando por un recurso que posee otra tarea, y as sucesivamente, hasta que una de las ta-
reas est esperando por un recurso posedo por la primera tarea, lo que hace que se produzca una cadena circular
de bloqueo. En DeadlockingDiningPbilosopbers.java, esta espera circular se produce porque cada fi lsofo trata
de obtener primero el palillo derecho y luego el izquierdo.
Como todas estas condiciones deben producirse para provocar un interbloqueo, basta con que impidamos que una de ellas
se produzca para conseguir que no haya interbloqueos. En este programa, la forma ms fcil de impedir el interbloqueo con-
siste en impedir que se d la cuarta condicin. Esta condicin sucede porque cada filsofo trata de tomar los correspondien-
tes palillos en un orden concreto, primero el derecho y luego el izquierdo. Debido a ello, resulta posible encontrarse en una
situacin en la que cada uno de los filsofos haya tomado su palillo derecho y est esperando a poder conseguir el izquier-
do, provocando la aparicin de la condicin de espera circular. Sin embargo, si inicializamos el ltimo filsofo para que trate
de obtener primero el palillo izquierdo y luego el derecho, ese filsofo nunca impedir que el filsofo situado inmediata-
mente a su derecha tome los dos palillos que necesita. En este caso, se impide la espera circular. sta slo es una de las posi-
bles soluciones del problema; tambin podramos evitar el problema impidiendo que se cumpla alguna de las otras
condiciones (consulte algn otro libro para conocer ms detalles sobre la gestin avanzada de hebras):
11: concurrency/FixedDiningPh i l osophers. java
II La cena de l os filsofos sin i nterbloqueo.
// {Args, 5 5 timeout}
import java.util.concurrent.*
public class FixedDiningPhilosophers
public s tatic void mai n (String [] a r gs ) t hrows Exception {
i nt ponder = 5;
if (args .length > O)
ponder = Integer.parselnt (args (O ] ) i
int s i ze = Si
if(args. l ength > 1)
size = Integer.parselnt (args [1] ) i
ExecutorService exec = Execut ors.newCachedThreadPool() i
Chopst ick [] sticks = n ew Chopstick [51ze] ;
for(int i = O; i < size; i ++)
st i cks[i ] = new Chopstick (} i
for (i nt i = O; i < size i+T}
if (i < (size- 1 )
exec.execute (new Phi losopher (
s ti c ks [i ] , sticks [i +1], i, ponder )) ;
eI se
exec . execute( new Phi l osopher (
st i cks [ O] f sticks [i ] , i , ponder ) ;
if (a r gs.length == 3 && args[ 2] . equal s ( "t i meout ") )
TimeUnit.SECONDS . sleep(S ) ;
else {
System. out . println (n Press r Enter r to gui t!l) i
System.in . read() i
exec.shutdownNow() i
/* (Ej ecutar para ver la salida) * / / /:-
21 Concurrencia 805
Garantizando que el ltimo filsofo tome y dej e el palillo izquierdo antes que el derecho, eliminamos el interbloqueo y el
programa funcionar sin problemas.
No hay ningn soporte del lenguaje para ayudarnos a prevenir el interbloqueo; es un problema que deberemos resolver noso-
tros mismos real izando un diseo cuidadoso. Ya s que estas palabras no sirven de mucho consuelo a aquellas personas que
estn tratando de depurar un programa sometido al interbloqueo.
Ejercicio 31: (8) Cambie DeadJockingDiningPhilosophers.java de modo que, cuando un filsofo haya tenuinado de
emplear sus palillos, los deje en una bandeja. Cuando un filsofo quiera comer, tomar los dos palillos
siguientes que estn disponibles en la bandeja. Elimina esto la posibilidad del interbloqueo? Podemos
reintroducir el interbloqueo simplemente reduciendo el nmero de palillos disponibles?
Nuevos componentes de biblioteca
La biblioteca java.util.concurrent de Java SES introduce un nmero significativo de nuevas clases diseadas para resolver
los problemas de concurrencia. Aprender a emplear estas clases puede ayudamos a escribir programas concurrentes ms
simples y robustos.
Esta seccin incluye un conjunto representativo de ejemplos de diversos componentes, aunque algunos de los componentes
no los analizaremos aqu (aquellos que es menos probable que el lector se encuentre a la hora de analizar programas o uti-
lice a la hora de escribirlos).
Puesto que estos componentes permiten resolver varios problemas, no existe una forma clara de organizarlos, as que trata-
r de comenzar con ejemplos sencillos y continuar con una serie de ejemplos de creciente complejidad.
CountDownLatch
Esta clase se usa para sincronizar una o ms tareas forzndolas a esperar a que se complete un conjlll1l0 de operaciones que
estn siendo realizadas por otras tareas.
Al objeto CountDownLatch le proporcionamos un valor de recuento inicial y cualquier tarea que invoque await() sobre
dicho objeto se bloquear hasta que el recuento alcance el valor cero. Otras tareas pueden invocar countDown( ) sobre el
806 Piensa en Java
objeto para reducir el valor de recuento, presumiblemente cuando esas tareas finalicen su trabajo. CountDownLatch est
diseado para util izarlo una sola vez, el valor de recuento DO puede reinicializarse. Si necesitamos una versin donde pueda
reinicializar el valor de recuento, podemos emplear en su lugar CyclicBarrier.
Las tareas que invocan countDown() no se bloquean cuando hacen esa llamada. Slo la llamada a await( ) se bloquea hasta
que el recuento alcanza el valor cero.
Un uso normal consiste en dividir un problema en n tareas independientemente resolubles y crear un objeto CountDown_
Latch con un valor n. Cuando cada una de las tareas finaliza invoca countDown() para el objeto encargado del recuento.
Las tareas que estn esperando a que el problema se resuelva pueden invocar a awail() sobre el objeto encargado del recuen-
lo para esperar a que el problema est completamente resuelto. He aqu un esqueleto de ejemplo que ilustra esta tcnica:
JI : concurrencyjCountDownLatchDemo . j ava
i mport java.util . concurrent.*
import java.util.*;
import static net.mindvi ew.util.Print.*
// Realiza cierta parte de una tarea :
cIass TaskPortlon i mplements Runnable
prvate static i nt counter = O;
prvate f i nal int id counter++
prvate static Random rand = new Random(47)
private fi nal CountDownLat ch latch
TaskPortion(countDownLat ch l a tch) {
this.latch = latch
public void run ()
try {
doWork() ;
latch .countDown()j
catch (InterruptedException ex)
II Forma aceptable de salir
public voi d doWork () throws InterruptedException {
TimeUni t. MILLISECONDS. s l eep(rand . nextlnt(2000) ;
print (t h is + "compl eted
ll
) ;
public String toString () {
return St ring . format (II %1$ -3d id) i
II Espera sobre CountDownLatch:
class Wai t ingTask implement s Runnable
prvate static i nt count er = O;
prvate fi nal int id = count er++
private final CountDownLatch l atch
Wait ingTask(CountDownLat ch l atch) {
thi s. latch = latch
}
public void run() {
try {
latch.awai t() ;
pri nt ( "Latch barri er passed f or " + thi s);
catch{Interr uptedExcept ion ex)
print(this + !1 nterrupted
n
) i
public String toString () {
return String . format (IIWaitingTask %1$-3d id) ;
public class CountDawnLatchDemo {
s tatic final i n t SI ZE = 100;
public s tatic void main(String[] argsl throws Exception {
ExecutorService exec = Executors.newCachedThreadPool( ) ;
/ / Todos deben compartir un nico objeto CountDownLatch:
CountDownLatch latch = new CountDownLatch(SIZEl
for(int i = O; i < 10; i+-1''' )
exec.execute(new WaitingTask(latch} ) i
for(int i = O; i < S IZE i ++ )
exec.execute(new TaskPortion(latch;
print ( IILaunched a 11 tasks" ) i
21 Concurrencia 807
exec. shutdown{); JI Sal i r cuando todas las tareas se hayan c ompletado
/ * (Ejecu tar para ver la sal ida) */// : -
TaskPortion duenne durante uu perodo aleatorio para simular la tenninacin de parte del proceso, y WaitingTask indica
que una parte del sistema tiene que esperar hasta que el problema inicial se haya completado. Todas las tareas funcionan con
el mismo contador CountDownLatch, que se define en main( ).
Ejercicio 32: (7) Utilice un objeto CountDownLatch para resolver el problema de correlacin de los resultados de las
distintas entradas de OrnamentalGarden.java. Elimine el cdigo innecesario en la nueva versin del
ejemplo.
Seguridad de las hebras en la biblioteca
Observe que TaskPortion contiene un objeto esttico Random, lo que significa que puede haber mltiples tareas invocan-
do Random.nextInt( ) al mismo tiempo. Es esto seguro?
Si existe un problema, puede resolverse en este caso asignando dos TaskPortion en su propio objeto Random, es decir, eli-
minando el especificado static. Pero esa misma cuestin seguir siendo pertinente, con carcter general, para todos los mto-
dos de la biblioteca estndar de Java: cules de esos mtodos son seguros de cara a las hebras y cules no lo son?
Lamentablemente, la documentacin del JDK no es muy explicativa en este aspecto. Resulta que Random.nextInt( ) s que
es seguro respecto a las hebras, pero es necesario descubrir en cada caso si un cierto mtodolo es, efectuando una bsque-
da en la Web o inspeccionando el cdigo de la biblioteca Java. Evidentemente, esta situacin no resulta particularmente
atractiva para un lenguaje de programacin que est diseado, al menos en teora, para soportar la concurrencia.
CyclicBarrier
La clase CyclicBarrier (barrera circular) se utiliza en aquellas situaciones en las que queremos crear un grupo de tareas para
realizar un cierto trabajo en paralelo, y luego esperar a que todas hayan finalizado, antes de continuar con el signiente paso
. (algo parecido a join( ), podramos decir). Esta clase alinea todas las tareas paralelas en la barrera circular, de modo que
podamos continuar hacia adelante al unsono. Se trata de una solucin muy similar a CountDo"nLatch, excepto porque
ConntDownLatch representa un suceso que slo se produce una vez, mientras que CyclicBarrer puede reutilizarse
muchas veces.
Las simulaciones me han fascinado desde que empec a utilizar computadoras y la concurrencia es un factor clave a la hora
de realizar simulaciones. El primer programa que recuerdo haber escrito
22
era una simulacin: un juego de carreras de caba-
llos escrito en BASIC y denominado (debido a las limitaciones de los nombres de archivos) HOSRAC.BAS. He aqulla ver-
sin orientada a objetos y con hebras de dicho programa, utilizando una clase CyclicBarrier:
11: concurrency/HorseRace.java
1I Utilizacin de Cycl icBarrier.
22 Cuando estaba en la universidad; en la laboratorio disponamos de un teletipo SR-33 con un nodo de acoplamiento de acstico de 110 baudios median-
te el que se acceda a una computadora HP-lOOO.
808 Piensa en Java
impor t java.util.concurrent.*
import java.util.*
i mport static net . mi ndview.util.Print.*
c I ass Harse implements Runnable {
prvat e sta tic int counter = O;
private final i nt id = counter++
private int strides = O:
prvate static Random rand = new Random(47 ) ;
private static CyclicBarrier barrier
public Horse (CyclicBarrier b) { bar rier = b;
public synchroni zed int getSt rides () { return strides;
publ ic void r un( ) (
try (
while ( !Th read. i n terrupted() )
synchronized (this ) {
s t rides += rand.nextInt(3) JI Produce O, 102
barrier.await() i
catch(InterruptedException el {
/1 Una forma leg tima de sal ir
catch(BrokenBarrierException el
/1 sta queremos probarla
throw new RuntimeException(e);
publ ic String taString () { return IIHorse ti + id + " 11
public String t racks () {
StringBuilder s = new StringBuilder();
for( i nt i = O; i < getStri des() ; i++)
s.append ( II *") i
s . append (id) ;
return s.toString ()
publ i c class Hor seRace {
static final i nt FINISH_LINE = 75;
private List <Horse > harses = new ArrayLi s t<Horse >() ;
private ExecutorServi ce exec
Executars.newCachedThreadPool() ;
private CyclicBarrier barrier;
public HorseRace(int nHorses, final int pause) {
barrier = new CyclicBarrier(nHorses, new Runnable ()
public void run() (
StringBuilder s = new StringBuilder();
for( i nt i = O; i < FINISH_LINE i ++ )
s . append ( "=" ); / / La val la en el hipdromo
print (s ) ;
f or(Horse harse : harses)
pri nt (horse.tracks () ;
for(Harse h9rse : horses)
if (horse.getStrides () >= FINISH_LINE l
print (harse + "won !" );
exec .shutdownNow() ;
return;
try
TimeUni t.MILLISECONDS .sleep(pause) i
}
}) ;
catch(InterruptedException el {
print (Ubarrier-action sleep interruptedl!);
for(int i = Di i < nHorses i++l
Harse harse = new Horse(barrier);
horses.add(horse) i
exec.execute(horse)
public static void main (String [] args) {
int nHorses = 7 i
int pause:;:: 200;
if (args .length > O) { / / Argumento opcional
int TI = new Integer(args[O]) i
nRorses :;:: TI > O ? TI : nHorses
if (args .1ength > 1) { / / Argumento opcional
int p new Integer(args[l]) j
pause :;:: p > -1 ? P : pause
new HorseRace(nHorses, pause)
/* (Ejecutar para ver la salida) *///:-
21 Concurrencia 809
Un objeto CyclicBarrier puede proporcionar una "accin de barrera", que es un objeto Runnable que se ejecuta autom-
ticamente cuando el contador alcanza el valor cero; sta es otra distincin entre Cyc1icBarrier y CountdownLatch. Aqu,
la accin de barrera se defme mediante una clase annima que se entrega al constructor de CyclicBarrier.
Intent que cada caballo pudiera imprimirse a s mismo, pero el orden de visualizacin dependa del gestor de tareas.
CyclicBarrier permite que cada caballo haga lo que necesita hacer para poder continuar adelante, y luego tiene que espe-
rar en la barrera hasta que todos los dems caballos hayan avanzado. Cuando todos los caballos se han desplazado,
CyclicBarrier llama automticamente a su objeto Runnable que define la accin de barrera para mostrar los caballos por
orden junto con la valla.
Una vez que todas las tareas han pasado la barrera, sta estar automticamente lista para la siguiente ronda.
Para obtener el efecto de una animacin muy simple, reduzca el tamao de la consola para que slo se muestren los caballos.
DelayQueue
Se trata de una cola BlockingQueue de objetos sin limitacin de tamao que implementa la interfaz Delayed. Un objeto s-
lo puede ser extrado de la cola una vez que su retardo haya trauscurrido. La cola est ordenada, de modo que el objeto situa-
do al principio es el que tiene el retardo que ha transcurrido hace ms tiempo. Si no ha transcurrido ninguno de los retardos,
no habr ningn elemento de cabecera y el mtodo poll( ) devolver null (debido a esto, no pueden insertarse elementos
null en la cola).
He aqu un ejemplo donde los objetos Delayed son tareas y el consumidor DelayedTaskConsumer extrae la tarea ms
"urgente" (aquella cuyo retardo haya caducado hace ms tiempo) de la cola y la ejecuta. Observe que DelayQueue es, por
tanto, una variante de una cola con prioridad.
jj: concurrencyjDelayQueueDemo.java
import java.util.concurrent.*
import java.util.*
import static java.util.concurrent.TimeUnit.*
import static net.mindview.util.Print.*
class DelayedTask implements Runnable, Delayed
private static int counter = O;
private final int id = counter++
810 Piensa en Java
private final int de l t a;
private f i na l long trigger;
protected static Lis t<DelayedTask> sequence
new ArrayList<DelayedTask>{) i
public DelayedTask(int delayl nMilliseconds)
delta = delaylnMilliseconds
trigger = System.nanoTime{) +
NANOSECONDS.convert(delta, MILLI SECONDS);
sequence.add{this) ;
public l ong getDelay(TimeUnit unit) {
return unit.convert(
trigger - System.nanoTime () , NANOSECONDS )
public int compareTo (Delayed arg) {
DelayedTask that = (DelayedTask) arg
if(trigger < that.trigger) return - li
if (trigger > that.trigger) return 1 ;
return O;
publ ic void run () { printnb (this + 11 11);
public String toString () {
return String.format{1I [%1$ - 4d] ", delta) +
" Task IJ + id
public String summary()
return IJ (IJ + id + II:!I + delta + !I) 11 i
publ ic static cIass EndSentinel extends DelayedTask {
private ExecutorService exec
public EndSentinel(int delay, Execut or Service e l {
super (delay) j
exec = e
public void run () (
for(DelayedTask pt : sequence)
printnb (pt . summary () + 11 !I )
print () ;
print (this + 11 Calling shutdownNow () II} ;
exec.shutdownNow()
c lass DelayedTaskConsumer implemente Runnable {
private DelayQueue<DelayedTask> q
public DelayedTaskConsumer {DelayQueue<DelayedTask> q ) {
this . q = q;
public void run ()
try (
whi l e(!Thread.interrupted( ))
q.take{) .run () ; // Ejecutar tarea con la hebra actual
catch(InterruptedException e)
// Forma aceptable de salir
print ("Finished DelayedTaskConsumer") j
public class DelayQueueDemo {
public static void main(String[] args) {
Random rand new Random(47);
ExecutorService exec = Executors.newCachedThreadPool();
DelayQueue<DelayedTask> queue =
new DelayQueue<DelayedTask>() i
JI Rellenar con tareas que tengan retardos aleatorios:
for{int i = O; i < 20; i++)
queue.put(new DelayedTask(rand.nextlnt(500D))) i
/1 Establecer el punto de detencin
queue.add(new DelayedTask.EndSentinel(SOOO, exec)} i
exec.execute(new DelayedTaskConsumer(queue)) i
/* Output:
[128 ] Task 11 [200 ] Task 7 [429 ] Task 5 [520 ] Task 18
[555 ] Task 1 [961 ] Task 4 [998 ] Task 16 [1207] Task 9
[1693] Task 2 [1809] Task 14 [1861] Task 3 [2278] Task 15
[3288] Task 10 [3551] Task 12 [4258] Task O [4258] Task 19
[4522] Task 8 [4589] Task 13 [4861] Task 17 [4868] Task 6
(0,4258) (1,555) (2,1693) (3,1861) (4,961) (5,429) (6,4868)
(7,200) (8,4522) (9,1207) (10,3288) (11,128) (12,3551)
(13,4589) (14,1809) (15,2278) (16,998) (17,4861) (18,520)
(19,4258) (20,5000)
[5000J Task 20 Calling shutdownNow()
Finished DelayedTaskConsumer
*///,-
21 Concurrencia 811
DelayedTask contiene nna lista List<DelayedTask> denominada sequenee que preserva el orden en que fueron creadas las
tareas, para poder comprobar que efectivamente se est realizando lUla ordenacin.
La interfaz Delayed tiene un mtodo, getDelay( ), que dice cunto queda para que caduque el retardo o cunto tiempo hace
que ha caducado. Este mtodo nos fuerza a utilizar la clase TImeUnit porque es el argumento de tipo. Esta clase resulta ser
bastante til, porque podemos convertir fcilmente las unidades sin realizar ningn clculo. Por ejemplo, el valor de delta
se almacena en milisegundos, pero el mtodo System.nanoTIme( ) de Java SE5 devuelve el tiempo en nanosegundos.
Podemos convertir el valor de delta indicando en qu unidades est y en qu nnidades lo queremos como en el ejemplo
siguiente:
NANOSECONDS.convert(delta, MILLISECONDS) i
En getDelay( ), pasamos las nnidades deseadas como argrnnento unit, y las usamos para convertir el intervalo de tiempo
transcurrido desde el instante de disparo a las nnidades solicitadas por elllamante, sin siquiera tener por qu conocer qu
nnidades son esas (ste es un ej emplo simple del patrn de diseo Estrategia, segn el cual parte del algoritmo se pasa como
argumento).
Para la ordenacin, la interfaz Delayed tambin hereda la interfaz Comparable, as que habr que implementar
eompareTo( ) para que realice uua comparacin razonable. toString( ) y summary( ) se encargan del formateo de la sali-
da y la clase anidada EndSentinel proporciona nna forma de terminar todo, colocndola como ltimo elemento de la cola.
Observe que, como DelayedTaskConsumer es ella misma una tarea, dispone de su propio objeto Thread que puede usar
para ejecutar cada tarea que se extraiga de la cola. Puesto que las tareas se estn ejecutando segn el orden de prioridad de
la cola, no hay necesidad en este ejemplo de iniciar hebras separadas para ejecutar las tareas DelayedTask.
Podemos ver, analizando la salida, que el orden en que se crean las tareas no tiene ningn efecto sobre el orden de ejecu-
cin, en lugar de ello, las tareas se ejecutan segn el orden de sus retardos, como cabra esperar.
PriorityBlockingQueue
Se trata, bsicamente, de una cola con prioridad que tiene operaciones de extraccin bloqueantes. He aqu un ejemplo en el
que los objetos de la cola con prioridad son tareas que salen de la cola segn el orden de prioridad. A cada tarea con priori-
dad PrioritizedTask se le proporciona nn nmero de prioridad para fijar el orden:
812 Piensa en Java
11 : concurrency/PriorityBlockingQueueDemo.java
import java . uti l . concurrent. *
i mport java . util .*
import static net.mindv iew.ut il.Print.*
class PrioritizedTask implements
Runnable, Comparable<PrioritizedTask>
pri vate Random rand = new Random(47)
private stat ic int counter = O
pri vate final int id = counter++i
private fi nal int prioritYi
protected static List<PrioritizedTask> sequence
new ArrayList<Pr ioritizedTa s k >()
pUblic Prior i tizedTask (i nt priority)
t hi s.priority = prioritYi
sequenee.add (thi s) ;
public int compareTo ( PrioritizedTasK arg)
return priority < arg.priority ? 1 :
(priority > arg.priori ty ? - 1 : O) i
publ i e void run ( )
try (
TimeUnit.MILLISECONDS. sleep(rand.next Int (250) ;
catch(InterruptedExcept i on el {
II Forma aceptabl e de salir
print (this) ;
publ ic St r ing toStri ng ()
return String .format (II [%1$- 3dl " , priori t y ) +
11 Task 11 + id;
public String summary ()
return " ( II + id + ":" + pri or ity + "l";
public static c l ass EndSent inel extends Prioriti zedTask
private ExecutorService exec
public EndSentinel(ExecutorServi ce el
super(-l); II La menor prioridad en este programa
exec = e;
public void run ()
int count = Oi
for (PrioritizedTask pt : sequence ) {
printnb (pt .summary (
if( ++count % 5 == O)
prin t () ;
print () ;
print (this + " Calling s hu tdownNow() " ) ;
exec.shut downNow () ;
c l ass Priorit izedTaskProducer implements Runnable {
private Random rand = new Random(47)
private Queue<Runnable> queue;
prvate ExecutorService exec
public Pr i or i tizedTaskProducer(
Queue<Runnable> q, ExecutorServi ce e ) {
queue :: q
exec = e JI Utilizado para BndSentinel
public void run() {
/1 Cola no limitada; nunca se bloquea.
JI Rellenarla rpidamente con prioridades aleatorias:
for (in t i :: O; i < 20; i++ ) {
queue . add (new PrioritizedTask(rand.nextlnt(lO)
Thread. yield () ;
/1 Introducir tareas de prior idad mxima:
try {
for(int i O; i < 10; i++l
TimeUni t .MILLISECONDS.sleep(2S0) i
queue.add(new Priorit izedTask( l O});
/1 Aadir tareas, primero l as de menor prioridad:
for[ i nt i = O; i < 1 0 ; i+. )
queue . add(new PrioritizedTask(i));
JI Un centinela para detener todas las tareas :
queue.add(new Priori tizedTask.EndSenti ne l (exec))
catch( I nterruptedException e) {
// For ma acept abl e de salir
print ( " Finis hed PrioritizedTaskProducer
ll
) i
class PrioritizedTaskConsumer implements Runnable
private PriorityBlockingQueue<Runnable> q
public PrioritizedTaskConsumer{
PriorityBl ockingQueue<Runnable> q ) {
this.q = q
public void run ()
try {
while(!Thread .interrupted())
// Utilizar la hebra actual para ejecutar la tarea:
q. take () . run () ;
catch ( Interr uptedException e)
// Forma aceptable de sali r
print(IIFinished PrioritizedTaskConsumer" )
public class PriorityBlockingQueueDemo {
public static void main{String (] args) throws Exception {
Random rand = new Random(47 )
Execut orServi ce exec = Executors . newCachedThreadPool( }
Pri orityBlockingQueue<Runnable> queue =
new Pri orityBlockingQueue<Runnable> ( )
exec.execute(new PrioritizedTaskProducer(queue, exec));
exec. execute(new PrioritizedTaskConsumer(queue))
/ * (Ejecutar para ver la salida) * /// :-
21 Concurrencia 813
814 Piensa en Java
Al igual que en el ejemplo anterior, la secuencia de creacin de los objetos PrioritizedTask se almacena en la lista sequen_
ce, para compararla con el orden real de ejecucin. El mtodo run( ) durante un corto perodo de tiempo aleatorio imprime
la informacin del objeto, mientras que EndSentinel proporciona la misma funcionalidad que antes al garantizar que es el
ltimo objeto de la cola.
Los objetos PrioritizedTaskProducer y PrioritizedTaskConsumer se interconectan a travs de una cola Priority_
BlockingQueue. Puesto que la naturaleza bloqueante de la cola proporciona todos los mecanismos necesarios de sincroni-
zacin, observe que no hace falta ninguna sincronizacin explicita: no tenemos que preocupamos por si la cola tiene algn
elemento en el momento de leer de ella, porque sta simplemente bloquear al lector cuando no tenga ningn elemento.
El controlador de invernadero implementado con ScheduledExecutor
En el Captulo 10, Clases internas, introdujimos el ejemplo de un sistema de control aplicado a un invernadero hipottico,
donde se encendian y apagaban diversos elemenlos o se los ajustaba de alguna manera. ste puede verse como un tipo de
problema de concurrencia, siendo cada suceso deseado en el invernadero una tarea que se ejecuta en un instante predefini-
do. La cIase ScheduledThreadPooIExecutor proporciona el servicio necesario para resolver el problema. Utilizando sche-
dule() (para ejecutar una tarea una vez) o scheduleAtFixedRate() (para repetir una tarea a intervalos regulares),
configuramos objetos Runnable que haya que ejecutar en un cierto instante futuro. Compare el cdigo siguiente con la tc-
nica utilizada en el Captulo 10, Clases internas, para observar cmo se simplifican las cosas cuando podemos emplear una
herramienta predefinida como ScheduledThreadPooIExecutor:
/1: concurrency/GreenhouseScheduler. java
// Reescri tura de innerclasses/GreenhouseController.java
// para usar la clase ScheduledThreadPoolExecutor.
II {Args, SOOO}
i mport java . uti l .concurrent.* i
import java.util.*;
public class GreenhouseSchedul er
private volatile boolean light
private volatile boolean water
false;
false
pri vate String thermostat = "Dayll;
public synchronized String getThermostat()
return thermostat
public synchroni zed void setThermos tat(String value) {
thermostat = value
ScheduledThreadPoolExecutor scheduler =
new ScheduledThreadPoolExecutor(lO) i
public void schedule(Runnable event, long delay} {
scheduler.schedule(event ,delay,TimeUnit.MILLISECONDS) i
public void
repeat(Runnabl e event , long initialDelay, l ong periad) {
scheduler.scheduleAtFixedRate {
event initialDelay, period, TimeUnit.MILLISECONDS);
class LightOn implements Runnable {
public void run() {
II Incluir aqu e l cdigo de control de l hardware
II para encender fsi camente la ilumi nacin.
System.out.println ( II Turning on l ights " );
light = true;
class LightOff implements Runnable {
publi c void run() {
II Incl uir aqu el cdigo de control del hardware
II para apagar fsicamente l a i luminacin.
Sys t em. out. pri ntln (IITurning off l ights") j
light = false;
class WaterOn implements Runnable (
public void run() {
II Incluir aqu el cdigo de control del hardware .
System. out .println (IITurning greenhouse water onU) j
water = true;
class WaterOff implements Runnable {
public void runll {
II Incluir aqu el cdigo de control del hardware .
System. out .println ("Turning greenhouse water off U) ;
water = false;
class ThermostatNight impl ements Runnable
public void run() {
II Incluir aqu el cdigo de cont rol del hardware .
Syst em.out .println (IIThermostat to night setting
Jl
);
setThermostat ( IlNight n ) ;
class ThermostatDay impl ernents Runnable (
public void run() (
II Incl uir aqu el cdigo de control del hardware.
System.out.println(IIThermostat to day setting
ll
);
setThermostat (nDayll) ;
class Bell i mplements Runnable (
publi c void run() ( System.out.print l n {"Bing !" );
c l ass Termi nate implements Runnabl e (
public void run() (
System. out .println (IITerminating
lt
) ;
schedul er.shutdownNow () j
II Hay que iniciar una tarea separada para hacer este trabajo,
II ya que el planificador ha sido terminado:
new Thread Il (
public void run( ) (
for(DataPoint d : data )
System.out.println {d ) i
}
} .startll;
II Nueva ca racter stica: recopi l acin de datos
sta tic clas s DataPoi nt (
final Calendar t ime;
final float temperature;
final float humiditYi
public DataPoint{Calendar d, float temp, float hum) {
time '" di
temperature = tempi
21 Concurrencia 815
816 Piensa en Java
}
humidi t y '" hum;
public String toString( )
r eturn time .getTime() +
String . format (
u t emperature : %1$ . lf humidity: %2$ .2f
u
,
temperatur e, humidity) i
prvate Calendar lastTime '" Calendar.getlnsta nce() j
{ 11 Ajustar la hora con medias horas
lastTime.set (Calendar.MlNUTE, 30) i
lastTime.set(Cal endar . SECOND, 00) i
private float l astTemp = 65.0f
private int tempDi r ection = +1;
private float l ast Humidity = 50 .0f;
private int humidityDirect ion = +1 ;
private Random rand = new Random(4 7);
List<DataPoint> data = Co11ections.synchronizedLi st(
new ArrayLi s t<DataPoint >(})
c l ass Colle c t Data implements Runnable {
public void run ( ) {
System.out.print1n( UCollecting data
U
) ;
sync hronized(GreenhouseScheduler.this)
11 Si mular que el interval o es mayor de lo que e s :
lastTi me.set(Calendar.Ml NUTE,
l astTime. get(Calendar.MI NUTE) + 30);
1/ Una oportunidad entre 5 de invertir la d i r e ccin:
if ( rand.next lnt (5) == 4)
tempDi rection = - tempDirectioD;
1/ Almacenar va l o r ant erior:
last Temp = l astTemp +
tempDi r ection * (l . Of + rand. n ext Float()) ;
if (rand .nextlnt (5 ) == 4 )
humi dityDirection = -humi d i tyDirection;
l as t Humidity = lastHumidity +
humi dityDirection * rand.nextFloat();
1/ Es preci s o clonar Cal endar, ya que en caso contrario todos
11 los puntos de datos a l macenaran referencias al mismo va l or
1/ l astTime. Para un objeto bs i co c omo Calendar , clone(} es OK.
data .add(new Da t aPoint( (Cal endar) lastTime.clone ( ) ,
l as tTemp, lastHumidity) i
public s t atic void main(String[ ] args ) {
Green houseSchedul er g h = new GreenhouseScheduler ();
gh .schedule(gh.new Terminate() , 5 000);
// La ant eri or clase "Restart
ll
no es necesaria:
gh.repeat(gh .new Bel l( ) , O, 1000 );
gh.repeat(gh.new ThermostatNight() , O, 2000);
g h .repeat(gh . new LightOn(), O, 200);
gh.repeat(gh . new LightOff (}, O, 4 00)
gh.repeat( g h .new WaterOn(), O, 600)
gh.repeat( gh.new WaterOff(}, O, SOO};
gh.repeat(gh.new ThermostatDay(), O, 1400)
gh.rep eat(gh.new Col l ectData(), 500, 500) i
1* (Ejecu tar para v e r la salida) */11:_
21 Concurrencia 817
Esta versin reorganiza el cdigo y aade una nueva caracterstica: recopilar las lecturas de temperatura y humedad en el
invernadero. Cada objeto DataPoint (punto de datos) almacena y visualiza un nico dato, mientras que CollectData es la
tarea planificada que genera los elementos simulados y los aade al contenedor List<DataPoint> de Greenhouse cada vez
que se la ejecuta.
Observe el uso tanto de vol.tiIe como de synchronized en los lugares apropiados, para impedir que las tareas interfieran
unas con otras. Todos los mtodos de la lista que almacena los objetos DataPoint se sincronizan empleando la utilidad
synchronizedList( ) de java.util.Collections en el momento de crear la lista.
Ejercicio 33: (7) Modifique GreenhouseScheduler.java para que utilice un objeto DelayQueue en lugar de
ScheduledExecutor.
Semaphore
Un bloqueo normal (de concurrent.locks o el bloqueo integrado synchronized) slo permite a una nica tarea acceder a un
recurso cada vez. Un semforo contador pennite que n tareas accedan al recurso simultneamente. Tambin podemos pen-
sar en un semforo como algo que entrega "permisos" para usar un recurso, aunque en realidad no se utiliza ningn objeto
penniso.
Como ejemplo, consideremos el concepto de conjunto compartido de objetos, que gestiona un .nmero limitado de objetos
permitiendo que esos objetos se extraigan del conjunto para utilizarlos y se devuelvan una vez que el usuari o haya termina-
do con ellos. Esta funcionalidad puede encapsularse en una clase genrica:
ji: concurrency/Pool .java
JI Utilizacin de un semforo dentro de conjunto compart ido,
JI para restringir el nmero de t areas q ue pueden usar un recurso .
mp o rt java . u ti l . concurre n t.*i
import java.util.*
pubIic c Iass Pool<T>
private int size;
prvate List<T> i tems new ArrayLst<T>() ;
prvate volatle bool ean [J checkedOut;
private Semaphore available;
public Pool{Class<T> c lassObject, int size)
this.size = size
checkedOut new boolean[sizeJ i
available new Semaphore(si ze, tru e);
II Cargar conj unt o con objetos que puedan ext raerse:
for {int i O; i < s i ze ++i )
t ry {
II Presupone un constructor predet erminado:
items.add(classObject.newInsta nce()) i
catch (Except ion e) {
throw new RuntimeExc eption(e);
publi c T c heckOut() throws InterruptedException {
a vai l able.acquire() i
return getl tem();
public void checkln(T xl
if(rel easeltem(x) )
avai lable.release()
private synchronized T getlte m()
for (int i = O; i < size ++i)
if ( ! checkedOut [ill {
checkedOut[i ] = t r ue;
818 Piensa en Java
return i t ems .get (i) i
}
r eturn nul l ; JI El semforo i mpi de l l egar aqu
private synchronized bool ean releaseltem(T item)
int index = items.indexOf (item);
if(index == -l} return fal se; JI No est en la lista
if IcheckedOut [index] ) {
checkedOut [indexJ = fal s e;
return true;
ret ur n f a l se; /1 No ha s ido extr a do
En esta fonna simplificada, el constructor utiliza newInstance() para cargar de objetos el conjunto compartido. Si necesi-
tarnos un nuevo objeto, invocamos checkOut( ), y cuando hemos fUlalizado con un objeto, lo devolvemos con cbeckIn( ).
La matriz booleana cbeckedOut lleva la cuenta de los objetos que han sido extrados y est gestionada por los mtodos
getItem() y releaseltem( ). Estos, a su vez, estn protegidos por el semforo available, de modo que, en checkOut( ), avai-
lable bloquea el progreso de la llamada si no hay pennisos de semforo disponibles (lo que quiere decir que DO hay ms
obj etos en el conjunto compartido). En cbeckIn( ), si el objeto que se est devolviendo es vlido, se devuelve un penniso
al semforo.
Para construir un ejemplo, podemos usar Fat, un tipo de objeto que resulta costoso de crear, porque su constructor tarda bas-
tante tiempo en ejecutarse:
JI : concurrency/ Fat. java
JI Objetos que son cost osos de crear.
public cIass Fat {
private vol ati l e dauble di /1 Impedir optimizacin
private stat i c int counter = O;
priva te final i nt id = count er++
public Fat I l {
II Operacin costosa e interrumpible:
for (i nt i = 1 ; i < 10000; i++} {
d +" IMath. PI + Math .El / Idouhl e l i;
public voi d operationO { System.out.print l n(this) }
public String toString () { return UFat id: u + id; }
/// ,-
Agruparemos estos objetos en un conjunto compartido para limitar el impacto de su constructor. Podemos probar la clase
Pool creando una tarea que extraiga objetos Fat, los conserve durante un cierto perodo de tiempo y luego los devuelva:
11 : concurrency/ SemaphoreDemo.java
II Pr ueba de la clase Pool
import java.util.concurrent.*
import java.ut il.*
import static net .mindview.ut il .Print.*;
II Una tarea para extraer un recurso de un conj unto compar tido:
c lass CheckoutTas k<T> impl ements Runnable
privat e stat ic i nt counte r = O;
pri vate final int id = counter++
private Pool<T> pool;
public CheckoutTask(Pool <T> pool)
this. pool = pool;
public void run( ) {
try {
T item = pool.checkOut(}
print(this + " checked out 11 + item);
TimeUnit.SECONDS.sleep (l) i
print (this +
lI
checking in 11 + item);
pool. checkl n (item) ;
catch (InterruptedExcept ion el
JI Forma aceptable de terminar
public String toString ()
return "CheckoutTask 11 + id + 11 ";
public c l ass SemaphoreDemo {
final stati c i n t SIZE = 25;
public static void main (String[] args) throws Exception {
final Pool<Fat> pool =
new Pool <Fat>(Fat.class, SIZE);
ExecutorService exec = Executors .newCachedThreadPool()
for(int i = O; i < SIZE i++}
exec . execute (new CheckoutTask<Fat>{pool))
print ( Il Al l CheckoutTasks created
ll
) i
List <Fat> list = new ArrayList<Fat>();
f or (int i = O; i < SIZE; i ++) {
Fat f = pool.checkOut();
printnb{i + ": main () thread checked out 11 ) ;
f . oper at i on{);
list. add(f) ;
Future<?> b l ocked exec . submit(new Runnable () {
public void run ()
}
try {
// El semforo i mpide extracciones adicionales,
// por lo que se bloquea l a llamada:
pool.checkOut() ;
catch(InterruptedException e) {
print ("checkOut () Interrupted");
} ) ;
TimeUnit .SECONDS.sleep(2) ;
bl ocked.cancel(true); // Salir de la llamada bloqueada
print (IIChecking in obj ects i n 11 + l ist);
for( Fat f , l ist )
pool.checkl n (f);
for(Fat f , list )
pool.checkln( f ) // Segunda devolucin ignorada
exec . s hutdown();
/* (Ej ecutar para ver la salida) * ///:-
21 Concurrencia 819
En maio(), se crea un objeto Pool para almacenar los objetos Fa! y un conjunto de tareas CheckoutTask comienza a ges-
tionar el conjunto compartido. Entonces, la hebra main() comienza a extraer objetos Fa! sin devolverlos. Una vez que ha
extrado todos los objetos del conjunto compartido, el semforo no pennitir ninguna extraccin adicional. El mtodo run()
de blocked se ve as bloqueado, y despus de dos segundos se invoca el mtodo cancel() para salir de Future. Observe que
las extracciones redundantes son ignoradas por el objeto Pool.
820 Piensa en Java
Este ejemplo depende de que el cliente de Pool sea riguroso y devuelva voluntariamente los elementos, lo cual es la solu-
cin ms simple, siempre que funcione. Si no podemos confiar siempre en esto, Thinldng in Patterns (en www.
MindView.llet) contiene anlisis adicionales de fonnas que pueden emplearse para gestionar los objetos extrados de conjun-
tos de objetos compartidos.
Exchanger
Un intercambiador (Exchanger) es una barrera que intercambia objetos entre dos tareas. Cuando las tareas entran en la
barrera, cada una de ellas tiene un objeto, y cuando salen tienen el objeto que anteriormente era propiedad de la otra tarea.
Los intercambiadores se utilizan tpicamente cuando una tarea est creando objetos que son caros de producir y otra tarea
est consumiendo dichos objetos; de esta forma, pueden crearse ms objetos al mismo tiempo que estn siendo consumidos.
Para probar la clase Exchanger, vamos a crear tareas productoras y consumidoras que, mediante genricos y objetos
Generator, funcionarn con cualquier tipo de objeto, y luego las aplicaremos a la clase Fa!. Los objetos Exchanger-
Producer y ExchangerConsumer utilizan una lista List<T> como el objeto que hay que intercambiar; cada uno contiene
un objeto Exchanger para este contenedor List<T>. Cuando se invoca el mtodo Exchanger.exchange( ), ste se bloquea
hasta que la otra tarea invoca su mtodo exchange( ), y cuando ambos mtodos exchange( ) se han completado, los conte-
nedores List<T> habrn sido intercambiados:
/1 : concurrencyjExchangerDemo.java
import java.util.concurrent.*
import java.util .*
import net.mindview.util.*
class ExchangerProducer<T> implement s Runnable
private Generator<T> generator;
pri vate Exchanger<List<T exchanger;
private Lis t<T> hol der;
ExchangerProducer( Exchanger<List<T exchg,
Generator<T> gen, List <T> ho lder) {
exchanger = exchg;
generator = gen ;
this.holder = holder;
public void r un() {
try {
while(!Thread .interrupted())
for(int i = O i < ExchangerDemo.size; i++l
holder .add(generator.next()) i
II Intercambiar el contenedor yacio por el lleno:
holder = exchanger.exchange(holder);
catch( I nterruptedExcept i on el
II OK terminar de esta forma .
c l ass ExchangerCons umer<T> implemen t s Runnable
p r ivate Exchanger<List<T exchanger;
prvat e List<T> holder;
private volatile T value
Exchanger Consumer(Exchanger<Li st <T ex, Li st<T> holder ) {
exchanger = ex;
this.holder = holder;
public void run() {
try {
while ( !Thread.interrupted( {
holder = exchanger.exchange(holder) i
for(T x : holder) (
value = Xi /1 Extraer valor
holder.remove(x) ; JI OK para CopyOnWriteArrayList
cat ch(InterruptedException el
// OK termi nar de esta f orma .
System.out.println("Final value: I! ... value) j
public c!ass ExchangerDemo
static i nt size = 1 0;
s tatic int delay = 5; // Segundos
public static voi d main(String[] argsl throws Exception {
if(args.length > O)
size = new I n teger (args[ Q) ;
if(args .length > 1)
delay = new Integer(args[l]);
ExecutorService exec = Executors.newCachedThreadPool ()
Exchanger<List<Fat xc = new Exchanger<Li st<Fat ()
List <Fat>
producerLi st = new CopyOnWriteArrayList<Fat>{),
consumerLi st = new CopyOnWr iteAr rayList<Fat>()
exec.execute (new ExchangerProducer<Fat>{xc,
BasicGenerator.create{Fat.class) J producerList)) j
exec.execute{
new ExchangerConsumer<Fat>(xc,consumerList ;
TimeUnit.SECONDS.sleep(delay) i
exec.shutdownNow{) ;
/ * Output : (Sample)
Final value: Fat id: 299 99
*///:-
21 Concurrencia 821
En main(), se crea un OOleo objeto Exchanger para que lo empleen ambas tareas, y dos contenedores CopyOn-
WriteArrayList para intercambiar. Esta variante concreta de Lis! permite que se invoque .el mtodo remove( ) mientras
que se est recorriendo la lista sin que se genere una excepcin CODcurrentModificationException. ExchangerProducer
rellena una lista y luego intercambia la lista llena por la vaca que ExchangerConsumer le entrega. Debido a la existencia
del objeto Exchangor, el llenado de una lista y el consumo de la otra pueden tener lugar simultneamente.
Ejercicio 34: (1) Modifique ExchangerDemo.java para utilizar su propia clase en lugar de Fa!.
Simulacin
Uno de los usos ms interesantes y atractivos de la concurrencia es el de crear simulaciones. Utilizando la concurrencia,
cada componente de una simulacin puede ser su propia tarea. y esto hace que la simulacin resulte mucho ms fcil de pro-
gramar. Muchos juegos infOlIDticos y animaciones por computadora en las pelculas son simulaciones, y HorseRace.java
y GreenhouseScheduler.java, mostrados anteriormente, tambin podran considerarse simulaciones.
Simulacin de un cajero
Esta simulacin clsica puede representar cualquier situacin en la que aparecen objetos aleatoriamente, y estos objetos
requieren un intervalo de tiempo aleatorio para ser servido por lU1 nmero limitado de servidores. Es posible construir la
simulacin para detenninar el nmero ideal de servidores.
822 Piensa en Java
En este ejemplo, cada cliente del banco requiere una cierta cantidad de tiempo de servicio, que es el nmero de UIdades de
tiempo que el cajero debe invertir en el cliente para satisfacer sus necesidades. La cantidad de tiempo de servicio ser dife-
rente para cada cliente y se detenninar aleatoriamente. Adems, no sabemos cuntos clientes llegarn en cada intervalo,
por lo que esto tambin se detenninar aleatoriamente.
1/: concurrency/BankTe llerSimulation . j ava
ji Utilizacin de colas y mecani smos multihebra.
II {Args, S}
import java.uti l.concurrent.*
import java.ut il.*;
JI Los objetos de slo lectura no requieren sincroni zacin :
cIass Custorner {
)
private final int serviceTime:
public Customer (int tm) { serviceTime ::=; tm: }
public int getServiceTime{) { return servi ceTime
publi c string toString () {
return 11 (Ir + serviceTime + 11] n i
/1 Mostrar a la fila de clientes cmo visualizarse:
cIass CustomerLine extends ArrayBlockingQueue<Customer>
public CustomerLine(int maxLineSize){
super (maxLineSize) i
public String toString ()
if (t his. s i ze() == O)
return 11 [Empty] 11 ;
StringBuilder result = new StringBuilder {);
for(Customer customer : this)
result.append{customer) i
return result.toString() i
II Aadir c lie ntes aleatoriamente a una col a:
c lass CustomerGenerator implements Runnable (
private CustomerLne customersj
prvate statc Random rand = new Random(47) i
public CustomerGenerator( CustomerLne cq) (
customers = cq
public void run ()
try {
while ( !Thread.int errupted (
TimeUnt,MILLISECONDS.sleep(rand.nextlnt(300)
customers.put(new Customer(rand.nextlnt(lOOO))) i
catch(InterruptedException el {
System, out .println ("CUstomerGenerator interrupted I!) i
System. out . println ( "Cust omerGenerator terminating
l l
) i
class Teller implements Runnabl e, Comparable<Te ller>
private static int counter = O;
private final int id counter++
1/ Clientes servidos durante este intervalo:
private int customersServed = o;
private CustomerLine customersj
prvate boolean servingCustomerLine = true
public Teller(CustomerLine cq} { customers = cq }
public void run() {
try {
while(!Thread.interrupted())
Customer customer = customers.take()
TimeUnit.MILLISECONDS.sleep(
customer.getServiceTime()) j
synchronized {this} {
customersServed++
while(!servingCustomerLine)
wait () ;
catchllnterr uptedException e) {
System.out.println{this + lIinterrupted"};
System.out.println(this + Uterminating
U
);
public synchronized void doSomethingElse()
customersServed = O;
servingCustomerLine = false
public synchronized void serveCustomerLine ()
assert ! servingCustomerLine: "already serving: " + this j
servingCustomerLine = true;
notifyAll () ;
public String toStri ng() { ret urn I'Te ller " + id + 11 ";
public Stri ng shortString () { return liT" + id; }
II Usado por la cola de prioridad :
public synchronized i nt compareTo(Teller other )
return customersServed < other.customersServed ? -1 :
(customersServed == other.customersServed ? O : 1)
clasa TellerManager implements Runnable
private ExecutorService exec;
private CustomerLine customers
private PriorityQueue<Teller> workingTellers
new PriorityQueue<Teller> ();
prvate Queue<Tel ler> tellersDoingOther Things
new LinkedList<Tel ler>()
private int adjustmentPeriod
privat e static Random rand = new Random(47 )
publ i c TellerManager(ExecutorService e,
CustomerLine customers, int adj ustmentPeriod)
exec = e
this.customers = customers
this.adjustmentPeriod = adjustmentPerod
II Comenzar con un nico cajero:
Teller teller = new Teller(customers)
exec.execute(teller)
workingTellers. add (teller)
21 Concurrencia 823
824 Piensa en Java
public void adjustTellerNumber() {
}
II Esto es realmente un sistema de control. Ajustando
II los nmeros, podemos descubrir problemas de estabilidad
II en el mecanismo de control.
II Si la fila es demasiado larga, aadir otro cajero:
if (customers.size () I workingTellers.size() > 2) {
II Si los cajeros estn descansando o haciendo
II otra tarea, decir a uno que venga:
if(tellersDoingOtherThings.size() > O)
Teller teller = tellersDoingOtherThings.remove()
teller.serveCustomerLine()
workingTellers.offer(teller) ;
return
II en caso contrario, crear (contratar) un nuevo cajero
Teller teller = new Teller(customers)
exec.execute(teller) i
workingTellers.add(teller) i
return;
II Si la fila es lo suficientemente corta, eliminar un cajero:
if(workingTellers.size() > 1 &&
customers. size () / workingTellers. size () < 2)
reassignOneTeller(} ;
II Si no hay una fila, slo hace falta un cajero:
if(customers.size() == O)
while (workingTellers.size () > 1)
reassignOneTeller() ;
II Dar a un cajero un trabajo diferente o un descanso:
pri vate void reassignOneTeller () {
Teller teller = workingTellers.poll()
teller.doSomethingElse() ;
tellersDoingOtherThings.offer(teller)
public void run() {
try {
while(!Thread.interrupted() )
TimeUnit.MILLISECONDS.sleep(adjustmentPeriod) i
adjustTellerNurnber() i
System.out .print (customers + 11 { ") i
for(Teller teller : workingTellers)
System.out .print (teller. shortString () + 11 11);
System. out .println ("} n)
catch(InterruptedException e) {
System.out.println(this + lIinterruptedn) i
System.out.println(this + "terminating
ll
);
public String toString () { return lITellerManager JI i
public class BankTellerSimulation {
static final int MAX_LINE_SIZE = 50j
static final int ADJUSTMENT PERlOD = 1000;
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
II Si la fila es muy larga, los clientes se irn:
CustomerLine customers =
new CustomerLine(MAX_LINE_SIZE) i
exec.execute (new CustomerGenerator (customers i
JI El director aadir o quitar cajeros segn sea necesario:
exec. execute(new TellerManager(
exec, customers, ADJUSTMENT_PERIOD;
if (args. length > O) /1 Optional argument
TimeUnit . SECONDS.sleep( new Integer (args[O} i
else (
System.out.println ( npress 'Enter' to quit");
System.in .read(} i
exec.shutdownNow() i
} /* Out put: (Sample)
[429] [2 00] [2 07] { TO T1 }
[861] [258] [140] [322] { TO T1 }
[575] [342] [8 04] [826] [8 96 ] [ 984 ] { TO T1 T2 }
[984] [8 10] [141] [ 12 ] [68 9] [992] [976] [368] [ 395 ] [354] { TO T1 T2 T3 }
Tel ler 2 int errupted
Te ller 2 termi nating
Teller 1 interrupted
Teller 1 terminating
TellerManager interrupt ed
TellerManager terminating
Teller 3 inter r upted
Tel l er 3 terminating
Teller O interrupted
Teller O terminating
CustomerGenerator interrupted
CustomerGenerator terminating
*/// :-
21 Concurrencia 825
Los objetos Customer son muy simples, conteniendo nicamente un campo final int. Puesto que estos objetos nunca c m ~
bian, son objetos de slo lectura y no requieren sincronizacin ni el uso de volatile. Adems, cada tarea Teller (cajero) slo
elimina un objeto Customer cada vez de la cola de entrada, y trabaja sobre un objeto Customer hasta que se haya comple-
tado, por lo que en cualquier caso a cada objeto Customer slo acceder una tarea en cada momento.
CustomerLine representa una nica fila en la que los clientes esperan antes de ser servidos por un objeto Teller. Se trata
simplemente de una cola ArrayBlockiogQueue que tiene un mtodo toString( ) que imprime los resultados de la forma
deseada.
Con cada objeto CustomerLine se asocia un obj eto CustomerGenerator, que introduce clientes en la cola a intervalos
aleatorios.
Cada objeto Teller extrae clientes de ]a cola CustomerLine y los procesa de uno en uno, llevando la cuenta del nmero de
clientes a los que ha servido durante ese intervalo concreto. Se le puede decir a] cajero que haga alguna otra cosa con
doSomethingElse( ) cuando no haya suficientes clientes o que atienda a la fila con serveCustomerLine( ) cuando haya
muchos clientes. Para seleccionar el siguiente cajero que hay que poner a atender a los clientes, el mtodo compareTo( )
examina el nmero de clientes seIVidos, de modo que una cola PriorityQueue puede seleccionar automticamente al caje-
ro que haya realizado un menor trabajo hasta el momento.
El objeto TellerManager'es el centro de actividad. Lleva el control de todos los cajeros y de lo que est sucediendo con los
clientes. Uno de los aspectos interesantes de esta simulacin es que se intenta descubrir el nmero ptimo de cajeros para un
flujo de clientes determinado. Podemos ver esto en el mtodo adjustTeUerNumber(), que es un sistema de contra] para agre-
gar y eliminar cajeros de una manera estable. Todos los sistemas de control tienen problemas de estabilidad; si reaccionan
demasiado rpido a un cambio, son inestables y si reaccionan demasiado lento, el sistema se desplaza a uno de sus extremos.
Ejercicio 35: (8) Modifique BankTellerSimulation.java para que represente clientes web que estn enviando solicitu-
des a un nmero fijo de servidores. El objetivo es detenninar la carga que el grupo de servidores puede
gestionar.
826 Piensa en Java
Simulacin de un restaurante
Esta simulacin retoma el ejemplo simple Restaurant.java mostrado anteriormente en el captulo, aadiendo ms compo_
nentes de simulacin, como los pedidos (Order) y los platos (Plate), y reutiliza las clases menu del Captulo 19, TIpos enu-
merados.
Tambin introduce la cola SynchronousQueue de Java SE5, que es una cola bloqueante que no tienen capacidad interna,
por lo que cada operacin put() (insercin) debe esperar a que se produzca una operacin take() (extraccin), y vicever_
sa. Es como si estuviramos entregando un objeto a alguien (no hay ninguna mesa sobre el que ponerlo, por lo que slo fun-
ciona si la otra persona est tendiendo una mano hacia nosotros y lista para recibir el objeto). En este ejemplo,
SynchronousQueue representa el espacio situado del ante del comensal, para hacer hincapi en la idea de que slo se puede
servir un plato cada vez.
El resto de las clases de la funcionalidad de este ejemplo se ajustan a la estructura de Restaurant.java o pretenden ser una
plasmacin bastante directa de las operaciones de un restaurante real:
JI : concurrency/ restaurant 2j RestaurantWithQueues.java
II {Ar gs, s}
package concurrency.rest aurant 2;
import enumerated . menu.*
impor t java.util.concurrent.*
i mport java.util.*;
import s tatic net.mindview.util. Print.*;
/1 Se entrega al camarero, que a su vez se lo da al:
class Order { 11 (un objeto de transferencia de datos)
private s tatic int counter = Di
private final int id = counter++
private final Customer customer
private final WaitPerson wai tPerson
private fina l Food foad;
publie Order{Customer elist, WaitPerson wp, Food f ) {
customer = cust;
watPersan = wp
food = fi
public Food item () { ret urn faod; }
publie Custamer getCustomer() { return eustomer }
public WaitPerson getWait Person() { return waitPerson;
public String taString () {
return II Order : 11 + id + 11 item: 11 + food +
for: " + custamer +
11 served by: n + waitPersan j
11 Esto es l o que el chef devuel ve:
c l ass Pl ate {
private final Order order;
prvate final Food food;
public Pl ate(Order ord, Food f ) {
a rder::: ord
food = f
publ ic Order getOrder() { return arder; }
public Foad getFood() { return food; }
public String toString() { return food.toString();
class Cus tomer implements Runnabl e {
private static int counter = o:
private final int id = counterT+
private final Wai tPerson waitPerson;
II Slo se puede recibir un p l ato cada vez:
private SynchronousQueue<Plate> placeSetting
new SynchronousQueue<Plate> ()
public Customer(WaitPerson w) { waitPerson = W
public void
deliver(Plate p ) throws InterruptedExcept ion
II S610 se bloquea si el cliente est todavia
II comiendo el plato anterior:
placeSet ting.put (p)
public void run() {
for (Course caurse : Caurse. values ()) {
Faod toad = course.randomSelection() i
try (
waitPerson.placeOrder(this, foad);
II Se bloquea hasta que se entregue e l plato :
pri nt (this + "eating 11 T placeSetting. take () ;
catch (InterruptedException e) {
print (this + Uwait ing far " +
caurse + 11 interrupted" ) ;
break:
print (this + uf i nished meal, l eavi ng");
public St ring toString()
return "Custarner 11 + id + !I n
c lass WaitPerson implements Runnable
private stat ic int counter = O;
private final int id = counter++
priva te final Res t aurant restaurant;
BlockingQueue<Plate> filledOrders =
new LinkedBlockingQueue<plate>()
public Wait Person(Restaurant rest) { restaurant = rest
public void placeOrder(Custamer eust , Food faad) {
try (
II No debera bloquearse en la prctica, porque es una cola
II Li nkedBlocki ngQueue sin lmite de tamao:
restaurant.orders. put(new Order(cust, this, food
catch {InterruptedExcept i on e) {
print (this + " placeOrder interrupted");
public void run() {
try {
while ( ! Thread. interrupted ( ) )
II Se bloquea hasta que est listo un plato
Plate plate = filledOrders. t ake( )
print (this + nreceived I! + plate +
11 delivering to 11 +
plate.getOrder() .getCustomer();
plate.getOrder() .getCustomer() . deliver(plate)
21 Concurrencia 827
828 Piensa en Java
catch(InterruptedExcept ion e l
print (this + 11 i n ter rupted");
print(thi s + of f duty") i
publ ic String t oSt ring()
return "Wai tPerson " + id + " 11 i
class Chef implements Runnable {
private static int counter = O;
prvat e final int id = count er++
private final Res taurant r estaurant;
prvat e static Random r and = new Random( 47 ) ;
public Chef(Restaur ant r est) { restaurant = rest;
public void run() {
try {
whi l e ( ! Thread. interrupted () )
// Se bloquea hasta que apar ece un pedido:
Order arder = restaurant.orders.take ();
Food reques tedltem = arder .item();
JI El tiempo para preparar e l pedido:
TimeUnit.MILLISECONDS.sleep(rand.nextlnt(SOO)) j
PI ate plate = new PI ate (order , requestedltem)
order.getWai t Person() .filledOrders . put (plate) i
catch(InterruptedExcept i on el
print (thi s + 11 interrupted" ) i
print (thi s + off duty" ) i
public St r ing toString () { return I1Chef 11 + id + u u i
class Restaurant implements Runnable {
pr vate List <WaitPerson> wait Persons
new ArrayList<WaitPerson>() i
private List<Chef > chef s = new ArrayList <Chef >() j
pr vate ExecutorService exec;
private static Random r and = new Random(47);
Bl ockingQueue<Order>
orders = new LinkedBlockingQueue<Order>() ;
public Restaurant{Execut orService e, i nt nWait Persons,
i nt nChefs) {
exec = e;
for(int i = O; < nWai tPersons; i ++) {
Wai tPerson wai tPers on = new WaitPerson(this)
wai tPersons. add (waitPer son) ;
exec. execute (waitPers onl i
for(int i = O; i < nChefs; i ++ )
Chef chef = new Chef(this);
chefs .add(chef) ;
exec .execute (chef ) ;
publi c void run() {
try {
whi le (!Thr ead. interrupted() )
}
// Ll ega un nuevo client e; asignar un camarero:
WaitPerson wp = waitPersons.get(
rand.nextlnt(waitPersons.size() ) i
Customer e = new Customer(wp) i
exec .execute (c) ;
TimeUnit.MILLISECONDS.sleep (l OQ ) i
catch(InterruptedException el {
print ("Restaurant i n terrupted" ) ;
print ("Restaurant closing") i
public class RestaurantWithQueues {
public static void main (String[] args ) throws Exception {
ExecutorService exec = Executors .newCachedThreadPool ();
Restaurant r estaurant = new Restaurant (exec, 5, 2);
exec.execute(restaurant) ;
if {args ,lengt h > O) ji Argumento opcional
Ti meUnit.SECONDS.sleep(new Integer(args[O) )
else (
print(IIPress 'Enter' to quitn) i
System.in.read() ;
exec.shutdownNow () ;
/* Output, (Sampl e)
WaitPerson O received SPRI NG_ROLLS delivering te Custemer 1
Cust omer 1 eat ing SPRING_ROLLS
WaitPerson 3 received SPRING_ROLLS del ivering to Customer O
Customer O eat i ng SPRING_ROLLS
WaitPersen O received BURRITO delivering te Customer 1
Customer 1 eat ing BURRI TO
WaitPerson 3 received SPRING_ROLLS delivering te Customer 2
Customer 2 eating SPRING_ROLLS
WaitPer son 1 received SOUP delivering to Customer 3
Cust omer 3 eat i ng SOUP
WaitPerson 3 received VINDALOO delivering to Customer O
Customer O eating VINDALOO
WaitPerson O received FRUIT del i vering to Customer 1
*/// ,-
21 Concurrenci a 829
Un aspecto muy importante de este ej emplo es la gestin de la complejidad utilizando colas para la comunicacin entre ta-
reas. Esta tcnica simplifica enonnemente el proceso de la programaci6n concurrente, al invertir el control: las tareas no
interfieren directamente entre s. En su lugar, las tareas se intercambian objetos a travs de colas. La tarea receptora gestio-
na el obj eto, tratndola como un mensaje, en lugar de recibir directamente mensajes. Si seguimos esta tcnica siempre que
podamos, tendremos una mayor posibiJidad de construir sistemas concurrentes robustos.
Ejercicio 36: (lO) Modifique RestaurantWithQueues.java para que haya un objeto OrderTicket (nota de pedido) por
cada mesa. Cambie order por orderTicket, y aada la clase Table (mesa), con mltiples clientes (Custo-
mer) por mesa.
Distribucin de trabajo
He aqu un ejemplo de simulacin simple donde se anan muchos de los conceptos vistos en el captulo. Considere una hipo-
ttica lnea de montaje robotizada para automviles. Cada objeto automvil (Car) ser construido en varias etapas, comen-
zando por la fabricacin del chasis y siguiendo por el montaje del motor, de la transmisin y de las medas.
830 Piensa en Java
JJ : concurrencyJCarBui l der. j ava
II Un ejemplo complej o de tareas que funcionan conj unt amente.
impert java.util.concurrent.*
impert java.util.*
import static net.mindvi ew.util.Print.*
cl ass Car
priva te fi nal int id;
private boolean
engine false, driveTrain = fal se , wheels false
publi c Car (int idn) {id = idn; }
IJ Objeto Car yaci o:
public Car () {id = -1; }
publi c synchronized int getId () { return id }
public synchronized void addEngine() { engine true}
publ ic synchronized void addDriveTrai n () {
driveTrain = true;
public synchronized void addWheels (} { wheels = true;
publ i c synchronized String toString () {
return IICa r 11 + id + H [11 + 11 engine: 11 + engine
+ driveTrain: 11 + driveTrain
+ 11 wheels: 11 + wheels + 11 ] 11 ;
cIass CarQueue extends Li nkedBlockingQueue<Car > {}
cIass ChassisBuilder impl ement s Runnable {
private CarQueue carQueuej
private i nt counter = O;
publ ic ChassisBuilder(CarQueue cq) { carQueue
publi c void run() {
t r y (
while(!Thread.interrupted (
TimeUnit.MILLISECONDS . s leep( SOO)
II Hacer chas i s :
Car c = new Car (counter++)
print ("ChassisBuilder created " + e) j
II Inse rtar en la cola
carQueue.put(c) ;
catch(InterruptedException e } {
print (11 I nterrupted : ChassisBuilder")
print ( "ChassisBuilder off " )
cIass Assembler impl ement s Runnable {
private CarQueue chass isQueue, finish ingQueue
private Car car
cq; }
priva te CyclicBarri er barri er = new CyclicBar rier (4)
p r ivate RobotPool robotPool
public Assembler(Car Queue cg, CarQueue fg, RobotPool rp){
chassisQueue = cq;
finishingQueue = fq;
robot Pool = rp;
public Car car () { return car; }
public CyclicBarrier barrier ( ) { return barrier i }
public void run() {
try {
while(!Thread.interrupted())
// Se bloque hasta que est disponible un chasis:
car = chassisQueue.take()
// Comprar robots para realizar el trabajo:
rObotPool.hire(EngineRobot.class, this);
robotPool.hire(DriveTrainRobot.class, this);
robotpool.hire(WheelRobot.class, this);
barrier.await() i // Until the robots finish
// Insertar coche en la cola de acabado (fin ishingQueue )
// para trabajos adicionales
finishingQueue.put(car}
catch (InterruptedException el {
print("Exiting Assembler v i a interrupt" ) ;
cat c h (BrokenBarrierException e) {
// Queremos que nos informen de esta excepcin
throw new RuntimeException(e);
print ("Assembler off") j
class Reporter implements Runnable {
private CarQueue carQueue;
p ublic Reporter(CarQueue cq) { carQueue
public void run() {
try {
while ( !Thread. i nterrupted( })
print(carQueue.take ( ;
catch ( InterruptedExcept i on e l {
c q; )
print (UExiting Reporter via i nterrupt
ll
);
pri nt (UReporter off n)
abstract c lass Robot implements Runnabl e {
prvate RObotPool pool;
public Robot(RobotPool p) { pool. p; )
p r otected Assembler assembler
public Robot assignAssembler(Assembler assembler)
this.assembler = assembl er
ret urn this
}
p r vate boolean engage = false
publ ic sync hronzed void engage ()
engage = tru e ;
notifyAll () ;
// La parte de run() que es diferente par a cada robot:
abstract protected void performServi c e();
public void run() {
try {
powerDown () // Esperar hasta que haga falta
21 Concurrencia 831
832 Piensa en Java
while(!Thread.interr upted(
perEormService() ;
assembler . barri er () . awai t (); II Sincronizar
II Hemos final izado con este trabajo ...
powerDown()
catch(Interr uptedException e) {
print (n Exi t i ng 11 + thi s + 11 via interrupt
n
) j
catch(BrokenBarr ierException e) {
I1 Queremos que nos informen de esta excepcin
throw new RuntimeException(e)
pri nt(this + 11 off");
private synchronized void
powerDown() throws InterruptedException
engage = fal se;
assembler = nuIl; II Desconectar del ensambl ador (Assembler)
II Volver a ponernos en la cola de disponibles:
pool . rel ease( this) ;
whi le(engage == f alse) 11 Desconectar alimentacin
wait () ;
public String toSt ring () { return getClass() . getName( ) ; }
class EngineRobot extends Robot {
public EngineRobot(RobotPool pool) { super (pool)
protected void performService () {
print (thi s + " installing engine");
assembler.car() .addEngine ()
class DriveTrainRobot extends Robot {
public DriveTrai nRobot(RobotPool pool ) { super (pool ) j
protected void performService() {
print (this + !I i ns t alling DriveTrain
ll
) ;
assembler.car() . addDr iveTrai n ()
class WheelRobot ext ends Robot {
pUblic WheelRobot (Robot Pool pool ) { super(pool)
protected void performService() {
print (this + n inst all ing Wheels
lJ
);
assembler.car( ) .addWheels ();
class RobotPool (
II Impide sileciosamente que existan entrada idnticas :
private Set<Robot > pool = new HashSet <Robot >() ;
public synchroni zed void add(Robot r) {
pool.add(r) i
not ifyAll () ;
pUblic synchronized void
hire(Class<? extends Robot> Assembl er d)
thr ows I nterruptedExcept i on {
f or(Robot r : pool )
if (r.getClass (1 . equals (robotTypel I
pool.remove(r) i
r.assignAssembler(dl i
r.engage() /1 Encenderlo para reali zar l a tarea
ret urn;
wait( ) i 1/ Ni nguno disponible
hire( robot Type, d); // Intentar de nuevo recursivamente
public synchroni zed void release(Robot r) { add(r); }
publi c c lass CarBui lder {
public static void main (String [J args) throws Exception {
CarQueue chassisQueue = new CarQueue(),
finishingQueue = new CarQueue()
Execut orServi ce exec = Executors.newCachedThreadPool () ;
RobotPool robotPool = new RobotPool( )
exec.execute (new EngineRobot(robotPool )} ;
exec. execut e(new DriveTrainRobot(robotPool));
exec.execute(new WheelRobot(robot Pool))j
exec. execut e(new Assembler(
chass i sQueue, f i nishi ngQue ue, robotPool )) i
exec.execut e(new Reporter( f inishingQueue) ) ;
// Comenzar a funci onar produci endo un chasi s:
exec. execute(new ChassisBuilder(chas sisQueue}};
TimeUnit .SECONDS.sleep(7) i
exec. shutdownNow() i
/ * (Ejecuta r para ver l a salida) *///:-
21 Concurrencia 833
Los objetos Car se transportan de un lugar a otro mediante una cola CarQueue, que es un tipo de LinkedBlockingQueue.
Un objeto ChassisBuilder crea un chasis de coche y lo coloca en una cola CarQueue. El objeto Assembler extrae los obje-
tos Car de una cola CarQueue y adquiere objetos Robot para trabajar con ellos. Una barrera CyclicBarrier permite que
Assembler espere hasta que todos los objetos Robot hayan terminado, en cuyo momento coloca el objeto Car en la cola
CarQueue de salida para transportarlo a la siguiente operacin. El consumidor de la cola CarQueue final es un objeto
Reporter, que simplemente imprime los datos del objeto Car para demostrar que las tareas se han completado apropiada-
mente.
Los objetos Robot se gestionan mediante un conjunto compartido y cuando hace falta realizar un trabaj o, se extrae el obje-
to Robot apropiado de ese conjunto. Despus de completar el trabajo, el objeto Robot se devuelve al conjunto compartido.
En main( ), se crean todos los objetos necesarios y se inicializan las tareas, inciando ChassisBuilder en ltimo lugar para
comenzar con el proceso (sin embargo, debido al comportamiento de la cola LinkedBlockingQueue, no importaria si ini-
ciramos la tarea de construccin del chasis en primer lugar). Observe que este programa se ajusta a todas las directrices rela-
tivas al tiempo de vida de los objetos presentadas en este captulo, de manera que el proceso de terminacin resulta seguro.
Observar que Car tiene defmidos todos sus mtodos como synchronized. En realidad, en este ejemplo, esto es redundan-
te, porque dentro de la fbrica, los objetos Car se desplazan a travs de colas y slo una tarea puede estar trabajando en un
cierto coche en un deteITI1inado momento. Bsicamente, las colas fuerzan a realizar un acceso serializado a los objetos Car,
Pero sta es exactamente el tipo de trampa en la que podemos caer; podemos decir "Tratemos de optimizar el programa no
sincronizando la clase Car, porque parece que no es necesario". Pero posteriormente, al conectar este sistema a otro que s
que necesite que el objeto Car est sincronizado, el sistema no funci onar.
Brian Goetz comenta:
Resulta mucho ms fcil decir: "Car podra ser usado en mltiples hebras, as que hagamos que sea
seguro de cara a las hebras de la forma ms evidente". La manera en que podemos caracterizar este
834 Piensa en Java
enfoque es la siguiente: en determinados lugares, podemos encontrar una serie de vallas dispuestas
en lugares donde existen desniveles abruptos, y junto a ellas podemos ver seales que dicen: "No se
apoye en las vallas ". Por supuesto, el autntico propsito de esta regla no es impedirnos apoyarnos
en las vallas, sino impedirnos que nos caigamos por el acantilado. Pero "No se apoye en las vallas"
es una regla mucho ms fcil de seguir que "no se caiga por el acantilado ".
Ejercicio 37: (2) Modifique CarBuilder.java para aadir otra etapa al proceso de construccin de automviles, en la
que aadiremos el sistema de escape, los asientos y los accesorios. Al igual que con la segunda etapa,
suponga que estos procesos pueden ser realizados simultneamente por robots.
Ejercicio 38: (3) Utilizando la tcnica empleada en CarBuilder.java, modele el ejemplo de construccin de casas que
hemos comentado en este captulo.
Optimizacin del rendimiento
Muchas de las clases de la biblioteca java.util.coucurrent de Java SE5 tienen el propsito de mejorar el rendimiento.
Cuando examinamos de manera somera la biblioteca concurrent, puede resultar difcil discernir qu clases estn pensadas
para una utilizacin normal (como por ejemplo BlockingQneue) y cules otras se usan exclusivamente para mejorar el ren-
dimiento. En esta seccin, vamos a examinar algunos de los problemas y de las clases relativos a las tcnicas de optimiza-
cin del rendimiento.
Comparacin de las tecnologas mutex
Ahora que Java incluye la antigua palabra clave synchronized junto con las nuevas clases Lock y Atomic de Java SE5,
resulta interesante comparar las diferentes tcnicas para poder comprender mejor las ventajas de cada una y saber cundo
emplearlas.
La tcnica ms simple consiste en intentar una prueba sencilla de cada tcnica, como la siguiente:
//: concurrency/SimpleMicroBenchmark.java
// Los peligros de las micropruebas.
import java.util.concurrent.locks.*i
abstract class Incrementable {
protected long counter = Di
public abstract void increment()
class SynchronizingTest extends Incrementable {
public synchronized void increment() { ++counter;
class LockingTest extends Incrementable {
private Lock lock = new ReentrantLock()
public void increment() {
lock.lock() ;
try {
++counter
finally (
lock.unlock() i
public class SimpleMicroBenchmark {
static long test(Incrementable incr)
long start System.nanoTime()i
for(long i = Di i < lOOOOOOOL i++)
incr,increment() i
return System.nanoTime() - start;
public static void main(String[] args)
long synchTime = test(new SynchronizingTest())
long lockTime = test (new LockingTest())
System.out.printf (Usynchronized: %1$10d\n", synchTime) j
System. out .printf (tlLock: %1$10d\n
ll
1 lockTime);
System.out.printf(!1Lockjsynchronized = %1$.3fll,
(double) lockTimej (doublel synchTime) ;
/* Output: (75% match)
synchronized: 244919117
Lock: 939098964
Lock/synchronized = 3.834
*///,-
21 Concurrencia 835
Podemos ver, analizando la salida, que las llamadas al mtodo synchronized parecen ser ms rpidas que la utilizacin de
un bloque ReentrantLock. Qu es lo que est sucediendo?
Este ejemplo ilustra los peligros de las denominadas "micropruebas de rendimiento".23 Generalmente, este tnnino se refie-
re a la realizacin de pruebas de rendimiento de una caracterstica aislada, fuera de contexto. Por supuesto, sigue siendo
necesario disear pruebas para verificar enunciados como "Lock es mucho ms rpido que synchronized". Pero tenemos
que ser conscientes de lo que est sucediendo realmente durante la compilacin y en tiempo de ejecucin a la hora de escri-
bir estos tipos de pruebas.
Existen diversos problemas en el ejemplo anterior. En primer lugar, slo podremos ver la verdadera diferencia derendimien-
to si los mutex estn contendiendo, as que tiene que haber mltiples tareas intentando acceder a las secciones de cdigo
protegidas por el mutex. En el ejemplo anterior, cada mutex se comprueba mediante la nica hebra main( ) aislada.
En segundo lugar, es posible que el compilador realice optimizaciones especiales al ver la palabra clave synchronized, y
que incluso se percate de que este programa tiene una sola hebra. El compilador podra incluso identificar que el contador
counter simplemente se est incrementando un nmero fijo de veces, y limitarse a precalcular el resultado. Existen muchas
variaciones entre los distintos compiladores y sistemas de ejecucin, as que resulta dificil saber exactamente qu es lo que
suceder, pero necesitamos impedir que el compilador pueda llegar a predecir el resultado de los clculos.
Para disear una prueba vlida, debemos hacer el programa ms complejo. En primer lugar, necesitamos mltiples tareas, y
no slo tareas que modifiquen valores internos, sino tambin tareas que lean esos valores (en caso contrario, el optimizador
podra darse cuenta de que los valores no estn siendo utilizados nunca). Adems, el clculo debe ser complejo y lo sufi-
cientemente impredecible como para que el compilador no tenga la posibilidad de realizar optimizaciones agresivas.
Conseguiremos esto precargando una matriz de gran tamao con valores enteros aleatorios (la precarga reduce el impacto
de las llamadas a Random.nextInt() en los bucles principales) y utilizando esos valores en un sumatorio:
11: concurrency/SynchronizationComparisons.java
II Comparacin del rendimiento de objetos Lock y Atomic
II explcitos y la palabra clave synchronized.
import java.util.concurrent.*
import java.util.concurrent.atomic.*
import java.util.concurrent.locks.*
import java.util.*
import static net.mindview.util.Print.*
abstract class Accumulator {
public static long cycles = 50000L
II Numero de modificadores y lectores durante cada prueba:
private static final int N = 4
public static ExecutorService exec =
23 Brian Goetz me ayud mucho, explicndome estos problemas. Consulte su artculo en -www128.ibm.com/developerworks/library(j-jtp12214paracono-
cer ms detalles acerca de las medidas de rendimiento.
836 Piensa en Java
Executors .newFixedThreadPool(N*2 ) ;
private s t a ti c CyclicBarr i er barrier
new CyclicBarrier (N*2 + 1 ) ;
protected vol ati l e i n t i ndex : Di
protect ed vol ati l e long value = O;
protected l ong duration = O;
protected String id = !l error";
protected fina l static i nt SIZE = 100000;
protected static int [] preLoaded = new i nt [SIZE];
static {
// Cargar la matriz con nmeros aleatorios:
Random rand = new Random(4?);
for (int i = O; i < SI ZE i++}
preLoaded[il = rand.next Int( }
publi c abs tract void accumulate() i
publi c abstract long read();
prvate c lass Modifier implements Runnable
publ ic void r un() (
for( l ong i = Di i < cycles; i++)
accumulat e () ;
try (
barrier.await() ;
catch(Exception el
t hrow new RuntimeException(e) ;
private c l ass Reader impl ements Runnable {
private volati le l ong val ue
public voi d run () (
for(long i = O; i < cycles i++)
val ue = read () ;
try (
barrier.awai t() ;
catch(Exception e)
t hrow new RuntimeException (e ) ;
public void timedTest( ) {
long start = System. nanoTime()
for {int i = O; i < Ni i++ ) {
exec.execute {new Modif i er (}) i
exec.execute (new Reader() i
}
try (
barrier.awai t() i
catch(Exception e)
thr ow new RuntimeExcept ion(e)
duration = System. nanoTime( ) - start
printf ( II %-13s: %13d \ n " , id, duration ) ;
public static vold
report(Accumulator accl, Accumulator acc2) {
p r int f ( I! %- 22s: %.2f\nl1 , accl.id + ,, / " + acc2.id,
(doubleJaccl.duration/(double)acc2.duration)
cIass BaseLine extends Accumulator
{ id = lIBaseLine"; }
public void accumulate(} {
value += preLoaded[index++] i
if(index >= SIZE) index = O;
public long read() { return value
cIass SynchronizedTest extends Accumulator
{ id = Usynchronized
ll
; }
public synchronized void accumulate()
value += preLoaded[index++J i
if(index >= SIZE) index = O;
public synchronized long read()
return value
class LockTest extends Accumulator {
{ id = "Lock"; }
private Lock lock = new ReentrantLock() i
public void accumulate() {
lock.lock() ;
try {
value += preLoaded[index++l;
if(index >= SIZE} index = Oi
finally {
lock. unlock () i
public long read()
lock .lock () ;
try {
return va!ue
finally {
lock.unlock() ;
cIass AtomicTest extends Accumulator {
{ id = I1Atomic" }
private Atomiclnteger index = new Atomiclnteger(O)
private AtomicLong value = new AtomicLong(O)
public void accumulate() {
II Vaya! Depender de ms de un objeto Atomic a la vez
II no funciona. Pero sigue dndonos un indicador de
II rendimiento:
int i = index.getAndlncrement()
value.getAndAdd(preLoaded[i])
if (++i >= SIZE)
indexo set (O);
public long read() { return value.get()
public class SynchronizationComparisons {
static BaseLine baseLine = new BaseLine();
static SynchronizedTest synch = new SynchronizedTest()
21 Concurrencia 837
838 Piensa en Java
static LockTest lock = new LockTest{ )
static AtomicTest atomi c = new AtomicTest();
static void test ()
print(" ============================") i
printf (11%-125 : %13d\n", "Cycles
ll
, Accumulator.cycles) i
baseLine.timedTest() ;
synch.timedTest() i
lock.timedTest{) ;
atomic.timedTest() i
Accumulator.report(synch, baseLine);
Accumulator.report(lock, baseLine);
Accumulator.report(atomic, baseLine) i
Accumulator.report(synch, lock) i
Accumulator.report{synch, atomic);
Accumulator.report {lock, atomic);
public static void main(String( ] args )
int iterations 5; II Predeterminado
if(args. l ength > O) II Cambiar opci onalmente las iteraciones
iterations = new Integer( args[O]}
JI La primera vez r e l lena el conjunto compartido de hebras:
print ( nWarmup" ) ;
baseLine.timedTest( ) ;
II Ahora la prueba i nicial no incluye el coste de
II iniciar las hebras por primera vez.
II Generar mltiples puntos de datos:
for(int i = O; i < iterationsi i++)
test () ;
Accumulat or.cycles *= 2;
Accumul ator.exec .shutdown() ;
/* Output, (Sample)
Warmup
BaseLine
Cyc l es
BaseLine
synchronized
Lock
34237033
50000
20966632
24326555
53669950
Atomic 30552487
synchronized/BaseLine 1.16
Lock/BaseLine 2 .56
Atomic/BaseLine
synchronizedJLock
synchronizedJAtomic
LockjAtomic
Cycles
BaseLine
synchronized
Lock
1.46
0.45
0.79
1.76
100000
41512818
43843003
87430386
Atomic 51892350
synchronizedj BaseLine 1.06
LockjBaseLine
Atomicj BaseLi ne
synchronized/ Lock
synchronized/ Atomic
LockjAtomic
2.11
1. 25
0.50
0. 84
1. 68
=============================
Cycl es
BaseLine
synchronized
Lock
At omic
200000
80176670
5455046661
177686829
101789194
synchronized/BaseLine
Lock/BaseLine
Atomic/ BaseLine
synchronized/Lock
synchronized/Atomic
Lock/Atomic
68.04
2.22
1. 27
30.70
53 .59
1. 75
=============================
Cycl es
BaseLine
synchronized
Lock
Atomic
400000
160383513
780052493
362187652
202030984
synchroni zed/ BaseLine
Lock/BaseLine
Atomic/BaseLine
synchronized/Lock
synchronized/Atomic
Lock/Atomic
4 . 86
2.26
1. 26
2.15
3.86
1. 79
============================
Cycles
BaseLine
synchronized
Lock
800000
322064955
336155014
704615531
Atomic 39323 1542
synchronized/BaseLine 1.04
Lock/BaseLine 2. 1 9
Atomi c/BaseLine 1.22
synchronized/Lock 0 .47
synchroni zed/Atomic 0.85
Lock/Atomic 1. 79
============================
Cycles
BaseLine
synchronized
Lock
Atomic
1600 000
650004120
52235762925
1419602771
796950171
synchronized/BaseLine
Lock/BaseLine
Atomic/BaseLine
synchronized/Lock
synchronized/Atomic
Lock/Atomi c
80.36
2.18
1. 23
36.80
65.54
1. 78
Cycles
BaseLine
synchronized
Lock
Atomi c
3200000
1285664519
96336767661
2846988654
1590545726
synchronized/BaseLine
Lock/BaseLine
Atomic/BaseLine
synchronized/Lock
synchronized/Atomic
Lock/At omi c
*///,-
74 . 93
2.21
1. 24
33.84
60.57
1. 79
21 Concurrencia 839
840 Piensa en Java
Este programa utiliza el patrn de diseo basado en plantillas
24
para poner todo el cdigo comn en la clase base y aislar
todo el cdigo variante en las implementaciones de accumulate( ) y read( ) en las clases derivadas. En cada una de las cla-
ses derivadas SynchronizedTest, LockTest y AtomicTest, podemos ver cmo accumulate( ) y read( ) expresan diferentes
formas de implementar la exclusin mutua.
En este programa, las tareas se ejecutan mediante un conjunto compartido FixedThreadPool en un intento de realizar toda
la creacin de hebras al principio, impidiendo as que se pague un coste adicional durante las pruebas. Simplemente para
asegurarse, la prueba inicial se duplica y el primer resultado se descarta, porque incluye el coste de la creacin inicial de
hebras.
Es necesaria una barrera CyclicBarrier porque queremos aseguramos de que todas las tareas se hayan completado antes de
declarar completa cada prueba.
Se utiliza una clusula static para precargar la matriz de nmeros aleatorios, antes de que den comienzo las pruebas. De esta
forma, si existe cualquier coste asociado con la generacin de los nmeros aleatorios, este coste no se reflejar durante la
prueba.
Cada vez que se invoca accumulate(), nos movemos a la siguiente posicin de la matriz preLoaded (volviendo al princi-
pio cuando se alcanza el final de la matriz) y se aade otro nmero generado aleatoriamente a value. Las mltiples tareas
Modifier y Reader hacen que aparezca el fenmeno de la contienda para el objeto Accumulator.
Observe que, en AtomicTest, la situacin es demasiado compleja como para tratar de utilizar objetos Atomic, bsicamen-
te, si hay ms de un objeto Atomic implicado, probablemente nos tengamos que dar por vencidos y utilizar mutex ms con-
vencionales (la documentacin del JDK indica especficamente que la utilizacin de objetos Atomic slo funciona cuando
las actualizaciones criticas de un objeto estn limitadas a una nica variable). Sin embargo, hemos dejado la prueba dentro
del ejemplo para poder seguir teniendo una idea de la mejora de prestaciones que se pueden tener con los objetos Atomic.
En main( ), se ejecuta la prueba repetidamente y tenemos una opcin de pedir que se ejecuten ms de cinco repeticiones,
que es el valor predetenninado. Para cada repeticin, se dobla el nmero de ciclos de prueba, de manera que podemos ver
cmo se comportan los diferentes mutex a medida que el tiempo de ejecucin crece. Analizando la salida, vemos que los
resultados son bastante sorprendentes. En las primeras cuatro iteraciones, la palabra clave synchronized parece ser ms efi-
ciente que la utilizacin de Lock o Atomic. Pero repentinamente, se cruza W1 umbral y synchronized parece ser bastante
ineficiente, mientras que Lock y Atomic parecen mantener aproximadamente las prestaciones relativas a la prueba inicial
BaseLine, llegando a ser as mucho ms eficientes que synchronized.
Recuerde que este programa slo nos proporciona una indicacin de las diferencias entre las diferentes tcnicas de mutex,
y que la salida del ejemplo anterior slo indica esas diferencias en mi mquina concreta y en mis circunstancias concretas.
Como podr ver si experimenta con el programa, encontrar significativos cambios de comportamiento cuando se utiliza un
nmero diferente de hebras y cuando se ejecuta el programa durante periodos de tiempo ms largos. Algunas optimizacio-
nes de tiempo de ejecucin no se invocan hasta que un programa ha estado ejecutndose durante varios minutos, y en el caso
de los programas servidores, algunas horas.
Dicho esto, resulta bastante claro que la utilizacin de Lock suele ser bastante ms eficiente que la de synchronized, y tam-
bin resulta que el coste de synchronized vara ampliamente, mientras que el de Lock es relativamente estable.
Quiere esto decir que nunca deberamos utilizar la palabra clave synchronized? Hay que considerar dos factores: en pri-
mer lugar, en SynchronizationComparisons.java, el cuerpo de los mtodos protegidos por mutex es muy pequeo. En
general, sta es una buena prctica: proteja slo con mutex las secciones que sean imprescindibles. Sin embargo, en la prc-
tica, las secciones protegidas con mutex pueden tener un tamao mayor que en el ejemplo anterior, por 10 que el porcenta-
je de tiempo que se invertir dentro del cuerpo de los mtodos, ser probablemente significativamente mayor que el coste
de entrar y salir del mutex, lo que podra anular cualquier beneficio derivado de los intentos de acelerar el mutex. Por
supuesto, la nica fonna de saberlo es (y slo en el momento en que estemos realizando las actividades de rendimiento, no
antes) probar las distintas tcnicas y ver el impacto que tienen.
En segundo lugar, est claro, al leer el cdigo contenido en este captulo, que la palabra clave synchronized permite un cdi-
go mucho ms legible que la sintaxis 10ck-try/finaUy-unlock que Lock requiere, y esa es la razn por la que en este cap-
tulo hemos utilizado principalmente la palabra clave synchronized. Como hemos dicho en otros lugares del libro, resulta
24 Vase Thinking in Patterns en www.MindView.nel.
21 Concurrencia 841
mucho ms nonnalleer cdigo que escribirlo (al programar resulta ms importante comunicarse con otros seres humanos
que comunicarse con la computadora), por lo que la legibilidad del cdigo es critica. Como resultado, tiene bastante senti-
do comenzar utilizando la palabra clave synchronized y cambiar nicamente a objetos Lock si la optimizacin del rendi-
miento lo requiere.
Finalmente, resulta bastante atractivo poder utilizar las clases Atomic en nuestros programas concurrentes, pero tenga en
cuenta, como hemos visto en SynchronizationComparisons.java, que los objetos Atonde slo son tiles en casos muy
simples, generalmente cuando slo hay un objeto Atomic que est siendo modificado y cuando dicho objeto sea indepen-
diente de todos los dems objetos. Resulta ms seguro comenzar con otras tcnicas ms tradicionales de mutex y slo tra-
tar de cambiar a Atomic posteriormente, si as lo exige la optimizacin del rendimiento.
Contenedores libres de bloqueos
Como hemos indicado en el Captulo ll, Almacenamiento de objetos, los contenedores son una herramienta fundamental en
el campo de la programacin y esto incluye, por supuesto, la programacin concurrente. Por esta razn, contenedores pri-
mitivos como Vector y Hashtable tenan muchos mtodos synchronized, que hacan que se incurriera en un coste inacep-
table cuando no se los estaba utilizando en aplicaciones multihebra. En Java 1.2, la nueva biblioteca de contenedores no
estaba sincronizada, y a la clase Collections se le aadieron varios mtodos de decoracin estticos "sincronizados" para
sincronizar los distintos tipos de contenedores. Aunque esto representaba una mejora, porque nos daba la posibilidad de usar
o no la sincronizacin dentro de nuestros contenedores, el coste asociado sigue estando basado en los bloqueos de tipo
synchronized. Java SE5 ha aadido nuevos contenedores especficamente para incrementar el rendimiento en aplicaciones
que sean seguras con respecto a las hebras, utilizando inteligentes tcnicas para eliminar el bloqueo.
La estrategia general que subyace a estos contenedores libres de bloqueo es la siguiente: las modificaciones de los contene-
dores pueden tener lugar al mismo tiempo que las leclmas, siempre y cuando los lectores slo puedan ver el resultado de
las modificaciones completadas. Cada modificacin se realiza en una copia separada de la estruclma de datos (o en ocasio-
nes en una copia separada de toda la estructura) y esta copia es invisible durante el proceso de modificacin. Slo cuando
la modificacin se haya completado se intercambia atmicamente la estructura modificada por la estruclma de datos "prin-
cipal", despus de lo cual los lectores podrn ver la modificacin.
En CopyOnWriteArrayList, una escritura har que se cree una copia de toda la matriz subyacente. La matriz original sigue
existiendo para que puedan realizarse lecturas seguras mientras se est modificando la matriz copiada. Una vez que se ha
completado la modificacin, una operacin atmica intercambia la nueva matriz por la antigua de modo que las nuevas lec-
turas podrn ver la informacin. Una de las ventajas de CopyOn WriteArrayList es que no genera excepciones
ConcurrentModificationException cuando hay mltiples iteradores recorriendo y modificando la lista, as! que no hace
falta escribir cdigo especial para protegerse frente a tales excepciones, a diferencia de lo que ocurria en el pasado.
CopyOn WriteArraySet utiliza CopyOn WriteArrayList para conseguir un comportamiento libre de bloqueos.
ConcurrentHasbMap y ConcurrentLinkedQuene emplean tcnicas similares para permitir leclmas y escrituras concu-
rrentes, pero slo se modifican partes del contenedor en lugar del contenedor completo. Sin embargo, los lectores seguirn
sin ver ninguna modificacin antes de que stas estn completadas. ConcurrentHasbMap no genera la excepcin
ConcurrentModificationException.
Problemas de rendimiento
Mientras que estemos principahnente leyendo de un contenedor libre de bloqueos, ste ser mucho ms rpido que otro
basado en synchronizcd, porque se elimina el coste de adquirir y liberar los bloqueos. Esto sigue siendo cierto en cuanto se
realiza un pequeo nmero de escrituras en un contenedor libre de bloqueos, aunque resultara interesante poder hacerse una
idea de qu quiere decir eso de "un nmero pequeo". En esta seccin trataremos de hacemos una idea aproximada de las
diferencias de rendimiento de estos contenedores bajo distintas condiciones.
Comenzaremos con un sistema genrico para la realizacin de pruebas sobre cualquier tipo de contenedor, incluyendo los
mapas. El parmetro genrico C representa el tipo de contenedor:
11: concurrency/Tester.java
II Sistema para probar el rendimiento de los contenedores concurrentes .
import java.util.concurrent.*
i mport n et.mindview.util.*
842 Piensa en Java
public abstract class Test er<C>
static int testReps = 10;
static int testCycles = 1000;
static int containerSize = 1000 ;
abstract e container l nitializer();
abstract void startReadersAndWriters()
C test Contai ner;
String test l d;
int nReaders;
int nWriters
volati le l ong readResult = O;
volatil e long readTime = O;
volatile long writeTime = O;
CountDownLatch endLatch;
static ExecutorService exec =
Executors.newCachedThreadPool () ;
Integer[] writeData
Tester(String testld, int nReaders, int nWrit ers)
this. testld = testld + " " +
nReaders + "r 11 + nWriters + Ilw
ll
i
this.nReaders = nReaders;
this.nWriters = nWritersj
writeData = Generated.array (Integer .class,
new RandomGenerator. Integer (). , c ontainerSize);
for(int i = O; i < testReps; i++) {
rllnTest () ;
readTime = O;
writeTime = O;
void runTest ()
endLatch = new CountDownLatch(nReaders + nWriters )
testContai ner = cont ainerlnitializer () ;
start ReadersAndWriters( } ;
try {
endLat ch. await(} ;
catch( InterruptedExcept i on ex)
System.out.println(llendLatch interrupted"}
System. out .printf("%-27s %14d %14d\n",
test l d, readTime, writeTime)
if(readTime != O && writeTi me != O)
System.out .printf ( "%- 275 %14d\ n",
"readTime + writeTime =", readTime + wr i teTime)
abst ract c lass TestTask impl ements Runnable
abstract void test(}
abst ract void putResult s();
long duration
public void run ()
l ong startTime = System.nanoTime (}
test () ;
duration = System.nanoTi me () - startTime
synchronized (Tester. this ) {
putResults(}
endLatch.countDown() ;
public static void initMain{String (] args) {
if(args.length > O)
testReps = new Integer(args [O] ) ;
if(args.length > 1)
testCycles = new I nteger (args[l J);
if {args.length > 2)
containerSi ze = new Integer{args(2]);
System.aut.print f(I!%- 27s %14s %14s \n
ll
,
u Type 11 , I!Read timen J IIWrite timen);
}
/// ,-
21 Concurrencia 843
El mtodo abstracto containerlnitializer( ) devuelve el contenedor inicializado que hay que probar, que se almacena en el
campo testContainer. El otro mtodo abstracto startReadersAndWriters( ), inicia las tareas lectora y escritora que leern
y modificarn el contenedor que estemos probando. Se ejecutan diferentes pruebas con un nmero diferente de lectores y
escritores para ver los efectos de la contienda de bloqueo (para los contenedores basados en synchronized) y de las escri-
turas (para los contenedores libres de bloqueos).
Al constructor se le proporciona diversa informacin acerca de la prueba (los identificadores del argumento deben ser auto-
explicativos), despus de lo cual invoca el mtodo runTest() un nmero de veces igual al valor repetitions. runTest() crea
un contador CountDownLatch (para que la prueba pueda saber cundo se han completado todas las tareas), inicializa el
contenedor, llama a startReadersAndWritcrs( ) y espera hasta que todas las tareas se hayan completado.
Cada clase lectora o escritora est basada en TestTask, que mide la duracin de su mtodo abstracto teste ), y luego llama
a putResults( ) dentro de un bloque de tipo synchronized para ahuacenar los resultados.
Para usar este sistema (en el que podemos reconocer el patrn de diseilo basado en el mtodo de plantillas), debemos here-
dar de Tester para el tipo de contenedor concreto que queramos probar, y proporcionar las apropiadas clases Reader y
Writer:
/ /: concurrency/ ListComparis,ons . j ava
1/ {Args: 1 10 ~ } (Prueba rpi da de verificacin durante la construccin)
/ / Comparaci 6naproximada del rendimi ento de listas compatibles con hebras.
import java.util.concurrent.*
i mport java. ut i l.*;
impert net.mindview.uti l .*
abstract class ListTest extends Tester <Li st<Integer {
ListTest (Stri ng testld, int nReaders, int nWriters) {
super (testld, nReaders, nWriters}
class Reader extends TestTask {
long resul t = O
void test () {
fer (long i = O; i < testCycLes i ++)
f or ( int i ndex = O; index < centainerSize i ndex++}
result += testContainer. get (index);
void putResul ts () {
readResult += resul t;
readTi.me += duration
c l ass Writer ext.ends TestTask {
void test () {
for (l ong i = O; i < testCycl esj i ++}
for( i nt index = O; i ndex < containerSize index++)
testContainer.set(index, writeData Iindex]);
void putResults (1 {
writeTime+=durat ion;
844 Piensa en Java
void s t artReadersAndWriters () {
for (i nt i = Di i < nReaders; i++}
exec.execute(new Reader()
for (int i = o; i < nWri ters i++ )
exec .execute{new Writer( ;
c lass SynchronizedArrayListTest extends ListTest
List<Integer> containerlnitializer ( ) {
return Collections.synchronizedList(
new ArrayList <Integer>(
new CountinglntegerList (containerSize );
SynchronizedArrayListTest( int nReaders, int nWri ters )
super ( "Synched ArrayList", nReaders, nWri t ers);
class CopyOnWri teArrayListTest extends ListTest
List<Integer> containerlnitializer () {
return new CopyOnWriteArrayList<Integer>(
ne w Counting l ntegerList(containerSize) i
CopyOnWriteArrayLi s tTest(int nReaders, int nWri ters )
super ( " CopyOnWri t eArrayList ", nReaders , nWriters) i
public c l ass List Comparisons {
public static void main(String[] args) {
Tester. initMain (args) i
1*
new SynchronizedArrayListTest(10, O) i
new SynchronizedArrayListTest(9, 1);
new SynchronizedArrayListTest(5, 5);
new CopyOnWri teArrayListTe st(10, O) i
new CopyOnWriteArrayListTest(9, 1)
new CopyOnWriteArrayListTest(5, 5);
Tester.exec.shutdown{) ;
Output : ISample)
Type Read time Wr ite time
Synched ArrayList 10r Ow 232158294700 O
Synched ArrayList 9r lw 1 98947618203 24918613399
readTime + writeTime 223866231602
Synched ArrayLis t 5r 5w 117367305062 132176613508
readTime + writeTime 249543918570
CopyOnWri t eArrayList 10r Ow 758386889 O
CopyQnWriteArrayList 9r 1w 741305671 136145237
readTime + writeTime 877450908
CopyOnWriteArrayLi s t 5r 5w 2127630 75 6796 7464300
readTime + writeTime 68180227375
* jjj ,-
En ListTest, las clases Reader y Writer realizan las acciones especficas para un contenedor List<Integer>. En
Reader.putResults( ), la duracin (duration) se almacena, al igual que el resultado (result), para impedir que los campos
sean optimizados por el compilador. startReadersAndWriters() se define a continuacin para crear y ejecutar los objetos
lectores y escritores especficos.
21 Concurrencia 845
Una vez creada la li sta ListTest, hay que heredar de ella para sustituir containerInitializer() con el fin de crear e iniciali-
zar los contenedores especficos de prueba.
En maine ), podemos ver variantes de las pruebas, con diferentes nmeros de lectores y escritores. Podemos cambiar las
variables de prueba usando argumentos de la lnea de comandos gracias a la llamada a Tester.initMain(args).
El comportamiento predeterminado consiste en ejecutar cada prueba 10 veces, esto ayuda a estabilizar la salida, que puede
variar debido a actividades propias de la mquina NM, como la optimizacin y la depuracin de memoria
25
La salida de
ejemplo que podemos ver ha sido editada para mostrar nicamente la ltima iteracin de cada prueba. Analizando la salida,
podemos ver que un contenedor ArrayList sincronizado tiene aproximadamente el mismo rendimiento independi entemen-
te del nmero de lectores y escritores: los lectores contienden con otros lectores para la obtencin de dos bloqueos, al igual
que hacen los escritores. Sin embargo, el contenedor CopyOn WriteArrayList es mucho ms rpido cuando no hay escri-
tores y sigue siendo significativamente ms rpido cuando hay cinco escritores. Parece que podemos utilizar de manera bas-
tante libre CopyOn WriteArrayList; el impacto de escribir en la lista no parece superar al impacto de sincroni zar la lista
completa. Por supuesto, es necesario probar las dos tcnicas en cada aplicacin especfica para cerciorarse de cul es la
mejor.
De nuevo, observe que este programa no constituye una verdadera prueba de rendimiento en lo que respecta a los nmeros
absolutos, y los resultados que obtenga en su mquina sern diferentes casi con toda seguridad. El objetivo del programa es
simplemente hacerse una idea del comportamiento relativo de los dos tipos de contenedor.
Puesto que CopyOn WriteArraySet utiliza CopyOn WriteAl'l'ayList, su comportamiento ser similar y no es necesario
que hagamos aqu una prueba separada.
Comparacin de las implementaciones de mapas
Podemos utilizar el mismo sistema para obtener una idea aproximada del rendimiento de un HashMap de tipo syncbroni-
ud comparado con un ConcnrrentHasbMap:
// : concurrency/ MapComparisons .java
/ I {Args: 1 la lO} (Prueba rpida de verificac in durante la construccin)
// Comparacin aproximada del rendimiento de los mapas
// compatibles con hebras.
import java.util.concurrent.*i
import j ava.ut i l.*;
i mport net.mindview.util.*
abst ract class MapTest
extends Tester<Map<Int eger ,Integer
MapTest(String testld, int nReaders, int nWriters) {
super (testld, nReaders, nWr iters) i
class Reader extends TestTask {
long result :: o;
void t est () {
for(l ong i = O; i < testCycles; i ++}
for ( i nt index = O; i ndex < containerSize index++ )
result += testContainer. get( index) i
void putResults() {
readResult += result
readTi me += duration
c lass Writer extends TestTask {
void test () {
for(long i = O i < testCycles i++)
25 Para ver una introduccin a la realizacin de pruebas comparativas de rendimiento bajo la influencia de la compilacin dinmica de Java, consulte www-
128. i bm. com/developerworkslli bralylj-j tp 12214.
846 Piensa en Java
for(int index = o; index < containerSize index++)
testContainer.put{index, writeData[index]) i
void putResults(}
writeTime += duration
void startReadersAndWriters()
for(int i = O; i < nReaders i++}
exec.execute(new Reader());
for(int i = o; i < nWriters; i++}
exec,execute(new Writer()) i
class SynchronizedHashMapTest extends MapTest
Map<lnteger,Intger> containerlnitializer()
return Collections.synchronizedMap(
new HashMap<Integer, Integer> (
MapData.map(
new CountingGenerator. Intger () ,
nw CountingGenerator,Integer(),
containerSize))) i
SynchronizedHashMapTest(int nReaders, int nWritersl
super ( n Synched HashMap n I nReaders, nWri ters) ;
class ConcurrentHashMapTest extends MapTest {
Map<Integer,Integer> containerlnitializer{)
return new ConcurrentHashMap<Integer,Integer>(
MapData.. map (
new CountingGenerator.lnteger(),
new CountingGenerator.lnteger{), containerSize));
ConcurrentHashMapTest(int nReaders, int nWriters)
super (IICncurrentHashMapll, nReaders, nWriters);
public class MapComparisons {
public static void main(String[] args)
Tester. initMain (args) i
new SynchronizedHashMapTest(10, O);
new synchronizedHashMapTest(9, 1) i
neW SynchronizedHashMapTest(5, 5);
new ConcurrentHashMapTest(lO, O) i
new ConcurrentHashMapTest(9, 1);
new CortcUrrentHashMapTest(5, 5);
Tester.exec.shutdown() i
1* Output: (Sample)
Type
Synched HashMap 10r Ow
Synched HashMap 9r lw
readTime + writeTime =
Synched HashMap Sr 5w
readTime + writeTime =
Read time
306052025049
428319156207
476016503775
243956877760
487968880962
Write time
O
47697347568
244012003202
ConcurrentHashMap l Or Ow
Concurren tHashMap 9r lw
readTime + writeTime
ConcurrentHashMap Sr 5w
readTime + writeTime
* /// , .
23352654318
18833089400
20374942624
12037625732
23888114831
o
1541853224
11850489099
21 Concurrencia 847
El impacto de aadir escritores a un mapa ConcurrentHashMap es todavia menos evidente que para CopyOn Write-
ArrayList, pero eso es porque ConcurrentHashMap emplea uua tcnica distinta que minimiza claramente el impacto de
las escrituras.
Bloqueo optimista
Aunque los objetos Atomic realizan operaciones atmicas como decrementAndGet( ), algunas clases Atomic tambin nos
permiten realizar lo que se denomina "bloqueo optimista". Esto quiere decir que no se utiliza realmente un mutex cuando
se est realizando un clculo, sino que, despus de que el clculo ba acabado y estamos listos para actualizar el objeto
Atomic, se usa un mtodo denominado compareAndSet( ). Lo que se hace es entregar a este mtodo el antiguo valor y el
nuevo valor, y si el antiguo valor no concuerda con el que est almacenado en el objeto Atomic, la operacin falla: esto sig-
nifica que alguna otra tarea ha modificado el objeto mientras tanto. Recuerde que normalmente 10 que haramos es utilizar
un mutex (synchronized o Lock) para impedir que ms de una tarea pudiera modificar un objeto al mismo tiempo, pero lo
que estamos haciendo aqu es ser "optimistas" dejando los datos desbloqueados y esperando que ninguna otra tarea llegue
mientras tanto y nos modifique. De nuevo, todo esto se hace en aras del rendimiento: utilizando Atomic en lugar de
synchronizcd o Lock, podemos .aumentar las prestaciones.
Qu ocurre si falla la operacin compareAndSet()? Aqu es donde el asunto se complica, y slo podemos aplicar esta tc-
nica a aquellos problemas que puedan adaptarse a una serie de requisitos. Si fallara compareAndSet( ), tenemos que deci-
dir qu hay que hacer; este aspecto es muy importante, porque si no hay nada que podamos hacer para recuperamos de este
hecho, entonces no se puede emplear esta tcnica y es preciso utilizar en su lugar mutex convencionales. Quizs podamos
reintentar la operacin y no pase nada si sta tiene xito a la segunda. O quiz sea perfectamente adecuado ignorar el fallo:
en algunas simulaciones, si se pierde un punto de datos, esto no tiene ninguna importancia dentro del esquema general de
las cosas (por supuesto, debemos entender nuestro modelo lo suficientemente bien como para saber si esto es cierto).
Considere una simulacin ficticia, compuesta de 100.000 "genes" de longitud 30; quiz pudiera tratarse del principio de
alguna especie de algoritmo gentico. Suponga que para cada "evolucin" del algoritmo gentico se realizan algunos clcu-
los muy costosos, por lo que decidimos emplear una mquina multiprocesador con el fm de distribuir las tareas y mejorar
las prestaciones. Adems, utilizamos objetos Atomic en lugar de objetos Lock para evitar el coste asociado de los mutex
(naturalmente, slo habremos llegado a esta solucin despus de escribir el cdigo de la forma ms simple posible, utilizan-
do la palabra clave synchronized; una vez que tenemos el programa ejecutndose, descubrimos que es demasiado lento y
empezamos a usar tcnicas de optimizacin). Debido a la naturaleza de nuestro modelo, si se produce una colisin durante
un clculo, la tarea que descubra la colisin puede limitarse a ignorarla, sin actualizar el valor. He aqu! el aspecto que ten-
dra la solucin:
JI : concurrency/FastSimulati on.java
import java . util.concurrent .*
import java.util.concurrent .atomic.*
import j ava.ut il.*;
i mpert static net. mindv iew. u til.Print . *
public class FastSimulation {
static final int N ELEMENTS = 100000;
static f i nal int N_GENES = 30;
static final int N_EVOLVERS = SO;
static final Atomiclnteger [] (] GRID ""
new Atomiclnt eger[N_ELEMENTS] [N_GENES];
stat ic Random rand = new Random ( 47)
a tatie class Evol ver implements Runnable
public void run () {
while(!Thread.interrupted()) {
848 Piensa en Java
11 Selecci onar aleatoriamente un e lement o con el que trabajar:
int element = r and.nextlnt(N_ELEMENTS)
for(int i = O i < N_GENES i++} {
i nt previous = element - 1;
i f (pr evious < O) previous = N ELEMENTS - 1;
int next = e l ement + 1 ;
if(next >= N_ELEMENTS) next = Oi
int oldvalue = GRID[e lement] [i] .get() i
11 Real izar algn tipo de clculo de modelado:
i nt newval ue = oldval ue +
GRID [pre vious l [ i l . get() + GRID [next l [il .get () ;
newvalue 1= 3; 11 Promediar los tres valores
if (! GRID [elementl [il
. compareAndSet(oldvalue, newvalue)) {
11 Aqu una pol tica para tratar l os fallos . En este caso,
1/ nos limita remos a i nformar del f a llo y a ignorar lo;
1/ nue stro model o se encargar de trata r con l,
print ("Old val ue changed f rom !I + oldvalue) i
public stat ic void main (String[] argsl throws Excepti on {
ExecutorService exec = Executars.newCachedThreadPaol()
f or( int i = O; i < N_ELEMENTS i++)
for (i nt j = O; j < N_GENES; j++)
GRID (i] ( jl = new Atomiclnteger (rand .next l nt (lOOO));
f a r(int i = Oi i < N_EVOLVERS i++)
exec.execute(new Evol ver()) i
TimeUnit , SECONDS.sleep (5) j
exec. shutdownNow( )
/ * (Ejecut ar para ver la salida ) *11/:-
Todos los elementos se insertan en una matriz, en la suposicin de que esto ayudar a incrementar la velocidad (esta supo-
sicin ser comprobada en un ejercicio). Cada objeto Evolver promedia su valor con los valores contenidos antes y despus
suyo, y si se detecta tul fallo cuando se intenta hacer la actual izacin, simplemente se imprime el valor y se contina.
Observe que no aparece ningn mutex en el programa.
Ejercicio 39: (6) Son razonables las suposiciones realizadas en FastSimnlation.java? Pruebe a cambiar la matriz, sus-
tituyendo los valores AtomicInteger por valores int ordinarios y utilizando mutex de tipo Lock. Compare
el rendimiento de las dos versiones del programa.
ReadWriteLock
Los bloqueos ReadWrlteLock optimizan aquellas situaciones en las que escribimos en una estructura de datos de manera
relativamente infrecuente, pero tenemos mltiples tareas leyendo a menudo de la misma. ReadWriteLock permi te tener
mltiples lectores simultneamente siempre y cuando ninguno de ellos est intentando realizar una escritura. Si alguien
adquiere el bloqueo de escritura, no se permite ninguna lectura hasta que el bloqueo de escritura se libere.
Es bastante incierto si RcadWriteLock permite mejorar la velocidad del programa, ya que esto depende de cuestiones como
la frecuencia con que se leen los datos, comparada con la frecuencia con que se modifican; la duracin de las operaciones
de lectura y escritura (el bloqueo es ms complejo, por lo que con operaciones de corta duracin no se percibira ninguna
ventaja); la frecuencia con que se producen contiendas entre las hebras; y el hecho de si estamos trabajando con una mqui-
na multiprocesador o no. En ltimo trmino, la nica forma de saber si podemos obtener alguna ventaja con Read-
WriteLock consiste en comprobarlo.
He aqu un ejemplo que muestra nicamente el uso ms bsico de los bloqueos ReadWriteLock:
JI: concurrency/ReaderWriterList.java
i mpor t j ava . util.concurrent.*
i mport j ava . util.concurrent.locks.*
import java .util.*;
import stati c net.mindview.util . Print.*
public c l ass ReaderWriterList<T> {
private ArrayList<T> lockedList
JI Realizar una ordenac in equitativa:
prvate ReentrantReadWriteLock lock
new ReentrantReadWriteLock(t rue ) ;
public ReaderWriterList(int size, T i nitialVal ue )
l ockedList = new ArrayList<T>(
COllections.nCopies (size, i nitialValue});
p ublic T set(in t i ndex, T e lement)
Lock wlock = lock .wr i teLock(};
wlock . l ock () ;
try (
r e turn l ockedList. s e t(index. elementl;
fin a lly (
wlock.unlock()
public T get (int index I (
Lock rlock = l ock . readLock ()
r l ock .lock 11 ;
try (
// Mostrar que ml t iples lector es pueden
// adquirir e l bloqueo de l ect ura :
if (lock.getReadLockCount () > 1)
print (lock .getReadLockCount ( ;
return l ockedList.get (index);
finally (
rl ock . unlock () ;
public static void main(St r ing[ ] args) thr ows Exception {
new ReaderWri t erLi stTest(30 , 1);
c l ass ReaderWriterListTest {
ExecutorService exec = Executors.newCachedThreadPool {) ;
private final static i nt SIZE : 100 ;
pri vate static Random r and = new Random( 47 l ;
privat e ReaderWriterList<Int eger> list =
ne w Rea de rWriterList<Int eger> (SIZE, O);
private class Writer impl ements Runnable {
public void run (1 (
try (
for( i nt i = O; i < 20; i ++) { // Prueba de 2 segundos
list .set(i, rand.next lnt();
TimeUnit.MILLISECONDS . sleep{l OO) ;
catch(Inte r r upt edException e l
// Forma aceptable de salir
print(I1Writer finished, shutting down" ) ;
21 Concurrencia 849
850 Piensa en Java
exec.shutdownNow () ;
private c l ass Reader i mplements Runnabl e {
public void r un () {
try {
while(!Thread.interr upted()
f or(int i = Oi i < SIZE i++l
list.get(il i
TimeUnit.MILLISECONDS ,sleep( l}
cat ch(InterruptedExcept ion el
// Forma aceptable de salir
public ReaderWr i terListTest (i nt readers , int writers) {
for (int i = Di i < reader s i++}
exec.execute (new Reader(
for (int i = O; i < writers i++}
exec.execute(new Wri t er ( i
1* (Ejecutar para ver la salida) *///:-
Una lista ReaderWriterList puede almacenar un nmero fijo de objetos de cualquier tipo. Debemos indicar al constructor
el tamao deseado de la lista y un objeto inicial con el que rellenar la lista. El mtodo set( ) adquiere el bloqueo de escritu-
ra para poder invocar el mtodo ArrayList.set( ) subyacente, mientras que el mtodo get() adquiere el bloqueo de lectura
para poder invocar ArrayList.get( ). Adems, get( ) comprueba si hay ms de un lector que haya adquirido el bloqueo de
lectura y, en caso afinnativo, muestra dicho nmero para demostrar que puede haber mltiples lectores que adquieran el blo-
queo de lectura.
Para probar la lista ReaderWriterList, ReaderWriterListTest crea tareas tanto lectoras como escritoras para una lista
ReaderWriterList<Integer>. Observe que hay muchas menos escrituras que lecturas.
Si examina la documentacin del JDK para ReentrantReadWrlteLock, ver que hay varios otros mtodos disponibles, as
como cuestiones relativas a la equidad y a las '"decisiones de poltica
n
Se trata de una herramienta -bastante sofisticada, y
que slo debemos usar cuando estemos buscando formas de aumentar las prestaciones. El primer prototipo de un programa
debera emplear una sincronizacin directa, debindose introducir ReadWriteLock s6lo si es necesario.
Ejercicio 40: (6) Siguiendo el ejemplo de ReaderWrlterList.java, cree un mapa ReaderWrlterMap utilizando un
mapa HashMap. Investigue su rendimiento modificando MapComparisons.java. Cmo se compara con
un contenedor HashMap sincronizado y con Wl contenedorConcur:r:entHashMap?
Objetos activos
Despus de estudiar este captulo, habr observado que el mecanismo de gestin de hebras en Java parece bastante comple-
jo y dificil de usar correctamente. Adems, puede parecer que es un mecanismo que reduce la productividad: aunque las
tareas funcionan en paralelo, es necesario hacer un gran esfuerzo para implementar tcnicas que impidan que dichas tareas
interfieran entre s.
Si alguna vez ha escrito programas en lenguaje ensamblador, la escritura de programas multihebra produce una sensacin
similar: todos los detalles importan, nosotros somos los responsables de todo y no existe ninguna red de seguridad en forma
de comprobaciones realizadas por el compilador.
Podria ser que existiera un problema con el propio modelo de hebras? Despus de todo, este modelo proviene, con pocas
modificaciones del mundo de la programacin procedimental. Quiz exista un modelo diferente de concurrencia que enca-
je mejor con la programacin orientada a objetos.
21 Concurrencia 851
Una tcnica alternativa es la basada en los denominados objetos activos o actores.
26
La razn de que ruchos objetos se deno-
minen "activos" es que cada objeto mantiene su propia hebra funcional y su propia cola de mensajes, y todas las soli citudes
a cada objeto se ponen en cola para procesarlas de una en lUla. Por tanto, con los objetos activos, serializamos los mensajes
en lugar de los mtodos, lo que significa que no necesitamos protegemos frente a aquellos problemas que smgen cuando se
interrumpe una tarea en mitad de su bucle.
Cuando enviamos un mensaje a un objeto activo, dicho mensaje se transforma en una tarea que se inserta en la cola del obje-
to, para ejecutarla en algn instante posterior. La clase Future de Java SES resulta til para implementar este esquema. He
aqu un ejemplo simple que dispone de dos mtodos que ponen en cola las llamadas a mtodo:
JI : concurrencyjActiveObjectDemo.java
/ I Slo se pueden pasar constantes, imrnutables, "obj etos
II desconectados" , u otros objetos ac tivos como argumentos
II a mtodos as ncronos.
import j ava. uti l .conc urrent . *
i mport j ava .util .*
import staci c net.mindview. util.Print .*
publc class Act iveObjectDemo {
private Execut orService ex
Executors.newSingl eThreadExecutor( ) i
prvate Random rand new Random(4 7)
II Insertar un retardo aleatorio para producir e l efecto
II de un tiempo de clculo:
prvate void pause (int fact or) {
try {
TimeUn it.MILLISECONDS.sleep (
1 00 + rand .nextlnt(factor)}
catch( I nterr upt edException e) {
print ( "sl eep() interrupted
1t
);
publ i c Fut ure<Integer>
calculatelnt (f i nal i nt x, final int y) {
ret u r n ex.submi t (new Cal l able<Integer>()
public Integer cal l () {
}
} ) ;
print ( lIs tarting 1t + x + 1t + 11 + y) i
pause(500) ;
return x + Yi
publi c Future<Float>
calcul a t e Float{final float x, f i nal float y)
r etur n ex.submit( new Callable<Float >() {
public Float call () {
}
} ) ;
print ( "start i ng " + x + " + n + y )
pause (2000);
return x + y;
public voi d shutdown () { ex.shutdown () ; }
publ i c stat ic void main {String[] args ) {
Ac t iveObj ectDemo dl new ActiveObjectDemo() ;
II Evita ConcurrentModificationException:
Li s t <Future<? results
26 Gracias a ABen Holub por dedicarme el tiempo necesario para explicarme este tema.
852 Piensa en Java
new copyOnWriteArrayList<Future<?();
for(float f ~ O.Of; f < 1.0f; f ~ 0.2f)
results.add(dl.calculateFloat(f, f));
for(int i "" o; i < s; i++)
results.add(dl.calculatelnt(i, i));
print (HAll asynch calls made
ll
);
while(results.size() > O) {
for(Future<?> f : results)
if(f.isDone()) {
try {
print (f .get ());
catch(Exception e) {
throw new RuntimeException(e) ;
results.remove(f) ;
dI . shutdown () ;
/* Output: (85% match)
All asynch calls made
starting 0.0 + 0.0
starting 0.2 + 0.2
O. O
starting 0.4 + 0.4
0.4
starting 0.6 + 0.6
0.8
starting 0.8 + 0.8
1.2
starting O + O
1.6
starting 1 + 1
O
starting 2 + 2
2
starting 3 + 3
4
starting 4 + 4
6
8
*///,-
El "ejecutor monohebra" producido por la llamada a Executors.newSingleThreadExecutor() mantiene su propia cola blo-
queante no limitada, y tiene una nica hebra que extrae tareas de la cola y las ejecuta hasta completarlas. Todo lo que nece-
sitamos hacer en calculateInt( ) y calculateFloat( ) es enviar con submit( ) un nuevo objeto CaUable en respuesta a una
llamada a mtodo, transformando as las llamadas a mtodos en mensajes. El cuerpo del mtodo est contenido dentro del
mtodo call( ) en la clase interna annima. Observe que el valor de retomo de cada mtodo de objeto activo es un objeto
Future con un parmetro genrico que es el tipo de retomo real del mtodo. De esta forma, la llamada a mtodo vuelve casi
inmediatamente, y elllamante utiliza el objeto Future para descubrir cundo se completa la tarea y para extraer el valor de
retomo real. Esto permite gestionar el caso ms complejo, pero si la llamada no tiene valor de retomo el proceso se simpli-
fica.
En main(), se crea una lista List<Future<? para capturar los objetos Future devueltos por los mensajes
calculateFloat() y calculateInt( ) enviados al objeto activo. Esta lista se sondea utilizando isDone( ) para cada objeto
Future, extrayndose el objeto de la lista cuando se completa el objeto y sus resultados se procesan. Observe que el uso de
CopyOn WriteArrayList elimina la necesidad de copiar la lista para evitar la excepcin ConcurrentModification-
Exception.
21 Concurrencia 853
Para impedir un acoplamiento inadvertido entre hebras, los argumentos que se pasen a una llamada a mtodo de objeto acti-
vo deben ser bien de slo lectura, bien objetos activos, o bien objetos desconectados (esto es terminologa del autor), que
son objetos que no tienen ninguna conexin con ninguna otra tarea (resulta difcil imponer esta regla, porque no hay ningu-
na estructura para soportarla.
Con los objetos activos:
1. Cada objeto tiene su propia hebra funcional.
2. Cada objeto mantiene un control total sobre sus propios campos (lo que es algo ms riguroso que las clases nor-
males, que no slo disponen de la opcin de proteger sus campos).
3. Toda la comunicacin entre objetos activos toma la forma de mensajes intercambiados entre esos objetos.
4. Todos los mensajes entre objetos activos se ponen en cola.
Los resultados son muy atractivos, puesto que un mensaje de un objeto activo a otro slo puede ser bloqueado por el retar-
do a la hora de ponerlo en cola, y puesto que dicho retardo es siempre muy corto y no depende de ningn otro objeto. el
envo de un mensaje no es bloqueable en la prctica (lo peor que puede pasar es un corto retardo). Puesto que un sistema
basado en objetos activos slo se comunica a travs de mensajes, no puede suceder que dos sucesos se bloqueen mientras
contienden por invocar un objeto de otro mtodo. y esto significa que puede llegar a producirse un interbloqueo, lo cual ya
es un gran paso adelante. Puesto que la hebra funcional dentro de un objeto activo slo ejecuta un mensaje cada vez, no hay
contienda por los recursos y no tenemos que preocupamos por sincronizar los mtodos. La sincronizacin se sigue produ-
ciendo, pero tiene lugar en el nivel de mensajes, poniendo en cola las llamadas a mtodos de modo que todas se procesen
de una en una.
Lamentablemente. sin un soporte directo del compilador, la tcnica de codificacin mostrada anteriormente resulta dema-
siado engorrosa. Sin embrago, hay que resaltar que se est progresando mucho en el campo de los objetos activos y actores
y, lo que es ms interesante, en el campo de la denominada programacin basada en agentes. Los agentes son, en la prc-
tica, objetos activos, pero los sistemas de agentes tambin soportan mecanismos de transparencia entre redes y mquinas.
No me sorprendera nada que la programacin basada en agentes llegara a convertirse en el sucesor de la programacin
orientada a objetos, porque combina los objetos con una solucin de concurrencia relativamente sencilla.
Puede encontrar ms informacin sobre los objetos activos, los actores y los agentes buscando en la Web. En particular,
algnnas de las ideas subyacentes a los objetos activos provienen de la Teoria de Procesos Secuenciales Comunicantes (CSP,
Cammunicating Sequential Pracesses) de C.A.R. Hoare.
Ejercicio 41: (6) Aada una rutina de tratamiento de mensajes a ActiveObjectDemo.java que no tenga ningn valor de
retomo e invquela desde maine ).
Ejercicio 42: (7) Modifique WaxOMatic.java para implementar objetos activos.
Proyecto:
27
Utilice anotaciones y Javassist para crear una anotacin de clase @Active que transforme la clase indica-
da en un objeto activo.
Resumen
El objetivo de este captulo era proporcionar las bases de la programacin concurrente con hebras en Java, de modo que el
lector entendiera que:
1. Pueden ejecutarse mltiples tareas independientes.
2. Hay que considerar todos los posibles problemas que pueden producirse cuando estas tareas terminan.
3. Las tareas pueden interferir entre s por el uso de recursos compartidos. El mutex (bloqueo) es la herramienta
bsica utilizada para impedir estas colisiones.
4. Las tareas pueden interbloquearse si no se disean cuidadosamente.
27 Los proyectos son sugerencias que pueden utilizarse, por ejemplo, como proyectos de fin de curso. Las soluciones a los proyectos no se incluyen en la
Guia de soluciones.
854 Piensa en Java
Resulta vital aprender cundo hay que utilizar la concurrencia y cundo hay que evitarla. Las principales razones para
emplearla son:
Gestionar una serie de tareas que al entremezclarlas utilizarn la computadora de manera ms eficiente (incluyen_
do la capacidad de distribuir transparentemente las tareas entre mltiples procesadores).
Permitir una mejor organizacin del cdigo.
Aumentar la comodidad para el programador.
El ejemplo clsico de equilibrado de recursos consiste en utilizar el procesador durante la espera de E/S. La mejora en la
organizacin de cdigo suele manifestarse de forma especialmente clara en las simulaciones. El ejemplo clsico de mejora
de la comodidad para los programadores es el de monitorizar un botn de "parada" durante las descargas de larga duracin
a travs de la red.
Una ventaja adicional de las hebras es que proporcionan cambios de contexto de ejecucin "ligeros" (del orden de 100 ins-
trucciones) en lugar de cambios de contexto de proceso "pesados" (miles de instrucciones). Puesto que todas las hebras de
un proceso dado comparten el mismo espacio de memoria, un cambio de contexto ligero slo modifica el punto de ejecu-
cin del programa y las variables locales. Un cambio de proceso (el cambio de coutexto pesado) debe intercambiar el espa-
cio de memoria completo.
Las principales desventajas de los mecanismos multihebra son:
1. Se produce una ralentizacin mientras las hebras estn esperando a utilizar los recursos compartidos.
2. Se requiere un gasto adicional de procesador para gestionar las hebras.
3. Las decisiones de diseo inadecuadas conducen a un incremento de la complejidad sin que se obtenga a cambio
ninguna ventaja.
4. Se abre la puerta a patologas tales como la inanicin de hebras, las condiciones de carrera, los interbloqueos y
los bloqueos activos (mltiples hebras estn realizando tareas individuales que el sistema no es capaz de termi-
nar).
5. Pueden aparecer incoherencias entre las distintas plataformas. Por ejemplo, al desarrollar algunos de los ejemplos
de este libro, descubr condiciones de carrera que aparecan rpidamente en algunas computadoras, pero que sin
embargo no aparecan en otras. Si desarrolla un programa en estas ltimas, podra encontrarse con desagradables
sorpresas a la hora de distribuir el programa.
Una de las mayores dificultades con las hebras se produce cuando hay ms de una tarea compartiendo un recurso (como por
ejemplo, la memoria de un objeto) y es necesario asegurarse de que no haya mltiples tareas intentando leer y modificar el
recurso al mismo tiempo. Esto requiere un uso juicioso de los mecanismos disponibles de bloqueo (por ejemplo, la palabra
clave synchronized). Estos mecanismos son herramientas esenciales, pero es necesario comprenderlos en profundidad, por-
que podrian introducirse inadvertidamente en situaciones de posible interbloqueo.
Adems, la aplicacin de hebras es todo un arte. Java est diseado para permitimos crear tantos objetos como necesitemos
para resolver nuestro problema, al menos en teora (la creacin de millones de objetos para un anlisis de elementos fmitos
en Bioingeniera, por ejemplo, podra no resultar prctica en Java sin utilizar el patrn de diseo Peso mosca). Sin embar-
go, parece que existe un lmite superior al nmero de hebras que pueden crearse, porque llegados a un cierto nmero las
hebras ralentizan el sistema. Este punto crtico puede ser dificil de detectar y depender a menudo del sistema operativo y
de la mquina NM; puede ser menor de cien o encontrarse en el rango de los miles. Puesto que muy a menudo slo crea-
remos un puado de hebras para resolver un problema, este lmite no suele ser una restriccin, pero en problemas de dise-
o ms generales s que esa restriccin puede obligamos a agregar un esquema de concurrencia cooperativo.
Independientemente de lo simple que pueda parecer la programacin multihebra empleando una biblioteca o un lenguaje
concretos, considere ese tipo de programacin como una tecnologa realmente avanzada. Siempre hay algo que puede esta-
llamos en la cara cuando menos 10 esperemos. La razn de que la cena de los filsofos resulte interesante es que se puede
ajustar de manera que el interbloqueo aparezca muy raramente, dndonos la impresin de que todo est bien diseado.
En general, utilice las hebras con cuidado y con mesura. Si sus programas multihebra son muy grandes y complejos, valo-
re utilizar un lenguaje como Erlang. ste es uno de los varios lenguajes funcionales especializados en el tema de hebras.
Puede que sea posible emplear uno de esos lenguajes para aquellas partes del programa que necesiten soporte multibebra,
21 Concurrencia 855
si es que est escribiendo muchos de estos programas y su nivel de complicacin es lo suficientemente alto como para jus-
tificar esta solucin.
Lecturas adicionales
Lamentablemente, hay mucha informacin errnea acerca de la concurrencia; esto constituye una constatacin de 10 confu-
so que puede llegar a ser este tema, y lo fcil que resulta pensar que se comprenden adecuadamente los problemas (y s de
lo que hablo porque ya he pensado muchas veces anterionnente que haba comprendido perfect.1mente el tema de las hebras
y no tengo ninguna duda de que volver a tener esa misma errnea sensacin en el futuro). Siempre es necesario b o r d ~
con una cierta mirada crtica cada nuevo documento que encontremos acerca del tema de concurrencia, para intentar enten-
der hasta qu punto la persona que ha escrito el documento comprende este tema adecuadamente. He aqu algunos libros
que podemos considerar como fiables:
Java Concurrency in Practice, por Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes y Doug Lea
(Addison-Wesley, 2006). Bsicamente, es el "quin es quin" dentro del mundo de la programacin multihebra en Java.
Concurrent Programming in Java, Second Edition, por Doug Lea (Addison-Wesley, 2000). Aunque este libro se basa sig-
nificativamente en Java SES, buena parte del trabajo de Doug se utiliz para crear las nuevas bibliotecas java.utll.concu-
rrent, as que este libro resulta esencial para comprender a fondo los problemas de concurrencia. Va ms all de la
concurrencia en Java y analiza el estado actual de la tcnica en lo que respecta a lenguajes y tecnologas. Aunque algunas
de las partes pueden resultar un tanto obtusas, merece la pena leerlo varias veces (dejando preferiblemente unos meses entre
una lectura y otra, para poder interiorizar la infonnacin). Doug es una de las pocas personas del mundo que comprende
realmente la concurrencia, as que merece la pena abordar la lectura de esta obra.
The Java Language Specificatioll, Third Editioll (Captulo 17), por Gosling, Joy, Steele y Bracha (Addison-Wesley, 2005).
La especificacin tcnica, est disponible como documento electrnico en http://java.sun.com/docs/books/jls.
Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico The Thinking in Java Annotated So/urjon Guide, disponible para
la venta en www.MindView.net.
Interfaces grficas
de usuario
Una directriz de diseo fundamental es: "Hacer fciles las cosas simples y
hacer posibles las cosas dificiles".l
El objetivo de diseo original de la biblioteca de interfaces grficas de usuario (GUI, graphical user interface) de Java 1.0
era permitir al programador construir una interfaz GUI que tuviera un buen aspecto en todas las plataformas. Ese objetivo
no se consigui. En su lugar, la herramientaAWT (Abstract Windowing Too/kit, herramienta abstracta de ventanas) de Java
produca una GUI con un aspecto igualmente mediocre en todos los sistemas. Adems, era bastante restrictiva; slo se uti-
lizaban cuatro tipos de fuentes y no se poda acceder a ninguno de los elementos GUI sofisticados que existen en el siste-
ma operativo. El modelo de programacin AWT de Java era asimismo bastante obstuso y no estaba orientado a objetos. Un
estudiante de uno de mis seminarios (que haba estado en Sun durante la creacin de Java) nos explic por qu: la herra-
mienta AWT original haba sido concebida, diseada e implementada en un solo mes. Se trata, ciertamente, de una produc-
tividad maravillosa, y tambin de una leccin instructiva sobre por qu es importante el diselo.
La situacin mejor con el modelo de sucesos AWT de Java 1.1, que adopta un enfoque orientado a objetos, mucho ms
claro, junto con la adicin de JavaBeans, un modelo de programacin con componentes que est orientado a facilitar la crea-
cin de entornos de programacin visuales. Java 2 (JDK 1.2) complet la transformacin desde el modelo A WT antiguo de
Java 1.0 sustituyendo esencialmente casi todo con las clases JFC (Java Foundation Classes), cuya parte para diseo de inter-
faces GUI se denomina "Swing". Se trata de un rico conjunto de componentes JavaBeans fciles de usar y de entender que
pueden arrastrarse y colocarse (adems de programarse de forma manual) para crear una GUI razonable. La regla de la
"tercera revisin" aplicable al sector del software (un producto no es bueno hasta la tercera revisin) parece tener vigencia
tambin en el campo de los lenguajes de programacin.
Este captulo presenta la moderna biblioteca Swing de Java y se basa en la razonable suposicin de que Swing es la biblio-
teca GUI que Sun tiene prevista como destino final para Java
2
Si por alguna razn necesita utilizar el modelo AWT
"antiguo" (porque tiene que soportar cdigo heredado o debido a limitaciones del explorador web), puede encontrar una
introduccin a dicho modelo en la primera edicin de este libro, descargable en www.MindView.net. Observe que algunos
componentes AWT continan estando presentes en Java y en algunas situaciones es preciso utilizarlos.
Tenga en cuenta que este captulo no constituye un glosario completo ni de todos los componentes Swing ni de todos los
mtodos de las clases descritas. Lo que pretendemos es proporcionar una introduccin sencilla. La biblioteca Swing es enor-
me y el objetivo de este captulo slo es familiarizar al lector con los aspectos esenciales y hacer que se sienta cmodo con
los conceptos. Si necesita ir ms all de lo que aqu se cuenta, probablemente Swing le permita resolver el problema que
tenga entre manos, aunque tendr que investigar un poco acerca del tema.
Presuponemos aqu que el lector ha descargado e instalado la documentacin del JDK desde http://java.sun.com y que tiene
la posibilidad de examinar las clases javax.swing contenidas en la documentacin para conocer los detalles concretos y los
mtodos de la biblioteca Swing. Tambin puede buscar informacin en la Web, aunque el mejor lugar para comenzar es el
propio tutorial de Swing elaborado por Sun disponible en http://java.slln.com/docslbooks/tllloria//uiswing.
I Una variante de esta regla es la que se denomina "el principio de menor estupefaccin" que esencialmente dice: "No sorprendas al usuario",
2 Observe que IBM ha creado una nueva biblioteca GUl de cdigo abierto pata su editor Eclipse (www.Eclipse.org) , que podemos evaluar como alterna-
tiva de Swing. Presentaremos esta alternativa posteriormente en el capitulo.
8S8 Piensa en Java
Existen numerosos (y voluminosos) libros dedicados especficamente a Swing, y puede consultar alguno de ellos si necesi-
ta analizar los temas con mayor profundidad o si quiere modificar el comportamiento predctemlinado de Swing.
A medida que estudiemos Swing, ver que:
1. Swing es un modelo de programacin enormemente mejorado, si se compara con muchos otros lenguajes y entor_
nos de desarrollo (no queremos decir que sea perfecto, pero s que representa un gran paso adelante). JavaBeans
(del que hablaremos hacia el final del captulo) es el marco de trabajo para dicha biblioteca.
2. Los "constructores de interfaces GUI" (entornos de programacin visual) fannan una parte fundamental de cual-
quier entorno completo de desarrollo en Java. JavaBeans y Swing permiten al constructor de interfaces GUl escri-
bir cdigo automticamente a medida que colocamos los componentes sobre fonnularios empleando herramientas
grficas. Esto pennite acelerar el desarrollo enonnemente durante la construccin de interfaces GUI, y pennite
una mayor dosis de experimentacin y, por tanto, la posibilidad de probar ms diseos y, presumiblemente, ter-
minar adoptando mejores diseos.
3. Puesto que Swing es razonablemente sencillo, an cuando uti li cemos un constructor de interfaces GUI en lugar
de realizar la codificacin a mano, el cdigo resultante debera seguir siendo comprensible. Esto resuelve uno de
los mayores problemas que los constructores de interfaces GUl presentaban en el pasado, que era que generaban
fci lmente cdigo ciertamente ilegible.
Swing contiene todos los componentes que cabra esperar ver en una interfaz de usuario moderna: desde botones con im-
genes a rboles y tablas. Se trata de una biblioteca enom1emente grande, pero ha sido diseada de tal fonna que su comple-
jidad resulta apropiada para la tarea que tengamos entre manos; si esa tarea es simple no es necesario escribir cdigo, pero
a medida que tratamos de hacer cosas ms compl ejas, la compl ejidad del cdigo se incrementa proporcionalmente.
Uno de los aspectos ms atractivos de Swi ng es lo que podramos denominar "ortogonalidad de uso". Este tnnino quiere
decir que, una vez que entendemos las ideas generales acerca de la biblioteca, usualmente podemos aplicarl as en todas par-
tes. Debido principalmente a los convenios estndar de denominacin, mientras escribamos ejemplos de este capitulo com-
prob que nonnalmente se poda adivinar con precisin qu es lo que cada mtodo haca basndose en su nombre.
Cienamente, se trata de todo un hito en lo que respecta al diseo adecuado de bibliotecas de programacin. Adems, pode-
mos generalmente complementar unos componentes con otros y las cosas funcionarn correctamente.
La navegacin mediante el teclado es automtica; podemos ejecutar una apli cacin Swing utilizando el ratn y esto no
requiere ningn esfuerzo de programacin adicional. El soporte de desplazamiento de pantalla es muy sencillo; basta con
insertar el componente en un panel JScrollPane en el momento de aadirlo al fonnulario. Caractersticas tales como las
sugerenci as de pantalla requieren, nonnalmente, una nica lnea de cdigo.
En aras de la portabilidad, Swing est escrito enteramente en Java.
Swing soporta tambin una funcionalidad bastante radical denominada '"'aspecto y esti lo seleccionables", que quiere decir
que la apari encia de la interfaz de usuario puede modificarse dinmicamente para ajustarl a a las expectativas de los usua-
rios que estn trabajando bajo diferentes platafomlas y sistemas operativos. Resulta incluso posible (aunque algo complica-
do) inventar nuestros propios aspecto y estilo de interfaz. En la Web pueden encontrarse algunos ejemplos de modelos
bsicos de interfaz.
3
A pesar de todos sus aspectos positivos, Swing no es para todos los programadores. ni tampoco ha podido resolver todos los
problemas de interfaz de usuario que los diseadores queran. Al fina l del captulo, examinaremos dos soluciones alternati-
vas a Swing: el modelo SWT patrocinado por IBM, desarrollado para el editor Eclipse que est disponible de manera gra-
tuita como biblioteca OUI autnoma de cdigo abieno y la herramienta F1ex de Macromedia para el desarrollo de intcrfaces
Flash del lado del cliente para aplicaciones web.
Applets
Cuando apareci Java, una pane de la publicidad que recibi el lenguaje se debia al concepto de applet, un programa que
puede distribuirse a travs de Internet para ejecutarlo dentro de un explorador web (dentro de un entorno de seguridad).
) Mi ejemplo favorito es el modelo "Servilleta" de Kcn Amold que hace que las ventanas parezcan dibujadas en una servi lleta. Vase Imp://napkilllajsollr-
ceforge.net.
22 Interfaces grficas de usuario 859
La gente pens que el applet Java era el paso siguiente en la evolucin de Internet, y muchos de los libros originales sobre
Java presuponan que la razn por la que uno poda estar interesado en el lenguaje era para escribir applets.
Por diversas razones, esta revolucin nunca lleg a suceder. Buena parte del problema es que la mayora de las mquinas
no incluye el software Java necesario para ejecutar applets, y descargar e instalar un paquete de 10 MB para poder ejecutar
algo que hemos encontrado casualmente en la Web no es algo que la mayora de los usuarios estn dispuestos a hacer.
Muchos usuarios se muestran incluso atemorizados ante esa idea. Los applets Java como sistema de distribucin de aplica-
ciones del lado del cliente nunca consiguieron una masa crtica, y ailllque ocasionalmente podemos seguir encontrndonos
con applets, estos componentes se han visto relegados, en general, al rincn de la historia de la informtica.
Esto no significa que los applets no sean una tecnologa interesante y valiosa. Si nos encontramos en una situacin en la que
podamos garantizar que los usuarios tienen instalado un entorno JRE (como por ejemplo dentro de un entorno corporativo),
entonces los applets (o JNLP/Java Web Start, que se describe posterionnente en el captulo) pueden ser la [onna perfecta de
distribuir programas cliente y de actualizar automticamente la mquina de todos los usuarios sin el coste y el esfuerzo habi-
tualmente necesarios para distribuir e instalar un nuevo software.
Puede encontrar una introduccin a la tecnologa de applets en los suplementos (en ingls) en lnea de este libro en
wl1lW.MindView.net.
Fundamentos de Swing
La mayora de las aplicaciones Swing se construyen dentro de un marco JFrame, que crea la ventana en el sistema opera-
tivo que estemos empleando. El ttulo de la ventana se puede fijar utlizando el constructor JFrame de la [onna siguiente:
jj: guijHelloSwing.java
import javax.swing.*;
public class HelloSwing
public static void main(String[] args)
JFrame frame '" new JFrame ("Hello Swingll);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ;
frame.setSize (300, 100);
frame.setVisible(true) ;
setDefanltCloseOperation( ) le dice a JFrame lo que debe hacer cuando el usuario ejecute una operacin de cierre. La
constante EXIT _ ON_ CLOSE le dice que salga del programa. Sin esta llamada, el comportamiento predetenninado consis-
te en no hacer nada, por lo que la aplicacin no se cerrara.
setSize( ) establece el tamao de la ventana en pxeles.
Observe la ltima lnea:
frame.setVisible(true) ;
Sin ella, no podramos ver nada en la pantalla.
Podemos hacer las cosas algo ms interesantes aadiendo una JLabel al marco JFrame:
jj: guijHelloLabel.java
import javax.swing.*;
import java.util.concurrent.*i
public class HelloLabel {
public static void main(String[] args) throws Exception {
JFrame frame = new JFrame ("Hello Swingll);
JLabel label = new JLabel (lIA Label
lT
);
frame.add(label) ;
frame. setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE) ;
frame.setSize(300, lOO} i
frame. setVisible (true) ;
860 Piensa en Java
TimeUnit.SECONDS.sleep {l );
label. setText ("Hey! This is Different! 11) ;
}
/// , -
Despus de un segundo, el texto de JLabel cambia. Aunque esto resulta entretenido y seguro, teniendo en cuenta la trivia-
lidad del programa, no resulta en realidad una buena idea que la hebra main( 1 escriba directamente en los componentes
GUI. Swing dispone de su propia hebra dedicada a recibir sucesos de la interfaz de usuario y a actualizar la pantalla. Si
comenzamos a manipular la pantalla con otras hebras, podemos encontramos con las colisiones e interbloqueos descritos en
el Captulo 21, Concurrencia.
En lugar de ello, otras hebras (como aqu main( II deben enviar las tareas para que sean ejecutadas por la hebra de despa-
cho de sucesos de Swing
4
Para hacer esto, entregamos una tarea a SwingUtilities.invokeLater( l, que la coloca en la cola
de sucesos para que la ejecute (en algn momento) la hebra de despacho de sucesos. Si hacemos esto con el ejemplo ante-
rior, lo que obtendramos es:
// : gui j Submi tLabel ManipulationTask .j ava
import javax.swing .*
import java.util.concurrent.*
public class SubmitLabelManipulationTask
public static void main(String[] argsJ throws Exception {
JFrame trame = new JFrame (lIHello Swingll );
final JLabel l abel = new JLabel ( "A Label " );
frame .addllabel) ;
frame. setDe f aultCloseOperation (JFrame .EXIT_ON_CLOSE) i
frame .setSize{300, 100) ;
frame. setVisible (true ) i
TimeUni t.SECONDS.sl eep (l)
Swingutilities .invokeLater(new Runnable() {
public void run() (
}
}) ;
}
/// , -
label. setText ( IIHey! Thi s is Different! " )
Abara ya no estamos manipulando directamente la etiqueta JLabel. En lugar de ello, enviamos un objeto Runnable, y la
hebra de despacho de sucesos se encarga de realizar la manipulacin real, cuando le toque procesar esa tarea dentro de la
cola de sucesos. Y cuando est ejecutando este objeto Runnable, no estar haciendo ninguna otra cosa, as que no se puede
producir ninguna colisin (siempre y cuando todo el cdigo del programa se ajuste a esta tcnica de enviar las solicitudes
de manipulacin a travs de SwingUtilities.lnvokeLater( ll. Esto incluye el propio arranque del programa: main() no debe
invocar los mtodos Swing como lo hace en el programa anterior, sino que en su lugar debe enviar una tarea a la cola de
sucesos.
5
Por tanto, el problema escrito correctamente tendra el siguiente aspecto:
11 : gui /SubmitSwingProgram.java
i mport javax .swing.*
import java.util.concurrent.*
public class SubmitSwingProgram extends J Frame {
JLabe l label;
public SubmitSwingProgram() {
super ("Hello Swing
ll
) i
label = new JLabel ( lO A Label" ) i
add(label) ;
4 Tcnicamente, la hebra de despacho de sucesos proviene de la biblioteca AWT.
5 Esta prctica fue aadida en Java SES, por lo que podr encontrar multitud de programas ms antiguos que no se ajustan a ella. Eso no quiere decir que
los autores de esos programas fueran ignorantes. Las prcticas sugeridas parecen estar en constante evolucin.
setDefaultCloseOperation{JFrame . EXIT_ON_ CLOSE) i
setSize (300, 100);
setVisible(true) ;
static SubmitSwingProgram SSp
publ ic static void main(String [] args) throws Exception
SwingUtilities.invokeLater(new Runnable () {
public void run() { SSp = new SubmitSwingProgram()
} ) ;
TimeUnit.SECONDS.sleep (l) i
SwingUtilities.invokeLater(new Runnable() {
public void run( ) {
}
} ) ;
ssp .labe!. set Text (" Hey! This i5 Different! n ) i
22 Interfaces grficas de usuario 861
Observe que la llamada a sleep( ) no se encuentra dentro del constructor. Si la ponemos ab, el texto de la etiqueta JLabel
original nunca aparecer, debido a que el constructor no se completa hasta que la llamada a sleep( ) finaliza y se inserta la
nueva etiqueta. Adems, si la llamada a sleep( ) se encuentra dentro del constructor, o dentro de cualquier operacin de la
interfaz de usuario, quiere decir que estaremos suspendiendo la hebra de despacho de sucesos durante la ejecucin de
sleep(), lo que generalmente no es una buena idea.
Ejercicio 1:
Ejercicio 2:
(l) Modifique HelloSwing.j ava para demostrar que la aplicacin no se cerrar sin la llamada a
setDefaultCloseOper atioo( ).
(2) Modifique HeDoLabel.j ava para demostrar que la adicin de etiquetas es dinmica, aadiendo un
nmero aleatorio de etiquetas.
Un entorno de visualizacin
Podemos combinar las ideas anteriores y reducir la redundancia del cdigo creando un entorno de visualizacin para em-
plearlo en los ejemplos Swing del resto del captulo:
/1 : net / mindview/util/SwingConsole .java
// Herramient a para ejecutar ejemplos de Swing desde
// l a consola, tanto appl ets como marcos JFrame.
package net.mi ndview. util
import javax. s wing.*;
public class SwingConsole
publ ic stat ic void
run (final JFrame f, final int width, final int height } {
SwingUtiliti es.invokeLater(new Runnable ()
public void run() [
}
j);
f .setTitle (f.getClass( ) . getSimpl eName( )) ;
f.s etDefaultCl oseOperation(JFrame.EXI T_ON_CLOSE) ;
f.setSize(width, height);
f. setVi sible{true) ;
Dado que el lector podra querer utilizar esta herramienta en otras situaciones, la hemos incluido en la biblioteca oet.mind-
vi.w.ut!. Para utilizarla, la aplicacin debe estar dentro de un marco J Frame (como lo estn todos los ejemplos del libro).
El mtodo esttico run( ) establece el ttulo de la ventana asignndole el nombre simple de la clase del marco JFrame.
Ejercicio 3: (3) Modifique SubmitSwiogProgram.java para utilizar SwingConsole.
862 Piensa en Java
Definicin de un botn
Defmir un botn es bastante simple: basta con invocar el constructor JButton con la etiqueta que queramos incluir en el
botn. Veremos posteriormente que se pueden hacer otras cosas ms atractivas, como por ejemplo incluir imgenes grficas
en los botones.
Usualmente, lo que haremos ser crear un campo para el botn dentro de nuestra clase, con el fin de poder hacer referencia
al mismo posteriormente.
JButton es un componente (con su propia ventana de pequeo tamao) que se volver a pintar automticamente como parte
de cada actualizacin. Esto quiere decir que no hace falta pintar explcitamente los botones ni ningn otro tipo de control;
basta con incluirlos en el formulario y dejar que ellos mismos se encarguen de pintarse. Normalmente, para insertar un botn
en un formulario, lo haremos dentro del constructor:
11: gui/Buttonl.java
II Inclusin de botones en una aplicacin Swing.
import javax.swing.*
import java.awt.*
import static net.mindview.util.SwingConsole.*
public class Buttonl extends JFrame
pri vate JButton
bl := new JButton{lIButton l
n
),
b2 := new JButton (IIButton 2 n)
public Buttonl() {
setLayout{new FlowLayout())
add(bl) ;
add(b2) ;
public static void main(String[] args) {
run{new Buttonl{), 200, 100);
}
///,-
Aqu hemos aadido algo nuevo: antes de colocar ningn elemento en el marco JFrame, definimos un "gestor de disposi-
cin" de tipo FlowLayout. El gestor de disposicin es la forma en que el panel decide implcitamente dnde colocar los
controles en un formulario. El comportamiento normal de un marco JFrame consiste en utilizar el gestor BorderLayout,
pero esa solucin no ftmcionaria aqu porque (como veremos posteriormente en el captulo) su comportamiento predetenni-
nado consiste en cubrir cada control completamente con los nuevos controles que se vayan aadiendo. Por el contrario,
FlowLayout hace que los controles fluyan equitativamente en el formulario de izquierda a derecha y de arriba a abajo.
Ejercicio 4: (1) Verifique que sin la llamada a setLayout() en Buttonl.java, slo aparece un botn en el programa
resultante.
Captura de un suceso
Si compilamos y ejecutamos el programa anterior, no sucede nada cuando apretamos los botones. Aqu es donde debemos
intervenir y escribir algo de cdigo para determinar qu es lo que tiene que suceder. La base fundamental de la programa-
cin dirigida por sucesos, que. estn muy relacionados con el comportamiento de ooa interfaz GUI, consiste en conectar los
sucesos con el cdigo que debe responder a esos sucesos.
La manera para llevar esto a cabo en Swing consiste en separar limpiamente la interfaz (los componentes grficos) de la
implementacin (el cdigo que queramos ejecutar cuando un suceso afecte a un cierto componente). Cada componente
Swing puede informar de todos los sucesos que le ocurran, pudiendo informar de cada tipo de suceso individualmente. Por
tanto, si no estamos interesados en, por ejemplo, si se ha desplazado el ratn por encima del botn, no registraremos nues-
tro inters en dicho suceso. Se trata de una forma muy sencilla y elegante de gestionar la programacin dirigida por suce-
sos, y una vez que se entiendan los conceptos bsicos, pueden emplearse fcilmente componentes Swing que no se hayan
visto anteriormente; de hecho, este modelo puede abarcar cualquier cosa que se pueda clasificar como un componente
JavaBean (de los que hablaremos ms adelante en el captulo).
22 Interfaces grficas de usuario 863
Al empezar, vamos a centrarnos solamente en el suceso de inters principal para los componentes que estemos utilizando.
En el caso de un botn JButton, este "suceso de inters" es que se pulse el botn. Para registrar nuestro inters en la pul-
sacin de un botn, invocamos el mtodo addActionListener() de JButton. Este mtodo espera un argumento que es un
objeto que implemente la interfaz ActionListener. Dicha interfaz contiene un nico mtodo denominado
actionPerformed(). Por tanto, para asociar cdigo con un botn JButton, implemente la interfaz ActionListener en una
clase y registre un objeto de dicha clase ante JButton mediante addActionListener( ). Entonces, se invocar el mtodo
actionPerformed() cada vez que se presione el botn (normalmente esto se denomina retrollamada) .
Pero, cul debe ser el resultado de presionar ese botn? Nos gustara que algo cambiara en la pantalla, as que introducire-
mos un nuevo componente Swing: el campo de texto JTextField. Este campo es un lugar en el que el usuario final puede
escribir texto o, en este caso, en el que el programa puede insertar texto. Aunque hay varias fonnas de crear un campo
JTextFleld, la ms simple consiste en informar al constructor sobre cul es la anchura que queremos que tenga ese campo.
Uoa vez colocado el campo JTextFleld en el formulario, podemos modificar su contenido utilizando el mtodo setText( )
(existen muchos otros mtodos eo JTextField, y puede encontrar la informacin correspondieote eo la documentacin del
JDK disponible eo http://java.sun.com) . He aqu UD ejemplo:
/1 : gui/Button2.java
1/ Respuesta a l a pulsacin de un botn.
i mpor t javax.swing .*
i mport java.awt.*
import java.awt.event.*;
import static net.mindview.util.Swi ngConsol e.*
pubIic cIass Button2 extends JFrame
private JButton
bl ::: new JButton (!lButton 1") f
b2 ::: new JButton (!lButton 2")
pri vate JTextField txt = new J TextFieId (lO )
c I ass ButtonLis tener impl ements ActionLis t ener
pubIic void actionPerformed (ActionEvent e ) {
St ring name = JButton) e.getSource ( .getText( )
txt .setText (name)
private ButtonListener b l = new But tonListener () i
public But ton2 () (
bl.addActionListener(bl) ;
b2.addActionListener(bl) ;
setLayout(new FlowLayout();
add(bl);
add(b2);
add(txt);
pubIic stat i c void main(String[] args ) {
r un (new Button2(}, 200, 150) ;
Para crear un campo JTextField y colocarlo en el formulario hay que realizar los mismos pasos que para JButton o para
cualquier otro componente Swing. La difereocia en el programa anterior radica en la creacin de la cIase ButtonListener
que implementa la interfaz ActionListener antes mencionada. El argumento de actionPerformed( ) es de tipo
ActionEvent, que contiene toda la informacin acerca del suceso y del lugar en que se ha producido. En este caso, queria-
mas describir el botn que ha sido presionado; getSource( ) devuelve el objeto en el que se ha originado el suceso y hemos
supuesto (utilizando una proyeccin de tipos) que el objeto es de tipo JButton. El mtodo getText() devuelve el texto aso-
ciado al botn, el cual se coloca en el campo JTextField para demostrar que verdaderamente se ha invocado el cdigo en
el momento de pulsar el botn.
En el constructor, se utiliza addActionListener( ) para registrar el objeto ButtonListener aute ambos botones.
864 Piensa en Java
A menudo resulta ms cmodo programar la clase ActionListener como una clase interna annima, especialmente, dado
que lo nonnal es utilizar una nica instancia de cada una de estas clases. Podemos modificar Button2.java para utilizar UDa
clase interna annima de la forma siguiente:
// : gui /But ton2b.java
/1 Utilizacin de clases internas annimas .
import javax.swing.*
import java. awt.*
impert java.awt .event.*;
impert s tat i c net . mi ndvi e w. uti l .SwingCensole.*
public class Button2b extends JFrame
private JButton
b1 = new JButton( "Button 1" ),
b2 = new JButton (IIButton 2")
private JTextField txt = new JTextField(10) ;
private Act ionListener bl = new ActionLis t ener ()
public void actionPerformed(ActionEvent el {
}
} ;
String name = JButton)e .getSource( .getText()
txt.setText(name)
public Button2b () (
bl.addActionLi stener(bl )
b2. addAct ionListener (bl) ;
set Layout (new FlowLayout (
add (bl) ;
add (b2) ;
add (txt) ;
publ ic stat i c voi d main (String[] argsl (
run (new Button2b() I 200, 150);
Esta tcnica de emplear una clase interna annima ser la que utilizaremos con preferencia (siempre que sea posible) en los
ejemplos del libro.
Ejercicio 5: (4) Cree una aplicacin utilizando la clase SwingConsole. Incluya un campo de texto y tres botones.
Cuando pulse cada botn, haga que aparezca un texto distinto en el campo de texto.
reas de texto
Un rea JTextArea se parece a un campo JTextField salvo porque puede tener mltiples lneas y tiene una mayor funcio-
nalidad. Un mtodo particularmente til es append( ); con l podemos ir colocando fcilmente la salida de datos del com-
ponente JTextArea. Corno podemos desplazar la pantalla hacia atrs, se trata de una mejora con respecto a los programas
de lnea de comandos que imprimen en la salida estndar. Por ejemplo, el siguiente programa rellena un rea JTextArea con
la salida del generador geography que hemos presentado en el Captulo 17, Anlisis detallado de los contenedores:
// : gui/TextArea.java
11 Uti li zacin del c ontrol JTextArea.
import javax.swing.*i
import java.awt.*
i mport java.awt.event.*
impert java. util.* ;
i mport net.mindview. ut i l. *
import static net.mindview.util.SwingConsole . *
publ ic class TextArea extends JFrame {
private J Button
b ::: new JButton ("Add Data 11) f
e = new JBu tton( "Clear Data");
prvate JTextArea t = new JTextArea(20, 40 ) ;
private Map<String,String> m =
new HashMap<String,String>( ) i
public TextArea () {
/1 Uti li zar todos los datos:
m.putAll (Countries.capitals ()) i
b.addAct ionList ener (new ActionListener()
public void ac tionPerformed(ActionEvent el
for{Map .Entry me : m.entrySet() )
t.append(me.getKey{) + " : 11+ me.getVal ue(}+"\n")
}
} ) ;
c.addAct ionListener(new ActionList ener() {
public void a ctionPerformed(ActionEvent e)
}
}) ;
t.setText(!1U ) i
setLayout( new FlowLayout ( i
add (new JScrollPane(t
add(b) ;
add(c) i
public stat ic void main {String [] args) {
run(new TextArea ( ) , 475, 425);
22 Inlerfaces grficas de usuario 865
En el constructor, el mapa se rellena con todos los pases y sus capitales. Observe que, para ambos botones, se crea el obje-
to ActionListener y se aade sin definir una variable intermedia, dado que no necesitamos volver a referimos a dicho obje-
to durante el programa. El botn "Add Data" (aadir datos) formatea y afiade todos los datos, y el botn "Clear Data" (borrar
datos) utiliza setText() para eliminar todo el texto de JTextArea.
Al aadir el control JTextArea al marco JFrame, lo envolvemos en un panel JScroUPane para controlar el desplazamien-
to de pantalla cuando se inserta demasiado texto en el control. Es lo nico que tenemos que hacer para obtener capacidades
completas de desplazamiento de pantalla. Habiendo intentado averiguar cmo hacer algo equivalente en algunos otros entor-
nos de programacin Gill, he de confesar que me impresionan bastante la simplicidad y el adecuado diseo de componen-
tes tales como JScroUPane.
Ejercicio 6:
Ejercicio 7:
Ejercicio 8:
(7) Transforme stringslTestRegnlarExpression.java en un programa Swing interactivo que permita
insertar una cadena de caracteres de entrada en un rea JTextArea y una expresin regular en un campo
JTextField. Los resultados deben mostrarse en un segundo control JTextArea.
(5) Cree una aplicacin usando SwingConsole, y aada todos los componentes Swing que dispongan de
un mtodo addActionListener() (bsquelos en la documentacin del JDK disponible en http://java.
sun.com. Consejo: busque addActionListener( ) utilizando el ndice). Capture sus sucesos y muestre un
mensaje apropiado para cada uno dentro de un campo de texto.
(6) Casi todos los componentes Swing derivan de Component, que dispone de un mtodo setCursor( ).
Busque este mtodo en la documentacin del JDK. Cree una aplicacin y cambie el cursor por uno de los
cursores definidos en la clase Cursor.
Control de la disposicin
La forma de colocar los componentes en un formulario en Java difiere, probablemente, de cualquier otro sistema Gill que
haya utilizado. En primer lugar, todo se fija en el cdigo, no hay ningn "recurso" que controle la colocacin de los com-
ponentes. En segundo lugar, la fonna en la que se colocan los componentes en un formulario est controlada no por la o s ~ ~
866 Piensa en Java
cin absoluta, sino por un "gestor de diseo o de disposicin" (layout manager), que decide c6mo se disponen los compo-
nentes, basndose en el orden con el que los agreguemos mediante el mtodo add( ). El tamao, la forma y la colocacin
de los componentes difieren enormemente de un gestor de diseo a otro. Adems, los gestores de diseo se adaptan a las
dimensiones del applet o de la ventana de aplicaci6n, por 10 que si se cambian las dimensiones de la ventana, el tamao, la
forma y la colocacin de los componentes pueden cambiar como resultado.
JApplet, JFrame, JWindow, JDialog, JPanel, etc., pueden contener y visualizar obj etos Component. En Container, exis-
te un mtodo denominado setLayout( ) que permite elegir un gestor de diseo diferente. En esta seccin, vamos a analizar
los diversos gestores de diseo, colocando botones en ellos (ya que sa es la cosa ms simple que podemos hacer). En estos
ejemplos no vamos a capturar los sucesos de botn, ya que slo queremos mostrar cmo se disponen los botones.
BorderLayout
A menos que indiquemos otra cosa, un marco JFrame utilizar un BorderLayout como su esquema de disposicin prede-
terminado. En ausencia de instrucciones adicionales, este gestor toma todo aquello que agreguemos con add( ) y lo coloca
en el centro, estirando el obj eto hasta alcanzar los bordes.
BorderLayout se base en la existencia de cuatro regiones de borde y un rea central. Cuando aadimos algo a un panel que
est utilizando BorderLayout, podemos emplear el mtodo sobrecargado add( ) que toma un valor constante como primer
argumento. Este valor puede ser uno de los siguientes:
BorderLayout.NORTH Arriba
BorderLayout.SOUTH Abajo
BorderLayout.EAST Derecha
BorderLayout.WEST Izquierda
BorderLayout.CENTER Rellenar la parte central, hasta alcanzar a otros componentes o
hasta alcanzar los bordes.
Si no especificamos un rea en la que colocar el objeto, el rea predeterminada ser CENTER.
En este ejemplo, utilizamos el gestor de disposicin predeterminado, ya que JFrame toma como opcin predeterminada
BorderLayont:
11: guilBorderLayoutl .j ava
II Ejemplo de BorderLayout.
import javax. swing.*
import java . awt.*
i mport static net.mindview.util.SwingConsole.*;
public class BorderLayoutl extends JFrame {
public BorderLayoutl( ) {
add(Bor derLayout . NORTH, new JButton ("Nort h
ll
;
add (BorderLayout . SOUTH, new JButton (JlSouth") ) ;
add(BorderLayout .EAST, new JButton(nEast") );
add (Bor derLayout. WEST, new JButton ( II West 11 ) ) ;
add (BorderLayout . CENTER, new JButton (If Center") ) ;
public s t atic void main( String(] args )
run(new Border Layoutl{), 300, 250 );
)
/// , -
Para todas las colocaciones salvo CENTER, el elemento que aadamos se comprime para que quepa en la cantidad de espa-
cio ms pequea posible a lo largo de una dimensin, mientras que se estira para ocupar el mximo espacio sobre la otra
dimensin. Sin embargo, CENTER se estira segn ambas dimensiones para ocupar toda la parte central.
22 Interfaces grficas de usuario 867
FlowLayout
Este gestor hace simplemente " fluir" los componentes en el formulario de izquierda a derecha, hasta que se llena la parte
superior; a continuacin, se desplaza una fila hacia abajo y contina con el fluj o de los componentes.
He aqu un ej emplo donde se selecciona FlowLayout como gestor de disposicin y luego se colocan botones en el fonnu-
lario. Observar que, con FlowLayout, los componentes se muestran con su tamao "natural", Un contro1 JButton, por
ejemplo, tendr el tamao de su cadena de caracteres asociada.
JI : gui jFlowLayoutl.java
JI Ej empl o de FlowLayout.
i mpor t j avax.swi ng .* ;
i mpo r t j ava .awt.*i
impor t static net .mindview.ut il.SwingConsole. *
public c lass Fl owLayoutl extends JFrame {
public FlowLayoutl () {
setLayout{new Fl owLayout ()) i
for(int i = O; i < 20; i'- T)
add (new JButton ( lfBu tton 11 + i));
publ i c static voi d ma i n(Str i ng [] args)
run (new FlowLayoutl() I 300, 300) ;
Todos los componentes se compactarn para tener el tamao ms pequeo posible cuando se emplea un gestor FlowLayout,
por lo que el comportamiento que se obtiene puede resultar algo sorprendente. Por ejemplo, como una etiqueta JLabel ten-
dr el tamao de su cadena de caracteres asociada, si tratamos de justificar a la derecha su texto, la visualizacin no se modi-
ficar.
Observe que si cambiamos el tamao de la ventana, el gestor de disposicin har que los componentes vuelvan a fluir en
consecuencia.
GridLayout
GridLayout pennite construir una tabla de componentes, y a medida que los aadimos, esos componentes se colocan de
izquierda a derecha y de arriba a abajo dentro de la cuadrcula. En el constructor, especificamos el nmero de filas y colum-
nas necesarias y dichas filas y columnas se disponen en iguales proporciones.
JJ : gui/GridLayoutl.java
JI Ejempl o de GridLayout.
i mport javax . swi ng.*;
impor t j ava.awt.*;
impor t stat i c net.mindview.util.SwingConsole.*
public cIass Gri dLayoutl extends JFrame
public GridLayoutl () {
setLayout(new GridLayout( 7 ,3)} ;
for(int i = O; i < 20; i ++}
add (new JButton (!1Button n +- i )) i
pubI i c s t atic voi d main(string [} a rgs )
r un(new Gr i dLayoutl() , 300 , 300);
En este caso, hay 21 casillas pero slo 20 botoues. La ltima casilla se deja vaca porque GridLayout no efecta ningn
proceso de "equilibrado".
868 Piensa en Java
GridBagLayout
GridBagLayout proporciona una gran cantidad de control a la hora de decidir cmo disponer exactamente las regiones de
la ventana y cmo stas deben reformatearse cuando el tamao de la ventana cambie. Sin embargo, tambin es el gestor de
disposicin ms complicado, y resulta bastante dificil de comprender. Est pensado principalmente para la generacin auto-
mtica de cdigo por parte de un constructor de interfaces GUI (los constructores de interfaces GUI pueden usar
GridBagLayout en lugar de nn sistema de posicionamiento absoluto). Si el diseo es tan complicado qne piensa que puede
necesitar GridBagLayout, es mejor que emplee una herramienta de construccin de interfaces GUI para generar ese dise-
o. Si, por alguna razn quiere conocer todos los detalles acerca de este gestor de disposicin, debe consultar para empezar,
alguno de los libros dedicados especficamente al tema de Swing.
Como alternativa, puede evaluar la utilizacin de TableLayout, que no forma parte de la biblioteca Swing pero puede des-
cargarse de http://java.sun.com. Este componente est apilado sobre GridBagLayout y oculta la mayor parte de su comple-
j idad, as que permite simplificar enormemente el diseo.
Posicionamiento absoluto
Tambin resulta posible establecer la posicin absoluta de los componentes grficos:
1, Asigne el valor null al gestor de disposicin del objeto Container: setLayout(null) .
2_ Invoque setBounds( ) o reshape( ) (dependiendo de la versin del lenguaje) para cada componente, pasando
como parmetro un rectngulo de contorno en coordenadas de pxel. Puede hacer esto en el constructor o en
paint( ), dependi endo del efecto que quiera consegnir.
Algunos constructores de interfaces GUI utilizan esta tcnica de manera intensiva, pero normalmente sta no es la mejor
forma de generar cdigo.
BoxLayout
Debido a que los programadores tenan muchas dificultades a la hora de comprender y utilizar GridBagLayout, Swing tam-
bin incluye BoxLayout, que proporciona muchos de los beneficios de GridBagLayout pero sin la complejidad asociada.
A menudo podemos utilizar este gestor de disposicin cuando necesitemos colocar manualmente los componentes (de
nuevo, si el diseo se hace demasiado complej o, utilice una herramienta de construccin de interfaces GUI que se encargue
de generar automticamente la disposicin de componentes). BoxLayout permite controlar la colocacin de los componen-
tes en sentido vertical u horizontal, as como el espaciado entre los componentes. Podr encontrar algooos ej emplos bsicos
de BoxLayout en los suplementos en linea (en ingls) del libro, disponibles en www.MindView.net.
Cul es la mejor solucin?
Swing es bastante potente; pueden hacerse una gran cantidad de cosas con slo unas lneas de cdigo. Los ejempl os mos-
trados son bastante simples y, de cara a aprender a utilizar la biblioteca tiene sentido escribirlos manualmente. De hecho,
podemos conseguir una gran cantidad de funcionalidad combnando gestores de disposicin simples. Llegados a un punto,
sin embargo, deja de tener sentido escribir de forma manual los formularios GUI; la tarea se hace demasiado complicada y
no es una buena forma de invertir nuestro tiempo de programacin. Los diseadores de Java y de Swing orientaron el len-
guaje y las bibliotecas para soportar las herramientas de construccin de interfaces GUI, que han sido creadas con el expre-
so propsito de facili tar la experiencia de programacin. Mientras que comprendamos el tema de los gestores de disposicin
y sepa..1JlOS cmo tratar los sucesos (tal como se describe a continuacin) no resulta particulannente importante conocer los
detalles acerca de cmo disponer los componentes de forma manual; dej e que la herramienta apropiada se encargue de hacer
su tarea por usted (Java est diseado, despus de todo, para incrementar la productividad de los programadores).
El modelo de sucesos de Swing
En el modelo de sucesos de Swing, un componente puede iniciar ("disparar") un suceso. Cada tipo de sucesos est repre-
sentado por una clase diferente. Cuando se dispara un suceso, es recibido por uno o ms "escuchas" que actan de acuerdo
22 Interfaces grficas de usuario 869
con ese suceso. Por tanto, el origen de un suceso y el lugar donde ese suceso se trata pueden estar separados. Puesto que
nonnahnente emplearemos los componentes Swing tal como son, pero necesitaremos escribir cdigo personalizado que se
invoque cuando los componentes reciban un suceso, ste es un ejemplo excelente de la separacin que existe entre interfaz
e implementacin.
Cada escucha de sucesos es un objeto de una clase que implementa un tipo concreto de interfaz de escucha. Por tanto, como
programador, todo lo que tenemos que hacer es crear un objeto escucha y registrarlo ante el componente que est dispa-
rando el suceso. Este registro se realiza invocando el mtodo addXXXListener( ) en el componente encargado de dispa-
rar el suceso, donde "XXX" representa el tipo de suceso para el que estamos a la escucha. Podemos determinar fcilmente
los tipos de sucesos que pueden tratarse fijndonos en los nombres de los mtodos "addListener" y si intentamos detectar
los sucesos incorrectos, descubriremos un error en tiempo de compilacin. Veremos ms adelante en el captulo que
JavaBeans tambin utiliza los nombres de los mtodos "addListener" para determinar los sucesos que un componente Bean
puede tratar.
Por tanto, toda la lgica de sucesos estar contenida dentro de la clase escucha. Cuando creamos una clase escucha, la nica
restriccin es que sta tiene que implementar la interfaz adecuada. Podemos crear una clase escucha global, pero sta es una
situacin en la que las clases internas tienden a ser muy tiles, no slo porque proporcionan un agrupamiento lgico de las
clases escucha dentro de la interfaz del usuario o de las clases de la lgica del negocio a las que estn prestando servicio,
sino tambin, porque un objeto de una clase interna mantiene una referencia a su objeto padre, lo que proporciona una forma
muy adecuada para realizar invocaciones a travs de las fronteras de clase y del subsistema.
Todos los ejemplos que hemos presentado hasta ahora en este captulo han estado empleando el modelo de sucesos de
Swing, pero en el resto de esta seccin vamos a proporcionar el resto de los detalles que describen dicho modelo.
Tipos de sucesos y de escuchas
Todos los componentes Swing incluyen mtodos addXXXListener( ) y removeXXXListener( ) de tal forma que se pue-
den agregar y eliminar los tipos apropiados de escuchas para cada componente. Observar que "XXX" en cada caso tam-
bin representa el argumento del mtodo, como por ejemplo en addMyListener(MyListener m). La siguiente tabla
presenta los sucesos, escuchas y mtodos bsicos asociados, junto con los componentes bsicos que soportan esos sucesos
concretos, proporcionando los mtodos addXXXListener( ) y removeXXXListener( ). Recuerde que el modelo de suce-
sos est diseftado para ser ampliable, as que puede que se encuentre con otros tipos de sucesos y de escuchas que no estn
incluidos en esta tabla.
ActionEvent
ActionListener
addActionListener( )
removeActionListener( )
AdjustmentEvent
AdjustmentListener
addAdjustmentListener( )
removeAdjustmentListener( )
ComponentEvent
ComponentListener
addComponentListener( )
removeComponentListener( )
ContainerEvent
ContainerListener
addContainerListener( )
removeContainerListener( )
JButton, JList, JTextField, JMenuItem y sus derivados,
incluyendo JCheckBoxMenuItem, JMenu y
JPopupMenu
Jscrollbar y cualquier cosa que creemos que implemente
la interfaz Adjustable
*Component y sus derivados, incluyendo JButton,
JCbeckBox, JComboBox, Container, JPanel, JApplet,
JScrollPane, Window, JDialog, JFileDialog, JFrame,
JLabel, JList, JScrollbar, JTextArea y JTextField
Container y sus derivados incluyendo JPanel, JApplet,
JScrollPane, Window, JDialog, JFileDialog y JFrame
870 Piensa en Java
FocusEvent
FocusListener
addFocusListener( )
removeFocusListener( )
KeyEvent
KeyListener
addKeyListener( )
removeKeyListener( )
Component y sus derivados*
Component y sus derivados*
MouseEvent (tanto para los ches como para Component y sus derivados*
el movimiento del ratn)
MouseListener
addMouseListener( )
removeMouseListener( )
MouseEvent
6
(tanto para los clics como para Component y sus derivados*
el movimiento del ratn)
MouseMotionListener
addMouseMotionListener( )
removeMouseMotionListener( )
WindowEvent
WindowListener
addWindowListener( )
removeWindowListener( )
ItemEvent
ItemListener
addIternListener( )
removeItemListener( )
TextEvent
TextListener
addTextListener( )
removeTextListener( )
Window y sus derivados, incluyendo JDialog,
JFileDialog y JFrame
JCheckBox, JCheckBoxMenuItem, JComboBox, JList
y cualquier cosa que implemente la interfaz
ItemSelectable
Cualquier cosa derivada de JTextComponent, incluyendo
JTextArea y JTextField
Puede ver que cada tipo de componente soporta slo ciertos tipos de sucesos. Resulta bastante tedioso buscar todos los suce-
sos soportados por cada componente. Una solucin ms sencilla consiste en modificar el programa ShowMethods.java del
Captulo 14, Informacin de tipos, para que muestre todos los escuchas de sucesos soportados por cualquier componente
Swing que introduzcamos.
En el Captulo 14, Informacin de tipos, se present el mecanismo de reflexin y dicha funcioualidad se emple para bus-
car los mtodos de una clase concreta, bien la lista completa de todos o un subconjunto de los nombres que se correspon-
dan con una palabra clave que proporcionemos. Uno de los aspectos ms atractivos del mecanismo de reflexin es que
permite mostrar automticamente todos los mtodos de una clase, sin tener que recorrer la jerarqua de herencia y tener que
examinar las cIases base de cada nivel. As, proporciona una valiosa herramienta de ahorro de tiempo de programacin,
puesto que los nombres de la mayora de los mtodos Java son suficientemente descriptivos, podemos buscar los nombres
de mtodo que contengan una palabra en concreto. Cuando haya encontrado 10 que crea que est buscando, consulte la docu-
mentacin del JDK.
He aqu la versin GUI ms til de ShowMethods.java, especializada para buscar los mtodos "addListener" de los com-
ponentes Swing:
6 No hay ningn suceso MouseMotionEvent, an cuando parezca que debera haberlo. Los clics y el movimiento de ratn estn combinados en el s u e ~
so MouseEvent, por lo que esta segunda aparicin de MouseEvent en la tabla no es un error.
22 Interfaces grficas de usuaro 871
JI : gUi /showAddLis t eners.java
JI Mues t ra los mtodos "addXXXLis tener " de cualquier clase Swing .
i mport javax. swi ng .*;
i mport j ava . awt. *
i mport j ava.awt.event .*i
i mport j ava.lang .ref lect.*i
i mport java.util.regex.*i
import stati c net.mindview.util.SwingConsol e.*
public class ShowAddLi steners extends JFrame {
pri vate JTextField name = new J Text Field (25)
pr ivate JTextArea resu l ts = new JTe x t Area( 40, 65);
p rivate static Pat tern addLi stener =
Pattern.compil e(" (add\\w+?Listener\\( .*? \\ " ) ;
pri vate s tatic Pat t ern qual ifier =
Pattern. compi le (11 \ \ w+ \ \ ." ) ;
c lass NameL i mplemen ts ActionListener
public void act i onPerfor med (ActionEvent e) {
String nm = name.getText(} . t rim()
if (nrn . lengt h () O) {
res ul ts. setText ("No mat ch " ) ;
return
Class<? :> kind
try {
kind = CIass. f orName (" j avax. s wing . 11 + nm);
catch(ClassNotFoundExcept ion ex) {
r esult s.set Text ( !lNo match
ll
)
r eturn;
Method[] rnethods kind.getMet hods( );
resul ts. setText ( 11 11 ) ;
for(Method m : methods ) {
Ma t cher matcher =
addListener . ma t cher(m. toString(
i f(ma tcher.find ( )}
resuIts .append(qualifi er.matcher(
matcher.group(l)} . r epl aceAll(lI!1) + lI\nl1) i
public ShowAddList eners () {
NameL nameLi s t ener = new NameL() i
name. addAct i onListener (nameList ener) i
JPanel top = new JPanel ()
t op . add (new J Labe l ( "Swing class name (pr e ss Enter) : " ;
top.add(narne) ;
a dd(BorderLayout. NORTH, top ) ;
a dd( new JScrol l Pane (resul ts;
II Datos i ni c iales y prueba:
name. setText ( IJTextArea")
nameListener.actionPerformed (
new ActionEvent ( "" I O 1"" i
public static void main(String[] args) {
run( new ShowAddListeners() I 500, 400) i
872 Piensa en Java
Introducimos el nombre de la clase Swing que queramos buscar en el campo JtextField Dame. Los resultados se extraen
utilizando expresiones regulares y se muestran en un control JTextArea.
Observar que no hay botones ni otros componentes para indicar que d comienzo la bsqueda. Eso se debe a que el campo
JTextField est monitorizado por un escucha ActionListener. Cada vez que realizamos un cambio y pulsamos Intro, la li sta
se actualiza inmediatamente. Si el campo de texto no est vaco, se utiliza en Class.forName() para tratar de buscar la clase.
Si el nombre es incorrecto, Class.forName() fallar, lo que quiere decir que generar una excepcin. Esta excepcin se cap-
tura yen el control JTextArea se muestra el mensaje "No match" (no hay correspondencia). Pero si escribimos un nombre
correcto (teniendo en cuenta las maysuculas y las minsculas), Class.forName() tendr xito y getMethods() devolver
una matriz de obj etos Method.
Aqu se emplean dos expresiones regulares. La primera, addListener, busca la cadena "add" seguida por cualquier combi-
nacin de caracteres alfabticos y seguida de "Listener" y de la lista de argumentos entre parntesis. Observe que esta expre-
sin regular est encerrada entre parntesis carcter de escape, lo que significa que ser accesible como "grupo" de la
expresin regular, cuando se detecte una correspondencia. Dentro de NameL.ActionPerformed( ) se crea un objeto
Matcher pasando cada objeto Method al mtodo Pattern.matcher( ). Cuando se invoca lind() para el objeto Matcher,
devuelve true s610 si se detecta una correspondencia, y en este caso podemos seleccionar el primer grupo de corresponden-
cia entre parntesis invocando group(l). Esta cadena de caracteres sigue conteniendo cualificadores, as que para quitarlos
se emplea el objeto Patlern de tipo qualilier, al igual que se haca en ShowMethods.java.
Al final del constructor, se coloca un valor inicial en name y se ejecuta el suceso de accin para realizar una prueba con
datos iniciales.
Este programa es una fonna cmoda de investigar las capacidades de un componente Swing. Una vez que conocemos los
sucesos soportados por un componente concreto, no necesitamos buscar informacin adicional para ver reaccionar a dicho
suceso. Simplemente:
1. Tomamos del nombre de la clase de suceso y eliminamos la palabra "Event". Aadimos la palabra "Listener" a
lo que nos haya quedado. sta ser la interfaz escucha que habr que implementar en la clase interna.
2. Implementamos la interfaz anterior y escribimos los mtodos para los sucesos que queramos capturar. Por ejem-
plo, podramos estar interesados en detectar movimientos del ratn, as que escribiramos cdigo para el mtodo
mouseMoved( ) de la interfaz MouseMotionListener (hay que implementar, por supuesto, los otros mtodos,
pero a menudo existe un atajo para esta tarea, como veremos ms adelante).
3. Creamos un objeto de la clase escucha del paso 2. Lo registramos ante el componente de inters utilizando para
ello el mtodo producido al agregar como prefijo "add" al nombre del escucha. Por ejemplo,
addMouseMotionListener( ).
He aqu algunas de las interfaces escucha:
ActionListener
AdjustmentListener
ComponentListener
ComponentAdapter
ContainerListener
ContalnerAdapter
FocusListener
FocusAdapter
KeyListener
KeyAdapter
actionPerformed(ActionEvent)
adjustmentValueChanged(AdjustmentEvent)
componentHidden(ComponentEvent)
componentShown(ComponentEvent)
componentMoved(ComponentEvent)
componentResized(ComponentEvent)
componentAdded(ContainerEvent)
componentRemoved(ContainerEvent)
focusGained(FocusEvent)
focusLost(FocusEvent)
keyPressed(KeyEvent)
keyReleased(KeyEvent)
MouseListener
MouseAdapter
MouseMotionListener
mouseClicked(MouseEvent)
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mousePressed(MouseEvent)
mouseReleased(MouseEvent)
mouseDragged(MouseEvent)
22 Interfaces grficas de usuario 873
WindowAdapter
windowOpened(\VindowEvent)
windowClosing(WinctowEvent)
windowClosed(WindowEvent)
windowActivated(WindowEvent)
windowDeactivated(WindowEvent)
windowlconified(WindowEvent)
No se trata de un listado exhaustivo, en parte porque el modelo de sucesos pennite crear nuestros propios tipos de sucesos jun-
to a sus escuchas asociados. Por tanto, probablemente se encuentre con bibliotecas en las que el programador habr inventado
sus propios sucesos y los conocimientos obtenidos en este captulo le permitirn figurarse cmo hay que usar esos sucesos.
Utilizacin de adaptadores de escucha por simplicidad
En la tabla anterior, podemos ver que algunas interfaces escucha slo tienen un mtodo. Implementar estas interfaces es tri-
vial. Sin embargo, las interfaces escucha que tienen mltiples mtodos pueden ser ms cmodas de emplear. Por ejemplo,
si queremos capturar un clic de ratn (que no haya sido ya capturado por nosotros, por ejemplo, mediante un botn), nece-
sitaremos escribir un mtodo para mouseClicked( ). Pero como MouseListener es llila interfaz, hay que implementar todos
los dems mtodos, incluso aunque no hagan nada. Esto puede resultar bastante tedioso.
Para resolver el problema, algunas (pero no todas) de las interfaces escucha que disponen de ms de un mtodo se propor-
cionan con adaptadores, cuyos nombres podemos ver en la tabla anterior. Cada adaptador proporciona mtodos predetermi-
nados vaCos para cada uno de los mtodos de la jnterfaz. Cuando heredamos del adaptador, basta con sustituir slo los
mtodos que tengamos que modificar. Por ejemplo, el escucha MouseListener tpico que utilizaremos sera similar al
siguiente:
class MyMouseListener extends MouseAdapter
public v oid mouseClicked(MouseEvent e) {
// Responder al clic del ratn ...
El objetivo de los adaptadores es facilitar la creacin de clases escucha.
Sin embargo, los adaptadores presentan una desventaja. Suponga que escribimos un adaptador MouseAdapter como el
anterior:
class MyMouseListener extends MouseAdapter
public void MouseClicked(MouseEvent e) {
// Responder al clic del ratn ...
Esto funciona, pero el programador puede volverse loco tratando de ver por qu, ya que todo se compilar y ejecutar correc-
tamente, salvo porque el mtodo no ser invocado cuando se haga un clic de ratn. Puede ver el problema? El problema
se encuentra en el nombre del mtodo: MouseClicked( l en lugar de mouseClicked (l. Un simple error en el uso de mays-
culas y minsculas ha dado como resultado la adicin de un mtodo completamente nuevo. Sin embargo, este mtodo nuevo
no es el que se invoca cuando se hace clic sobre el ratn, as que no se obtienen los resultados deseados. A pesar de la inco-
874 Piensa en Java
modidad, una interfaz garantiza que los mtodos se implementen adecuadamente, porque el compilador avisar, en caso de
cometer algn error con el uso .de maysculas y de que falte por implementar un mtodo.
Una mejor alternativa para garantizar que estamos sustituyendo efectivamente un mtodo consiste en emplear la anotacin
@Override predefmida en el cdigo anterior.
Ejercicio 9: (5) Partiendo de ShowAddListeners.java, cree un programa con la funcionalidad completa de typeinfo.
ShowMethods.java.
Control de mltiples sucesos
Para demostrar que estos sucesos se estn realmente disparando, merece la pena crear un programa que controle el compor-
tamiento de un botn JButton, y que no se limite a ver si ha sido pulsado. En este ejemplo tambin nos muestra cmo here
dar nuestro propio baln de JBntton
7
En el cdigo que se muestra a continuacin, la clase MyBntton es una clase interna de TrackEvent, por lo que MyButton
puede acceder a la ventana padre y manipular sus campos de texto, lo cual es necesario para poder escribir la informacin
de estado en los campos de la ventana padre. Por supuesto, se trata de tilla solucin limitada, ya que MyButton slo puede
emplearse en conjuncin con TrackEvent. Este tipo de cdigo se denomina en ocasiones Hcdigo altamente acoplado":
jj : gui jTrackEven t . j ava
jj Mostrar l os sucesos a medida que tienen l ugar.
import javax.swing.*i
import java. awt.*;
import java.awt .event.*;
import java . uti l .*i
import static net .mi ndview.ut il. SwingConsol e .*
public class TrackEvent extends JFrame
private HashMap<String,JTextField> h
new HashMap <String,JTextField>();
private String[J event = {
} ;
"focusGained
n
, nfocusLost", "keyPressed
ll
,
"keyReleased", !1keyTyped", "mouseClicked
ll
,
"mouse Entered", "mouseExited
n
, II mousePressed
ll
,
"mouseRe l eased", IImouseDragged", "mouseMoved"
private MyButton
bl = new MyBut ton (Col or .BLUE, "testl" ),
b2 = new MyButton(Color.RED, IItest2
11
) i
class MyButton ext ends JButton {
void report(String field, String msg) {
h.get( field) .setText(msg);
FocusListener fI = new FocusListener {)
public void focusGained(FocusEvent e ) {
report( UfocusGained
ll
, e.paramString( ) i
publi c void focusLost (FocusEvent e) {
report("focusLost
n
, e .paramString() l i
}
} ;
KeyLi stener kl = new KeyListener() {
public void keyPressed(KeyBvent e)
report ( "keyPressed
ll
, e.paramString( )) ;
public void keyReleased( KeyEvent el {
7 En Java 1.O/ I.1 no se poda heredar el objeto botn ni ninguna clase til. ste era uno de los numerosos fallos de diseo fundamentales.
report ( "keyReleased
ll
, e.paramString(;
public void keyTyped(KeyEvent e l {
r epart ("keyTypedl! , e . paramString ( ) ) ;
}
} ;
MouseListener mI = new MouseLi stener () {
public void mouseClicked(MouseEvent el
report{ lI rnouseClicked", e.paramString()) i
public void mouseEntered (MouseEvent e l
repar t ( IImouseEntered", e .paramString () ) ;
public void mouseExi ted {MouseEvent el {
repart (IImouseExi t ed" , e .paramStri ng () ) ;
public voi d mousePressed (MouseEvent e) {
repart ("mousePressed
ll
, e . paramSt ring () ) ;
public void mous eReleased (MouseEvent e) {
repart ( "mollseReleased", e. paramString () ) i
}
};
MouseMotionLi stener mml new MouseMot i onLi stener()
public void mouseDragged(MouseEvent e) {
repart ( "mouseDragged ", e . paramStri ng () )
public void mouseMoved(MouseEvent e ) {
report ("mouseMoved
ll
, e.paramString {))
}
} ;
publ i c MyButton{Col or color, String labe l ) {
super (label ) i
setBackground {color )
addFocusListener(fl )
addKeyListener(kl)
addMouseListener(ml l
addMouseMot ionListener(mml) j
public TrackEvent () {
set Layout( new GridLayout(event. l ength T 1, 2 i
f or (Stri ng evt : event ) {
JText Field t = new JTextFiel d() i
t . s e t Editable(false) ;
add {new JLabel {evt, JLabel.RIGHTl);
add (t) ;
h.put (evt, tl;
add(bl) ;
add (b2 ) ;
public static void main{String [] argsl
run( new TrackEvent(), 700, 500);
22 Interfaces grficas de usuario 875
En el constructor de MyBu!ton, el color del baln se fij a mediante una llamada a SetBackgrouud( ). Todos los escuchas
se instalan con si mpl es llamadas a mtodos.
876 Piensa en Java
La clase TrackEvent contiene un HashMap para almacenar las cadenas de caracteres que representan el tipo de suceso, y
campos JtextField donde se almacena la informacin acerca de dicho suceso. Por supuesto, podamos haber creado esta
informacin estticamente en lugar de incluirla en un contenedor HashMap, pero estoy convencido de que el lector estar
de acuerdo en que el mapa es mucho ms fcil de utilizar y modificar. En particular, si necesitamos agregar o eliminar un
nuevo tipo de suceso en TrackEvent, simplemente basta con aadir o borrar una cadena de caracteres en la matriz event;
todo lo dems se hace automticamente.
Cuando se invoca reporte ), le proporcionamos al mtodo el nombre del suceso y la cadena de parmetro del suceso. Ese
mtodo utiliza el mapa HashMap h de la clase externa para buscar el campo JTextField asociado con ese nombre de suce-
so y luego coloca la cadena de parmetro dentro de dicho campo.
Resulta bastante entretenido este programa, porque con l podemos ver qu sucesos se estn esperando dentro del progra-
ma.
Ejercicio 10: (6) Cree una aplicacin utilizando SwingConsole, con un control JButton y otro control JTextField.
Escriba y asocie los escuchas apropiados, de modo que si botn tiene el foco, los caracteres escritos apa-
rezcan en el campo JTextField.
Ejercicio 11: (4) Herede un nuevo tipo de botn de JButton. Cada vez que pulse este botn, debe cambiar su color a
otro color elegido aleatoriamente. Consulte ColorBoxesojava (ms adelante en el captulo) para ver cmo
generar un valor aleatorio de color.
Ejercicio 12: (4) Monitorice un nuevo tipo de suceso en TrackEventojava aadiendo el nuevo cdigo de tratamiento de
sucesos. Deber descubrir por s mismo el tipo de suceso que quiera monitorizar.
Una seleccin de componentes Swing
Ahora que comprendemos los gestores de disposicin y el modelo de sucesos, estamos preparados para ver cmO pueden
utilizarse los componentes Swing. Esta seccin es un recorrido no exhaustivo de los componentes Swing y de las caracte-
rsticas que probablemente vaya a utilizar la mayor parte de las veces. Hemos intentado que cada ejemplo sea razonable-
mente pequeo, para poder tomar fcilmente ese cdigo y aplicarlo en otros programas.
Tenga en cuenta que:
1. Podemos ver fcilmente qu aspecto tendra la ejecucin de estos ejemplos compilando y ejecutando el cdigo
fuente descargable correspondiente a este captulo (www.MindView.net).
20 La documentacin del JDK disponible en http://java.sun.com contiene todas las clases y los mtodos de Swing
(aqu slo mostramos algunos).
3. Debido al convenio de denominacin empleado a los sucesos Swing, resulta bastante fcil adivinar cmo escri-
bir e instalar una nueva rutina de tratamiento para lID tipo de suceso concreto. Utilice el programa de bsqueda
Show AddListeners.java, presentado anterionnente en el captulo, como ayuda a la hora de investigar un com-
ponente concreto.
4. Cuando las cosas comiencen a complicarse demasiado. piense en utilizar un constructor de interfaces GUI.
Botones
Swing incluye distintos tipos de botones. Todos los botones, casillas de verificacin, botones de opcin e incluso elemen-
tos de men heredan de AbstractButlon (que en realidad, ya que estn incluidos los elementos de men, debera, proba-
blemente, haberse llamado "AbstractSelector" o algn otro nombre igualmente genrico). Ms adelante veremos la
utilizacin de los elementos de men, pero el sigui ente ejemplo ilustra los distintos tipos de botones disponibles:
11: guilButtons. j ava
II Diversos botones Swing.
import javax.swing.*
import javax. s wing.border.*
import j avax. swing.plaf. bas ic.*
import java.awt.*
import static n et. mindview.util . SwingConsole.*
p ublic c lass Buttons extends J Frame {
pri vate JButt on jb "" n ew JButton ( IIJButton" )
pri vate BasicArrowButt on
up = new Basi cArrowBut ton(Basi cAr rowButton.NORTH) ,
down = new BasicAr rowBu tton( BasicAr rowBu tton . SOUTH),
right = new Basi cArrowBut ton (Basi cArrowButton. EAST) I
left = n ew Basi cArrowBut ton( Bas i cArrowBu tton . WEST) ;
public Bu t tons () (
set Layout(new FlowLayout());
add Ij b) ;
add(new JToggl eBut ton (uJToggleBut ton
ll
)) ;
add (new JCheckBox( uJCheckBox
ll
) ) i
a dd (new JRad i oBut t on ( II JRadioBu tton 11 ) ) i
Jpane l j p = new JPanel ();
jp.setBorder (new TitledBorder ( "Di rections
lI
));
jp. addlup) ;
jp. add (down) ;
jp. addlleft) ;
jp. addlright) ;
add (jp) ;
public static void main (String [J a r gs) {
r un (new Bu t tons(}, 350, 2 00}
22 Interfaces grficas de usuario 877
El ejemplo comienza con el botn BasicArrowButton de javax.swing.plaf.basic, y luego contina con los diversos tipos
especficos de botones. Cuando se ejecuta el ejemplo, vemos que el botn conmutador mantiene su ltima posicin, pulsa-
do o no pulsado. Pero las casillas de veriftcacin y los botones de opcin se comportan de forma idntica, pennitiendo sim-
plemente hacer clic para activarlo O desactivarlo (heredan de JToggleButton).
Grupos de botones
Si queremos que una serie de botones de opcin se comporte de forma "or exclusiva", debemos aadirlos a un "grupo de
botones". Sin embargo, como el siguiente ejemplo demuestra, cualquier botn de tipo AbstractButton puede aadirse a un
grupo ButtonGroup.
Para evitar repetir una gran cantidad de cdigo, este ejemplo utiliza el mecanismo de reflexin para generar los grupos de
diferentes tipos de botones. Esto se ve en makeBPanel( J, que crea un grupo de botones en un control JPanel. El segundo
argumento de make8Panel( J es una matriz de objetos String. Para cada objeto String, se aade al control JPanel un botn
de la clase representada por el primer argumento:
//: gui /ButtonGroups . java
ji Uso de l mecanismo de reflexin para c rear grupos
// de d ifere ntes t ipos de botones Abst rac tBut ton .
i mport javax.swing.*
import javax. s wing. border .*
import java.awt .*i
import java . l ang.refl ect.*
import stati c net . mindview. util.SwingConsol e.*;
publ ic class ButtonGroups ext ends JFrame {
private static Str ng [) ds = {
11 J une 11 , uWard" , uBeaver", "Wa llyll , " Eddie", "Lumpy"
} ;
st atic Jpanel makeBPanel (
Class<? ext ends Abstract Button> k ind, String [] i ds) {
But tonGroup bg = new ButtonGroup (}
878 Piensa en Java
Jpanel jp = new Jpanel()
String title = kind.getName()
title = title.substring(title.lastlndexOf('. 1) + 1) i
jp.setBorder(new TitledBorder(title)) i
for (String id : ids) {
AbstractButton ab = new JButton(l!failed") i
try (
JJ Obtener el mtodo constructor dinmico
JJ que toma un argumento de tipo String:
Constructor ctor =
kind.getConstructor(String.class)
JJ Crear un nuevo objeto:
ab = (AbstractButton) ctor.newl nstance (id)
catch (Exception ex) {
System.err.println("can1t create TI + kind)
bg.addlabl;
jp.addlabl;
return jp
public ButtonGroups()
setLayout(new FlowLayout())
add(makeBPanel(JButton.class, ids)) i
add(makeBPanel(JToggleButton.class, ids))
add(makeBPanel(JCheckBox.class, ids)) i
add(makeBPanel(JRadioButton.class, ids))
public static void main(String[] args)
run(new ButtonGroups(), 500, 350)
}
///,-
El ttulo del borde est tomado del nombre de la clase, una vez eliminada la informacin de ruta. El botn AbstractButton
se inicializa con un control JBution que tiene la etiqueta "Failed", de modo que si ignoramos el mensaje de excepcin,
seguiremos viendo el botn en la pantalla. El mtodo getConstructor() produce un objeto Constructor que toma la matriz
de argumentos de los tpos contenidos en la lista de objetos Class que se pasan a getConstructor( ). Entonces todo lo que
tenemos que hacer es invocar newInstance( ), pasndole la lista de argumentos, que en este caso es simplemente el objeto
String de la matriz ids.
Para conseguir un comportamiento de tipo "or exclusivo" con los botones, creamos un grupo de botones y aadimos cada
uno de los botones deseados al grupo. Al ejecutar el programa, veremos que todos los botones excepto JButton exhiben este
comportamiento de tipo "or exclusivo".
Iconos
Podemos utilizar un objeto Icon dentro de un control JLabel o de cualquier control que herede de AbstractButton (inclu-
yendo JButton, JCheckBox, JRadioButton y los diferentes tipos de JMenuItem). Utilizar Icon con JLabel resulta bas-
tante sencillo (veremos un ejemplo posteriormente). El siguiente ejemplo explora todas las formas adicionales en las que
podemos emplear iconos con los botones y con sus descendientes.
Podemos utilizar cualquier archivo GIF que deseemos, pero los empleados en este ejemplo forman parte de la distribucin
de cdigo del libro, disponible en www.MindView.net. Para abrir un archivo y tomar la imagen, cree simplemente un con-
l:rol ImageIcon y pasarle el nombre del archivo. A partir de ah, podr emplear el icono resultante en su programa.
JJ: guijFaces.java
jj Comportamiento de los iconos en controles JButton.
import javax.swing.*
import java.awt.*
import j ava.awt.event .*
import sta tic net . rni ndview. util.SwingConsole.*
public class Faces extends J Frame {
private stat i c Icon [] f aces;
pri vate JButton j b, j b 2 = new JButton {UDisabl e " ) i
pr vate boolean mad = false ;
public Faces () {
faces = new Icon [] {
} ;
new Image lcon (getCl as s () . getResource ( "FaceO . gi f n ) ) ,
new I mage l con (getCl ass () .ge t Resource ( "Facel . g i f!!) ) ,
new I magelcon (getClass( } . getResource {"Face2 . gi f
U
,
new Image l con (ge tClass() .getResource( nFace3 . g if" ,
new l magel con (getCl ass () . getResource ( II Face4 . g if l1 ) ) f
jb = n ew JBu t ton (II JBu t ton " , faces[3 ] )
setLayout (new Fl owLayout(});
j b . addAct i onListener (new Act i onListene r ()
public voi d actionPerf ormed(ActionEvent e)
}
if (mad) {
jb. setlcon( f aces [3])
ma d ::: f a l se
el se {
j b . set l con (faces [ O] )
mad :: true
jb.setVert i calAl ignment (JBu t ton. TOP) ;
jb.setHorizontalAlignment{J Button.LEFT) ;
) ) ;
jb.setRol loverEnabled (true)
jb . setRol l overlcon (faces [l )}
jb.setPressedl con (faces [2] )
j b . setDisabledlcon (faces[4) ) i
jb. setToolTipText ( "YoW! " ) ;
add (jb) ;
j b2.addActionLi s tener (new ActionLis tener()
public voi d ac t i onPer f ormed(ActionEvent el
}
}J ;
if (jb. i s En abl ed ()) {
jb. setEnabl ed(f a lse)
jb2.se tText(
lI
Enable" )
else {
j b.se t Enabl ed (true ) ;
j b2 . setText ("Disable " )
add (jb2) ;
public static void main (String [] args ) {
run (new Faces {) , 250, 125)
}
///:-
22 Interfaces grficas de usuari o 879
Podemos emplear un objeto leon como argumento para muchos constructores diferentes de componentes Swing, pero tam-
bin podemos usar se!leon( ) para aladir o modifi car un obj eto Ieon. Este ejemplo tambin muestra cmo un control
JButton (o cualquier control AbstractButton) puede fij ar los distintos tipos de iconos que aparecen cuando suceden cosas
con un botn: cuando se pulsa, cuando se desactiva o cuando se pasa el ratn por encima del botn sin hacer clic. Podr
comprobar que estos efectos proporcionan a los botones un aspecto bastante animado.
880 Piensa en Java
Sugerencias
En el ejemplo anterior hemos aadido una "sugerencia" a un botn. Casi todas las clases que utilizaremos para crear nues-
tras interfaces de usuario derivan de JComponent, que cont iene un mtodo denominado setTooITipTcxt(String), que sirve
para definir una sugerencia (tool tip). Por tanto, para casi todos los componentes que coloquemos en el fonnulario, lo nico
que hace falta es decir (para un objeto jc de cualquier clase derivada de JComponent):
jc . setToolTipTe xt (UMi sugerencia 11) i
Cuando el ratn pelmanezca sobre dicho objeto JComponent durante un perodo predeterminado de tiempo, aparecer un
pequeo recuadro al lado del puntero del ratn en el que se mostrar la sugerencia.
Campos de texto
Este ejemplo muestra lo que se puede hacer con los controles JTextField:
/1 : gui/TextFields. j ava
JI Campos de texto y sucesos Java.
import j avax.swing .*
i mpor t javax.swing.event.*
impor t javax . swing .text .*i
import j ava.awt.*
impor t j ava.awt .event.*
import static net.mindview.util. SwingConsol e.*;
public c l ass TextFields extends JFrame
prvate JBut ton
bl "" new JBut ton( 1IGet Text
1l
) J
b2 = new JBut ton("Set Textil) i
prvate JTextFi e ld
t l new JTextField (30 ) ,
t2 = new JText Field (30) ,
t3 = new JText Field (30) ;
priva te Str ing s = 1111;
private UpperCaseDocument ucd
public TextFields 1) {
ne\\I UpperCaseDocument () i
tl.setDocument (ucd) ;
ucd.addDocumentListener(new TI()) i
bl . addAc tionListener(new Bl());
b2 . addAct i onListener(new B2());
tl.addActionListener(new TIA() i
setLayout( new FlowLayout();
addlbl ) ;
add (b2);
add 101) ;
addlt2);
addlt3) ;
c lass Tl implernents DocumentListener {
public void changedUpdate(DocumentEvent e ) {}
publ i c void insert Update(Document Even t el {
t2 .setText(tl . getText ()) i
t3.setText("Text: "+ tl. getText()) i
public void removeUpdate(Document Event el {
t2 .setText (t 1.getText ());
class TlA implements ActionLi s tener
private int count = O;
public void act ionPerformed (ActionEvent e) {
t3.setText ( "tl Action Event .. + count++ ) ;
c lass 81 i mpl ements ActionLi stener {
public void actionPerformed(ActionEvent el {
if(tl.getSelectedText() == null)
5= tl.getText ()
else
s = tl.getSelectedText() i
t l .setEdi table(true ) ;
class 82 implements ActionListener {
publ i c void actionPerf ormed(ActionEvent e) {
ucd.setUpperCase (false ) i
tl . setText ( " Inserted by Butte n 2 : " + sl i
ucd . setUpperCase(true) ;
tl. setEditable (false l ;
publi c s tatic void main(String[] args)
r un(new Text Fields(), 375 , 200);
class UpperCaseDocument extends PlainDocument
prvate boolean upperCase = true;
public voi d setUpperCase(boolean flag) {
upperCase = flag
publ ic vo i d
i nsertSt ring (int offset , String st r, At t r ibuteSet attSet )
t hr ows BadLocationExcep tion {
if (upperCase) str = str. toUpperCase( ) ;
super .insertString(of f set , str, at tSet)
}
/// , -
22 Interfaces graficas de usuario 881
El control t3 de tipo JTextField se incluye para disponer de un lugar en el que infonnar cada vez que se dispare el escncha
de accin para el control tI de tipo JTextField tI. Podr comprobar que el escucha de accin de un control JTextField slo
se dispara cuando se pulsa la tecla Intro.
El control tl de tipo JTextField tiene asociados varios escuchas. El escucha TI es un objeto DocumentListener que res-
ponde a cualquier cambio que se produzca en el "documento" (el contenido del control JTextField, en este caso). Ese escu-
cha copia automticamente todo el texto de tl en t2. Adems, el documento de t1 ha sido definido como una clase derivada
de PlainDocument, denominada UpperCaseDocument, que fuerza a todos los caracteres a aparecer en maysculas. Esta
clase detecta automticamente los caracteres de retroceso y se encarga de realizar el borrado, ajustando la posicin del cur-
sor de texto y gestionando toda la operacin de la manera que cabra esperar.
Ejercicio 13: (3) Modifique TextFields.java para que los caracteres en t2 retengan su condicin original de mayscu-
las o minsculas con la que fueron escritos, en lugar de transformar automticamente todo a maysculas.
Bordes
JComponent contiene un mtodo denominado setBorder( ), que permite aadir diversos bordes interesantes a cualquier
componente visible. El siguiente ejemplo ilustra varios de los diferentes bordes disponibles utilizando un mtodo denomi-
nado showBorder() que crea un control JPanel y agrega en cada caso el borde correspondiente. Asimismo, el ejemplo uti-
liza el mecanismo RTTI para averiguar el nombre del borde que se est usando (quitando la informacin de ruta) y luego
inserta dicho nombre en una etiqueta JLabel situada en la zona central del panel:
882 Piensa en Java
jj: guijBorders. j ava
jj Diferentes bordes Swing.
import javax.swing.*;
i mpor t javax. swing.border.*;
import j ava.awt.*i
i mport static net. mi ndv iew.util.SwingConsole .*
publi c cIass Borders extends JFrame {
static JPanel showBorder (Border b) {
JPanel jp = new JPanel()
jp.setLayout (new BorderLayout(;
String nro = b.getClass() .toString ();
nm = nm.subs tring(nm.last l ndexOf (' .1) + 1) i
jp.add(new JLabel(nm, JLabel .CENTER),
BorderLayout.CENTER) ;
j p. set Border(b)
return jPi
pUblic Borders()
setLayout(new GridLayout (2, 4;
add (showBorder (new T t ledBorder ( "Title
l1
)
add(showBorder(new EtchedBorder ());
add(showBorder(new LineBorder(Color. BLUE);
add (showBorder (
new Matt eBorder (5,5, 30,30, Color.GREENl;
add(showBorder(
new BevelBorder(BevelBorder . RAISED )
a dd (showBorder (
new SoftBevelBorder(Bevel Border.LOWERED);
add(showBorder(new CompoundBorder(
new EtchedBorder(),
new LneBorder(Color . RED
publ i c stat ic void main{String(] args )
run(new Borders(), 500, 30 0)
}
jjj,-
Tambin podemos crear nuestros propios bordes y colocarlos en botones, etiquetas, etc., es decir, en cualquier cosa deriva-
da de JComponent.
Un mini-editor
El control JTextPalle proporciona un gran soporte de edicin de texto, sin demasiado esfuerzo. El siguiente ejemplo hace
un uso muy simple de este componente, ignorando gran parte de su funcionalidad:
jj: guij TextPane.java
jj El cont rol JText Pane es un pequeo editor .
i mport javax . swing.*
i mport java.awt.*
import java . awt.event.*
import net.mindview.util.*;
import static net.mindview.ut il.SwingConsole.*
public c l ass TextPa ne extends JFrame {
private JBut ton b := new JButton(IfAdd Textil ) i
private JText Pane tp := new JTextPane()
private static Generator 99 =
new RandomGenerator.St ring(7) i
publ ic TextPane () {
b.addAct ionListener (new ActionLi s tener() {
public void actionPerformed (ActionEvent e l
for(int i == 1; i < 10; i ++)
tp.setText(tp.getTextO .... sg.next() + " \n") ;
}
}) ;
add(new JScrollPane(tp) ) i
add(BorderLayout.SOUTH, b);
public static void main (String [] args) {
run(new TextPane() I 475, 425) j
22 Interfaces grficas de usuario 883
El botn aade texto generado aleatoriamente. La intencin del control JTextPane es pennitir editar el texto en pantalla,
por lo que ver que no existe un mtodo append(). En este ejemplo (que admito que constituye un uso muy pobre de las
capacidades de JTextPane), el texto tiene que capturarse, modificarse y volverse a colocar en el panel utili zando setText().
Los elementos se aaden al control J Frame utilizando su gestor predeterminado BorderLayout. El control JTextPane se
aade (dentro de un panel JScrollPane) sin especificar una regin, por lo que el gestor rellena con l, el centro del panel,
hasta los bordes del mismo. El botn JButton se aade a la regin SOUTH, por lo que el componente encajar en dicha
regi6n, en este caso, el botn se mostrar en la parte inferior de la pantalla.
Observe la funcionalidad integrada de JTextPane, como por ejemplo, el salto automtico de lnea. Este control tiene otras
muchas funcionalidades que puede examinar en la documentacin del JDK.
Ejercicio 14: (2) Modifique TextPane.java para usar Wl control JTextArea en lugar de JTextPane.
Casillas de verificacin
Una casilla de verificacin proporciona una forma de efectuar una nica eleccin binaria, Est fonnada por un pequeo
recuadro y una etiqueta. El recuadro almacena normalmente una pequea "x." minscula (o alguna otra indicacin de que la
casi Ua est activada) o est vaca, dependiendo de si el elemento ha sido seleccionado o no,
Nonnalmente, creamos un control JCheckBox utilizando un constructor que tome la etiqueta como argumento. Podemos
establecer y consultar el estado y tambin establecer y consultar la etiqueta, si es que queremos leerla o modificarla despus
de haber creado el control JCheckBox.
Cada vez que se activa o desactiva un control JCheckBox se produce un suceso que se puede capturar de la misma fornla
que para un botn: utilizando un objeto ActionListener. El siguiente ejemplo emplea un control JTextArea para enumerar
todas las casillas de verificacin que hayan sido activadas:
jj: guijCheckBoxes .java
jj Utilizacin de controles JCheckBox.
import javax.swing.*
import java.awt.*;
i mport java.awt.event.*
import static net. mindview. ut i l ,swingConsole.*
public class CheckBoxes extends JFrame {
private JText Area t = new JText Area(6, 15 ) ;
pri vate .JChec kBox
ebl new JCheckBox ("Check Box 1" ) ,
cb2 = new JCheckBox ( I! Check Box 2" ) ,
eb3 = new JCheckBox ( n Check Box 3
11
l ;
public CheckBoxes() {
cbl.addActionListener(new ActionListener()
pUblic void actionPerformed(Ac t i onEvent e)
}
} ) ;
trace(l!l ", cbl)
884 Piensa en Java
cb2.addActionListen er(new Act ionLi stener()
public void act ionPerformed( Act ionEvent el
trace( '12", cb2 );
}
}) ;
cb3.addActionListener(new ActionLi stener ()
publ ic void ac tion Performed(ActionEvent e)
trace(1l3", eb3);
}
}) ;
setLayout(new FlowLayout() } i
add(new JScrollPane(t
add lebl) ;
a dd(eb2) ;
add (eb3) ;
pr vate void trace (String b, JCheckBox eb) {
if(cb .isSelect ed( ) )
t . append ( 11 Box
else
t . a ppend ( 11 Box
+ b +
+ b +
Set \nll) ;
Cleared\nn) ;
publ ic static void main(String[] args)
run(new CheckBoxes(), 200, 300) i
El mtodo trace() enva el nombre de la casilla JCheckBox seleccionada, junto con su estado actual , al control JTextArea
utilizando el mtodo append( ), de manera que podremos ver una lista acumulada de las casillas de verificacin que hayan
sido seleccionadas, junto con su estado.
Ejercicio 15: (5) Aada una casilla de verificacin a la aplicacin creada en el Ejercicio 5, capture el suceso e inserte
diferentes textos en el campo de texto.
Botones de opcin
El concepto de botones de opcin (o de radio) en la programacin de interfaces GUI proviene de las radios de automvil
anteriores a la aparicin de los circuitos electrnicos que estaban equipadas con botones mecnicos. Cuando se pulsaba uno
de ellos, todos los dems saltaban hacia arriba, As, este tipo de controles nos permite imponer que se efecte una nica elec-
cin entre varias disponibles.
Para definir un grupo asociado de botones JRadioButton, los aadimos a un grupo ButtonGroup (en un formulario puede
haber varios grupos ButtonGroup). Uno de los botones puede opcionalmente configurarse con el valor troe (utilizando el
segundo argumento del constructor). Si tratamos de asignar el valor true a ms de un botn de opcin, slo el ltimo de los
botones configurados adoptar el valor true.
He aqu un ejemplo simple del uso de los botones de opcin, donde se ilustra la captura de sucesos utilizando
ActonListener:
ji : gui/RadioButtons . java
1/ Utilizacin de controles JRadioBut ton.
i mport javax.swing. *
import java. awt .*
import j ava. awt .event .*i
import s t atic net . mi ndview. ut i l.SwingConsole . *
public class Radi oBut t ons extends J Frame {
private JTextFiel d t = new J TextFi e ld( 15);
p rivate ButtonGroup 9 = new ButtonGroup();
pri vate JRadioButton
rbl new JRadioButton (lIone
lT
, falsel I
rb2 new JRadioButton ("two", fa!se) I
rb3 new JRadioButton("three
lT
, false) i
private ActionListener al = new ActionListener()
public void actionPerformed(ActionEvent el {
t. setText (IIRadio button 11 +
}
} ;
( (JRadioButton) e .getSource () ) .getText () ) i
public RadioButtons () {
rbl.addActionListener(al) i
rb2.addActionListener(al) i
rb3.addActionListener(al) i
g.add(rbl); g.add(rb2); g.add(rb3);
t. setEditable (false) i
setLayout(new FlowLayout()) i
add(t) ;
add(rbl) ;
add(rb2) ;
add(rb3) ;
public static void main(String[] args)
run(new RadioButtons(), 200, 125);
}
///,-
22 Interfaces grficas de usuario 885
Para mostrar el estado, se emplea un campo de texto. Este campo se define como no editable, ya que slo se utiliza para
mostrar datos, no para aceptarlos como entrada. As, se trata de una alternativa a JLabel.
Cuadros combinados (listas desplegables)
Al igual que los grupos de botones de opcin, una lista desplegable es una forma de obligar al usuario a seleccionar un ele-
mento de entre un grupo de posibilidades. Sin embargo, es una forma ms compacta de conseguir este efecto y una fonna
ms fcil de cambiar los elementos de la lista sin sorprender al usuario (tambin podramos cambiar los botones de opcin
dinmicamente, pero el efecto visual resulta bastante molesto).
De manera predetenninada, el recuadro JComboBox no se parece al cuadro combinado de Windows, que permite seleccio-
nar un elemento de una lista o escribir nuestra propia seleccin. Para conseguir este comportamiento debemos invocar el
mtodo setEditable( l. Con un recuadro JComboBox, se selecciona un nico elemento de entre una lista. En el siguiente
ejemplo, el recuadro JComboBox comienza con un cierto nmero de entradas y luego se aaden nuevas entradas al recua-
dro cuando se pulsa un botn.
11: gui/comboBoxes.java
II Utilizacin de listas desplegables.
import javax.swing.*
import java.awt.*
import java.awt.event.*;
import static net.mindview.util.SwingConsole.*
public class ComboBoxes extends JFrame {
private String[] description = {
} ;
"Ebullient
lT
I ITObtuse", "Recalcitrant
ll
, IlBrilliant
lT
,
ITSomnescent", !1Timorous
TT
, "Florid
ll
, "Putrescent"
private JTextField t = new JTextField(15);
private JComboBox c = new JComboBox();
private JButton b = new JButton(TTAdd items
lT
)
private int count = O;
public ComboBoxes () {
for(int i = O; i < 4 i++)
886 Piensa en Java
c.addl t em(description [count++}) ;
t.setEditable(false l ;
b.addActionListener {new Act ionListener ()
public void actionPerformed(ActionEvent e l
i f(count < description.length)
c.addl tem(description[ count ++] )
}
} ) ;
c . addActionListener (new ActionListene r () {
public void actionPerformed(Ac tionEvent el
}
}J;
t . setText (JI index: JI + c. getSelectedlndex () + JI +
((JComboBox)e.getSource ()) .getSeleet edI tem());
setLayout (new F1owLayout(
add(t) ;
add (e) ;
add(b) ;
public stat ic void mai n(String[) argsl
run(new ComboBoxes{) 1 20 0, 175);
El control JTextField muestra el "ndice seleccionado", que es el nmero de secuencia del elemento actualmente seleccio-
nado, junto con el texto que ese elemento tiene en el recuadro combinado.
Cuadros de lista
Los cuadros de lista difieren significativamente de los cuadros JComboBox, y no slo por su distinta apariencia visual.
Mientras que 1m recuadro JComboBox se despliega cuando los activamos, un contro JList ocupa un nmero fijo de lnea
dentro de una pantalla durante todo el tiempo, y no se modifica. Si queremos ver los elementos de una lista, basta con invo-
car getSelectedValues( ), que produce una matriz de objetos String de los elementos que hayan sido seleccionados.
Un control JList permite efecfuar selecciones mltiples; si hacemos control-elic sobre ms de un elemento (manteniendo
pulsada la tecla Control mientras realizamos clics adicional es con el ratn), el elemento original continuar estando resalta-
do y podemos seleccionar tantos elementos como queramos. Si seleccionamos un elemento, y luego pulsamos Mays-clic
sobre otro elemento, todos los elementos pertenecientes al rango comprendido entre esos dos elementos quedarn seleccio-
nados. Para eliminar un elemento de un grupo, podemos hacer Control-clic sobre l.
jj , guijList.java
import javax.swi ng .*
impor t j avax.swing .border.*
import javax.swing.event.*
import j ava.awt .*
i mport java.awt.event.*;
i mport static net .mi ndview.uti1. SwingConso1e .*
public c lass List ext ends JFrame {
private String[J f 1avors {
} ;
"Chocol ate", "Strawberry " , "Vani lla Fudge Swi r 1n,
IlMint Chip", "Mocha A1mond Fudge", tlRum Ra i sin" ,
uPra1i ne Cream", IIMud Pie"
private DefaultListModel 1Items = new DefaultListModel();
private JList 1st = new JList (l I temsl
priva t e JTextArea t =
new JTextArea{flavors.1ength, 20);
private JButton b = new JButton("Add I tem
U
);
pri vate ActionListener bl = new ActionLi stener ()
public voi d act i onPerformed (ActionEvent e} {
}
} ;
i f (count < f l avors . length) {
l I tems.add (O , f lavors[count++}) i
e l s e (
/1 Desactivar , ya que no quedan ms
JI sabores por aadi r a la l ista.
b . setEnabled (false ) ;
private Lis tSelectionListener 11 =
new ListSel e ct i onListener () {
public void valueChanged{ListSelectionEvent el {
i f(e.getVal ue IsAdjus t i ng()) return
}
} ;
t . setText ( " ") i
f or(Object i t em lst.getSelectedVa l ues())
t .append(item + lI \n" ) i
private int count = O;
p ub lic List () (
t. setEditable(fa lse ) ;
setLayout(new FlowLayout()) i
/1 Crear bordes para los componentes:
Bor der brd = Borde rFactory.cr eateMatteBorder(
1 , 1, 2, 2
1
Color. BLACK) i
lst.setBorder(br d ) ;
t .setBor der(brd) ;
11 Aadir l os primeros cuatro elementos a l a list a
for (int i = Oi i < 4; i ++)
lItems.addElement (flavors[count++J ) ;
add( t) ;
add(lst) ;
add(b) ;
11 Regi s trar escuchas de sucesos
l st.addListSe l ect i onListener{l l) i
b . addActionListener (b l) ;
public stati c void main(String [] args ) {
run( new List () , 250, 375) i
Como podr observar tambin se han aadido bordes a las li stas.
22 Interfaces grficas de usuari o 887
Si simplemente queremos incluir una matriz de objetos String en un control JList, existe una solucin mucho ms sencilla:
basta con pasar la matriz al constructor JList, y ste construye la lista automticamente. La nica razn para usar el "mode-
lo de lista" en el ej emplo precedente es para poder manipular la lista durante la ejecucin del programa.
JList no proporciona un soporte directo automtico para el desplazamiento de pantalla. Por supuesto, nos bastana con inser-
tar el control JList en un panel JScrollPane, que se encargara de gestionar todos los detalles por nosotros.
Ejercicio 16: (5) Simplifique List.java pasando la matriz al constructor y eliminando la posibilidad de adicin dinmi-
ca de elementos a la lista.
Tableros con fichas
El control JTabbedPalle permite crear un "cuadro de dilogo con fichas", que tiene una serie de pestaas de archivador a
lo largo de uno de sus bordes. Cuando se pulsa en una de las fi chas, aparece un cuadro de dilogo diferente.
888 Piensa en Java
jj : guijTabbedPanel .java
jj Ejempl o de panel con fichas.
import javax.swing.*
import javax.swi ng.event.*i
impor t j ava . awt.*
import static net.mindview.util.SwingConsole.*
public class Tabbedpanel extends JFrame
private String [J flavors = {
};
"Chocolate", "Strawberry", "Vanilla Fudge Swi rl",
"Mint Chip", IIMocha Almond Fudge"
f
"Rum Rai s in",
npraline Cr eam" , II Mud Pie"
private JTabbedPane tabs = new JTabbedPane( ) ;
private JTextField txt new JTextField(20)
publ ic TabbedPanel() (
int i = O i
for(String f l avor : flavors)
tabs.addTab (f l avors(i ] ,
new JButton{"Tabbed pane " + i ++ i
tabs.addChangeListener (new ChangeListener ()
public void stateChanged(ChangeEvent e) {
txt. setText ("Tab selected: 11 +
tabs.getSel ectedlndex() ;
}
} ) ;
add (BorderLayout . SOUTH, txt ) ;
add (tabs ) ;
publ ic stati c void ma i n (String [] args )
run(new TabbedPanel ( ), 400, 250);
}
///,-
Al ejecutar el programa, vemos que el control J TabbedPane apila automticamente las fichas si hay demasiadas como para
que todas quepan en una misma fila. Podemos comprobar que esta funcionalidad cambia el tamao de la ventana al ejecu-
tar el programa desde la lnea de comandos de la consola.
Recuadros de mensaje
Los entornos de ventanas suelen contener un conjunto estndar de recuadros de mensaje que pennite presentar rpidamen-
te informacin al usuario o capturar informaci6n del usuario. En Swing, estos recuadros de mensajes estn contenidos en el
control JOptionPane. Disponemos de muchas posibilidades distintas (algunas bastante sofisticadas), y las que usaremos
ms comnmente, con toda probabilidad, son el cuadro de dilogo de mensajes y el cuadro de dilogo de confitmacin, que
se invocan con los mtodos estticos JOptionPane.showMessageDialog( ) y JOptionPane.showConfirmDialog( ). El
siguiente ejemplo muestra un subconjunto de los recuadros de mensajes disponibles con JOptionPane:
jj : gui jMessageBoxes .java
jj Ejemplo de JOptionPane.
i mport javax.swing.*
import java.awt.*i
import java.awt.event.*
i mport stat i c net .mindview.ut i l.SwingConsole.*
public c l ass MessageBoxes extends JFrame {
private JButton[] b = {
new JButton( ITAlert
lT
), new JButton( IIYe sjNoll),
new JButton( IT Color"), new JButton ("Input
"
),
new JButton("3 Vals")
) ;
private JTextFi eld t xt = new JTextFiel d (15)
private Ac t i onLis t ener al new ActionLi s tener{)
public voi d actionPerformed (ActionEvent el {
Stri ng id = ((JButton)e . getSource ( ) .getText()
i f (id.equa l s ( "Alert" ) )
)
);
JOptionPane. showMessageDialog (null,
uThere r s a bug en you! 11 I "Hey!!I I
JOpt ionPane.ERROR_MESSAGE) ;
e l se if{id.equal s {"Yes/Nol!))
JOpt i onPane. showConfirrnDialog (null ,
"er no", II c hoos e yes",
JOp t ionPane . YES_NO_OPTION) ;
else if (id.equals ( nColo r ")) {
Objec t[] aptians = { 11 Red " , "Green
n
} i
i nt sel = JOptionPane.showOptionDial og (
null, uChoose a Co l or ! I!, "Warn ing
ll
,
JOpt i onPane . DEFAULT_OPTION,
JOptionpane . WARNING_MESSAGE, null ,
aptians, options [O] ) i
if(sel ! = JOptionPane . CLOSED_OPTION)
txt . setText ( "Color Sel ected: " + options [se l] ) i
else if( id. equals ( IIInput")) {
String val = JOpt ionPane.showl nputDialog (
uHow many fi ngers do you s ee? ");
txt .setText (val ) i
el se if (id . equals ( "3 Val s
ll
}) {
Object(J sel ect i ons = {"Fi rs t " , "Second", IIThird
ll
}
Object val = JOpt i onPane.showl nputDialog (
nul l, IIChoose one
ll
, "Input
H
,
J OptionPane . INFORMATI ON_MESSAGE,
nu ll, selections , select ions [O]) ;
i f (val != nul l )
txt . s etText (val.toSt ring (
publ i c Me s sageBoxes() {
setLayout(new FlowLayout();
for(i nt i = O; i < b. length i++) {
b[i ] . addActionListener (al ) i
add(b[iJ) ;
a dd (tx t) ;
pub l ic s t a ti c void main (St ring [] ar gs )
r un (new MessageBoxes () , 200 , 200);
)
/// :-
22 Inlerfaces grficas de usuario 889
Para escribir un nico escucha ActionListener, hemos adoptado en el ejemplo la solucin, algo arriesgada, de comprobar
las etiquetas de tipo String de los botones. El problema con este enfoque es que resulta fci l obtener la etiqueta de manera
errnea, nonnalmente en lo que respecta al uso de maysculas y minsculas, y estos errores pueden ser dificHes de detectar.
Observe que showOptionDialog() y showInputDialog( ) proporcionan objetos que contienen el valor introducido por el
usuario.
Ejercicio 17: (5) Cree una aplicacin utilizando SwingConsole. En la documentacin del JDK disponible en
http://java.sun.com, localice el control JPasswordField y adalo al programa. Si el usuario escribe la
contrasea correcta, utilice JOptiollPane para proporcionar un mensaje de confinnacin al usuario.
890 Piensa en Java
Ejercicio 18: (4) Modifique MessageBoxes.java para que tenga un escucha ActionListener para cada botn (en lugar
de buscar la correspondencia por el texto del baln).
Mens
Cada componente capaz de almacenar un men, incluyendo JApplet, JFrame, JDialog y sus descendendientes, dispone de
Wl mtodo setJMenuBar() que acepta un objeto JMenuBar que representa una barra de men (slo puede haber un com-
ponente JMenuBar en cada componente concreto). Lo que hacemos es aadir mens JMenu a la barra de mens
JMenuBar y elementos de men JmenuItem a los mens JMenu. Cada objeto JMenultem puede tener asociado un escu-
cha ActionListener que se seleccione cuando se seleccione ese elemento de men.
Con Java y Swing es necesario agrupar manualmente todos los mens en el cdigo fuente. He aqu Wl ejemplo simple de
men:
JI : guijSimpleMenus. java
import javax. swing .*
i mport jav a.awt.*
i mport java.awt.event .* i
i mport stat i c net.mindview.util. Swi ngConsole.*
public c l ass SimpleMenus extends J Frame {
private JText Field t = new JTextField(15 ) i
privat e ActionLis tener al = new ActionLis tener()
public void actionPerformed(ActionEvent el
t.setText( (JMenul tem)e.getSource ( .getText{;
}
};
private J Me nu (] menus = {
};
new JMenu("Wi nken
tr
) f new JMenu(ItBlinken
lt
) f
new JMenu ("Nod 11)
private JMenu I tem[] items =
} ;
new JMenultem(llFee
ll
) f new JMenultem{I1Fi
l1
) f
ne w JMenultem{n Fon ) f new J Menu l t em{nZipll ) ,
new JNenu ltem("Zap" ) , new J Menultem( IZot " ),
new JMenultem( "Ollyll ) , new JMenu l tem( "0 xe n " ),
new JMenultem( "Free
n
)
public SimpleMenus()
for( int i == o; i < items .length i++) {
items [i] . addActionListener (al)
menus [ i % 3J .addli tems [ iJ ) ;
JMenuBar mb == new J MenuBar ( )j
f or(JMenu jm : menu s )
mb.add ljm) ;
setJMenuBar(mb) ;
setLayout(new FlowLayout();
add lt) ;
pub lic static void main (Stri ng[ ] args )
r un{new Simpl eMenus () , 200, 150) j
}
((( , -
El uso del operador mdulo en "i%3" distribuye los elementos de men entre los tres controles JMenu. Cada objeto
Jl\1enultem puede tener asociado un escucha ActionListener; aqu, se utiliza el mismo escucha ActionListener en todas
partes, pero lo nonnal es que necesitemos un escucha distinto para cada elemento Jl\'Ienultem.
22 Interfaces grficas de usuario 891
JMenuItem hereda de AbstractButtou, as que tiene algunos comportamientos similares a los de los botones. Por s
mismo, proporciona un elemento que puede situarse en un men desplegable. Existen tambin tres tipos heredados de
JMenuItem: JMenu, para almacenar otros elementos JMenuItem (de modo que pueda haber mens en cascada);
que produce una casil1a de verificacin para indicar si dicho elemento de men est seleccionado
y JRadioButtonMenuItem, que contiene un baln de opcin.
Corno ej emplo ms sofisticado, he aqu de nuevo el ej emplo de los sabores de helados para crear mens. Este ejemplo tam-
bin ilustra la definicin de mens en cascada, de ataj os de teclado, de el ementos JCheckBoxMenultem, y tambin mues-
tra la fanna de cambiar dinmicamente los mens.
ji: gui/Menus .java
// Submens, elementos de men con casi l las de verif icacin, mens
/1 i ntercambiables (ataj os de teclado) y comandos de acci n .
impor t javax .swing .*i
i mport java . awt .*
i mport j ava.awt . event.*
impor t static net. mindview.util.SwingConsole.*
publ ic cIass Menus extends JFrame {
private String [J f l avors = {
} ;
"Chocol a te
ll
, nStrawbe rry", ITVanilla Fudge Swirl" ,
Il Mi nt Chip 11 , I1Mocha Almond Fudge
ll
, "Rum Rai sint!,
" Pralne Cream
ll
, "Mud Pie"
prvate JTextFi eld t
private JMenuBar mbl
new JTextFiel d ( liNo flavor" , 30)
new J MenuBar () ;
prvate JMenu
f new J Menu( "Fil e " ) ,
ro = new JMenu ( 11 Flavors 11 ) ,
S = new JMenu ( "Safety" )
11 Solucin al ternativa:
pr vate J CheckBoxMenul tern{ ] safet y
new JCheckBoxMenultem( "Guard" ) ,
new J CheckBoxMenuI tem ( "Hide IT )
};
pri vate J Menul tem[) file = { new JMenu ltem( II 0pen
lt
) }
11 Una segunda barra de men para efectuar un intercambi o :
private JMenuBar mb2 = new JMenuBar( )
pri vate JMenu fooBar = new J Menu ( 11 f ooBar
ll
) ;
private JMenul tem[ ] other = {
} ;
11 Adicin de un a tajo de teclado e s muy simpl e,
// pero slo pueden incluirse l os a tajos en los cons truct ores
/1 en el caso de l os elementos J Menultem:
new JMenultem{IIFoo", KeyEvent .vK_ F),
new J Menul tem{IIBar
ll
, KeyEvent .VK_Al,
// Sin a tajo de teclado:
new JMenuI tem ( 11 Baz 11 ) ,
private JBut ton b = new JBut ton ( IISwap Menus
ll
);
class BL implements Act ionLi stener {
publ ic void actionPerformed(ActionEvent el {
J MenuBar m = getJMenuBar () ;
setJMenuBar (m == mbl ? mb2 : mbl )
validate() // Refrescar el marco
c lass ML implements ActionLi stener {
publi c void act ionPerforme d (ActionEvent e) {
JMenult em target = (JMenul tem) e.getSource ()
892 Piensa en Java
String actionCommand = target.getActionCommand() i
if (actionCommand. equals (IIOpen")) {
String s = t.getText() i
boolean chosen = false;
for(String flavor : flavors)
if(s.equals(flavor))
chosen = true;
if ( ! chosen)
t. setText (nChoose a flavor first! 11)
else
t.setText(ITOpening 11 + S + TI. Mmm, mm!IT);
class FL implements ActionListener {
}
public void actionPerformed(ActionEvent e)
JMenultem target = (JMenultem)e.getSource()
t.setText(target.getText())
II Alternativamente, podemos crear una clase diferente
II para cada elemento Menultem. Entonces, no tenemos
II por qu tratar de figurarnos cul es:
class FooL implements ActionListener {
public void actionPerformed(ActionEvent e)
t.setText(IIFoo selected
ll
)
class BarL implements ActionListener {
public void actionPerformed(ActionEvent e)
t. setText ("Bar selected
ll
)
class BazL implements ActionListener {
public void actionPerformed(ActionEvent e)
t. setText (ITBaz selected 11) ;
class CMIL implements ItemListener {
public void itemStateChanged(ItemEvent e)
JCheckBoxMenultem target =
(JCheckBoxMenultem)e.getSource() j

String actionCommand = target.getActionCornmand();
if (actionCommand.equals (!1Guard
ll
))
t. setText (IIGuard the Ice Cream! 11 +
"Guarding is JI + target.getState ());
el se if (actionCommand. equals (IIHide") )
t. setText (IIHide the Ice Cream! 11 +
"Is it hidden? IT + target.getState())
public Menus ()
ML mI = new ML();
CMIL cmil = new CMIL() i
safety [O] . setActionCommand ("Guard
ll
) i
safety[O] .setMnemonic(KeyEvent.VK_G)
safety[OJ . addltemListener(cmil) i
safety [lJ . setActionCommand ("Hide
ll
) ;
safety[lJ .setMnemonic(KeyEvent.VK_H) j
safety[l] . addlternListener( cmil ) ;
other [ Ol .addActionListener(new FooL(
other [l] .addAct ionListener(new BarL( i
other[2] .addActionListener(new BazL();
FL fl = new FL( ) ;
int n = O;
for (String flavor : flavors) {
JMenultem mi = new JMenultem( flavor );
mi.addActionList ener(fl) ;
m. add(mi ) ;
1/ Aadi r separadores a int ervalos:
ifl(n++ + 1 ) % 3 == O)
m. addSeparat or ( ) ;
for(JCheckBoxMenultem sfty : safety)
s.addl sfty) ;
s. setMnemoni c (KeyEvent .VK_A) ;
f. a dd (s) ;
f.setMnemoni c(KeyEvent.VK_Fl i
for( int i = Oi i < fi l e.l ength i++) {
f i l e [ i ] . addActionLi stener (f I ) i
f.add(file[i l) ;
mb l.addl f) ;
mbl. add (m) ;
setJMenuBar (mbl) ;
t . setEditabl e (f alse) i
add (t, BorderLayout. CENTER);
JI Preparar un sist ema para inter cambiar mens:
b.addAct ionLi s tener( new BL ())
b . set Mnemonic( KeyEvent . VK_ S} j
add (b, BorderLayout.NORTH) i
for (JMenul t em oth : other)
fooBar .add (oth) ;
f ooBar . s etMnemonic (KeyEven t . VK_ B) ;
mb2.add(fooBar) ;
public s tatic void main (String[] args) {
r un(new Menus (} , 300, 200);
22 Interfaces grficas de usuario 893
En este programa, hemos colocado los elementos de men en matrices y luego hemos recorrido cada matriz invocando
add() para cada elemento JMenuItem. Esto hace que la adicin o eliminacin de lUl elemento de men sea algo menos
tedioso.
Este programa crea dos controles JmenuBar para demostrar que las barras de men pueden intercambiarse de manera acti-
va mientras el programa se est ejecutando. Podemos ver cmo los patrones JM.enuBar estn compuestos de elementos
JMenu, y que cada control JMenu est formado por elementos JMenuItem, JCheckBoxMenuItem, o incluso por otros
elementos JMenu (lo que permite obtener submens). Cuando se define un control JMenuBar, se puede instalar en el pro-
grama actual mediante el mtodo setJMenuBar() . Observe que, cuando se pulsa el botn se comprueba qu men est ins-
talado actualmente invocando getJMenuBar( ) y luego se sustituye por el otro men.
A la hora de comprobar la correspondencia con la cadena de caracteres "Open", observe que la ortografia y la utilizacin de
maysculas y minsculas son crticas, pero que Java no proporciona ningwIa seal de error si no se detecta ninguna corres-
pondencia con "Open". Este tipo de comparacin entre cadenas de caracteres constituye una fuente de errores de programa-
cin.
La activacin y desactivacin de los elementos de men se gestiona automticamente. El cdi go que gesti ona los el emen-
tos JCheckBoxMenuItem muestra dos fonnas di stintas de detenninar qu es lo que se ha activado: comparacin de cade-
894 Piensa en Java
na de caracteres (la tcnica menos segura, aunque tambin se suele utilizar) y la deteccin del objeto de destino del suceso.
Tal como se muestra, podemos utilizar el mtodo getState( ) para determinar el estado. Tambin podemos cambiar el esta-
do de un objeto JCheckBoxMenuItem con setState().
Los sucesos relacionados con los mens son algo incoherentes y pueden conducir a confusin: los elementos JMenultem
utilizan escuchas ActionListener, mientras que los elementos JCheckBoxMenuItem utlizan escuchas ItemListener. Los
objetos JMenu tambin pueden soportar los escuchas ActionListener, aunque eso no suele resultar til. En general, aso-
ciaremos escuchas a cada elemento JMenuItem, JCheckBoxMenuItem o JRadioButtonMenuItem, pero el ejemplo
muestra escuchas de tipo ItemListener y ActionListener asociados a los diversos componentes de men.
Swing soporta el uso de "atajos de teclado", as que podemos seleccionar cualquier cosa derivada de AbstractButton (boto-
nes, elementos de men, etc.) utilizando el teclado en lugar del ratn. Estos atajos funcionan de fonna muy simple; para
JMenuItem, podemos utilizar el constructor sobrecargado que admite como segundo argumento el identificador de la tecla.
Sin embargo, la mayora de las clases AbstractButton no tiene constructores como ste, por lo que la fonna ms general
de resolver el problema consiste en utilizar el mtodo setMnemonic( ). En el ejemplo anterior se aaden atajos al botn y
a algunos elementos de men; al hacerlo, aparecen automticamente indicadores de los componentes que infonnan del atajo
de teclado.
Tambin podemos ver cmo se utiliza el mtodo setActionCommand( ). Este mtodo parece algo extrao, porque en cada
caso el "comando de accin", defmido por el mtodo coincide exactamente con la etiqueta del men. Por qu no utilizar
snnplemente la etiqueta en lugar de esta cadena de caracteres alternativa? El problema estriba en la internacionalizacin. Si
rehiciramos este programa para otro idioma lo que querramos es cambiar simplemente la etiqueta del men, sin tener que
cambiar el cdigo (porque sin duda ese proceso de modificacin podra introducir nuevos errores). Utilizando
setActionCommand( ), el "comando de accin" puede ser inmutable, mientras que la etiqueta de men se puede modifi-
car. Todo el cdigo funciona con el "comando de accin", as no se ve afectado por los cambios que efectuemos en las eti-
quetas de men. Observe que en este programa, no se examinan todos los componentes de men para detenninados
comandos de accin, de modo que no hemos configurado aquellos componentes de men donde ese examen no se realiza.
El grueso del trabajo tiene lugar dentro de los escuchas. BL se encarga de realizar el intercambio de los objetos JMenuBar.
En ML, se adopta la solucin de "adivinar quin ha llamado" obteniendo el origen del suceso ActionEvent y proyectndo-
lo sobre JMenuItem, despus de lo cual se extrae el comando de accin para pasarlo a travs de una cascada de instruccio-
nes if.
El escucha FL es lo bastante simple, an cuando se encarga de gestionar todos los diferentes sabores del men de sabores.
Esta tcnica resulta til si la lgica que estamos utilizando es suficientemente simple, pero en general, conviene emplear la
solucin por FooL, BarL y BazL, en la que cada escucha se asocia a un nico componente de men, lo que hace innece-
sario utilizar una lgica adicional de deteccin y nos pennite saber exactamente quin ha invocado el escucha. Incluso con
la profusin de cIases que de esta manera se genera, el cdigo tiende a ser ms pequeo, y el proceso est ms libre de erro-
res.
Como puede ver, el cdigo de especificacin de mens puede llegar rpidamente a ser muy largo y confuso. ste es un caso
en el que la solucin apropiada consistira en emplear una herramienta de construccin de interfaces GUI. Si se tiene una
buena herramienta, sta se encargar tambin del mantenimiento de los mens. .
Ejercicio 19: (3) Modifique Menus.java para utilizar botones de opcin en lugar de casillas de vetificacin en los
mens.
Ejercicio 20: (6) Cree un programa que descomponga un archivo texto en sus palabras componentes. Disttibuya dichas
palabras como etiquetas en una serie de mens y submens.
Mens emergentes
La fonna ms directa de implementar un men emergente JPopupMenu consiste en crear una clase interna que ample
MouseAdapter, y luego agregar un objeto de dicha clase interna a cada componente al que queramos aadir ese compor-
tamiento emergente:
jj: guijpopup.java
jj Creacin de mens emergentes con Swing.
import javax.swing.*
import j ava.awt . *i
import j ava.awt . eVent.*i
import static net . mindview.util.SwingConsole.*;
public c l ass Popup extends JFrame {
private JPopupMenu popup = new JPopupMenu()
private t = new JTextField(lO)
public Popup () {
setLayout(new FlowLayout());
add (t) ;
ActionLi stener al = new ActionListener()
public void act i onPerformed(ActionEvent e)
t.setText(JMenultem)e.getSource () ) .getText() ) ;
}
} ;
JMenu ltem m = new JMenul t em(" Hi ther
ll
);
m. addActionListener (al ) ;
popup.add(m) ;
m = new JMenul tem ("Yon " ) ;
m.addAct i onListener(al ) ;
popup.add(m) ;
ID = new JMenultem( "Afar")
rn.addActionListener(all;
popup. add (m) ;
popup.addSeparator() i
m = new JMenultem (11 Stay Here ") i
m.addActionListener{al) ;
popup. add (m) ;
PopupLi stener p I = new PopupListener () ;
addMouseListener (pl ) ;
t.addMouseListener(pl ) ;
class POpupLi s tener extends MouseAdapter {
public void mousePressed(MouseEvent e ) {
maybeShowPopup (e) ;
publ ic void mouseReleased(MouseEvent el {
maybeShowpopup( e) ;
private void maybeShowPopup(MouseEvent e) {
if(e.isPopupTrigger{))
popup.show(e .getComponent()} e.getX() I e . getY() ) i
public static void main(String (] args) {
r un( new Popup () , 300, 200);
22 Interfaces grficas de usuario 895
En el ejemplo se aade el mismo escucha ActionListener a cada elemento JMenuItem. El escucha extrae el texto de la eti-
queta del men y lo inserta en el campo JTextField.
Dibujo
En un buen entorno GUI, las tareas de dibujo deberan resultar razonablemente sencillas, y as sucede en la biblioteca Swing.
El problema con cualquier ejemplo de dibujo es que los clculos que determinan dnde hay que colocar cada elemento sue-
len ser bastante ms complicados que las llamadas a las rutinas de dibujo, y estos clculos estn a menudo mezclados con
las llamadas a esas rutinas, lo que puede hacer que parezca que la interfaz es ms complicada de lo que realmente es.
896 Piensa en Java
En aras de la simplicidad, considere el problema de representar una serie de datos en la pantalla; aqu, los datos estarn pro-
porcionados por el mtodo predefinido Math.sin( ), que genera una funcin matemtica seno. Para bacer las cosas algo ms
interesantes y para demostrar lo fcil que es utilizar los componentes Swing, colocaremos un deslizador en la parte inferior
del fonnulario, para controlar dinmicamente el nmero de ciclos de la onda senoidal que se van mostrando, Adems, si
cambiamos el tamatlo de la ventana, veremos que la onda seno se reajusta automticamente en la nueva ventana,
Aunque cualquier control JComponent puede ser pintado y ser utili zado como el lienzo, si queremos disponer de una super-
ficie de dibujo sencillo, lo que haremos nonnalmente ser heredar de JPanel. El nico mtodo que hay que sustituir es
paintComponent( ), que se invoca cada vez que hay que repintar dicho componente (normalmente no hace falta preocu-
parse por esto, porque Swing toma la decisin). Cuando se invoca el mtodo, Swing le pasa un objeto de tipo Graphic.,
pudiendo nosotros pasar dicho objeto para dibujar o pintar en la superficie.
En el siguiente ejemplo, toda la inteligencia relativa al proceso de dibujo se encuentra dentro de la clase SineDraw; la clase
SineWave simplemente configura el programa y el control deslizador. Dentro de SineDraw, el mtodo .etCycles() propor-
ciona un mecanismo para que otro obj eto (en este caso, el control destizador) controle el nmero de ciclos.
jj : guij sineWav e.java
jj Dibujo con Swing, utilizando un control JSlider .
i mport javax. s wing .* ;
i mpor t javax,swing.event.*
import j ava.awt. *
import stati c ne t . mi ndvi ew. ut il .SwingConsole,*
cIass SineDraw extends Jpanel {
private static final int SCALEFACTOR 200;
private int cycl es
pri vate int po ints;
pri vate double [l sine -Si
privat e int [] ptS
publ ic SineDraw(} { setCyc l es (5) ;
publ ic void paintComponent(Graphics g) {
super.paintComponent(g) ;
int maxWidth = getWidth() i
double hstep = (double)ma xWidth j (doubl e)poi nts
int maxHeight = getHeight () ;
pts = new i nt [poi nt s } ;
for{ i n t i = O; i < p o i n ts; i + + )
pts l i J =
(int ) (sines [ i] * maxHeigh tj2 * . 9S + maxHei ghtj2);
g.setColor(Color .RED) i
for{i nt i = 1 i < points i++) {
int xl (int) ((i - 1) * hstep) ;
int x 2 ( int) ( i * hstep)
i n t y 1 p t s (i- 1] ;
int y2 p t s [i J ;
g.dr awLine (xl, yl, x2, y2 )
public void setCycles(int newCycles)
cycles = newCycles;
points = SCALEFACTOR * c yc l es * 2'
sines = new double (point s l ;
for( i nt i = O; i < points; i ++ )
doubl e radian s = (Ma t h,PI / SCALEFACTOR) * i
sine s[ i) = Math . sin (radians ) ;
repaint () i
public class SineWave extends JFrame {
private SineDraw sines = new SineDraw();
private JSlider adjustCycles = new JSlider(l, 30, 5);
public SineWave () {
add (sines) ;
adjustCycles.addChangeListener(new ChangeListener()
public void stateChanged(ChangeEvent e) {
sines.setCycles(
}
}) ;
((JSlider)e.getSource()) .getValue());
add (BorderLayout. SOUTH, adjustCycles);
public static void main (String [] args) {
run(new SineWave(), 700,400);
22 Interfaces grficas de usuario 897
Todos los campos y matrices se utilizan en el clculo de los puntos que definen la onda senoidal; cycles indica el nmero
de ondas senoidales completas que deseamos, points contiene el nmero total de puntos que se dibujarn, sines contiene los
valores de la funcin seno y pts contiene las coordenadas de los puntos que se dibujarn sobre el panel JPanel. El mtodo
setCycles( ) crea las matrices de acuerdo con el nmero de puntos necesarios y rellena la matriz sines con una serie de valo-
res. Invocando repaint( J, setCycles( J fuerza a que se llame a paintComponent( J, con lo que se producir el resto de los
clculos y el proceso de redibujo.
Lo primero que hay que hacer cuando se sustituye paintComponent( J es invocar la versin de la cIase base del mtodo.
Despus, somos libres de hacer lo que queramos; normalmente, esto significa emplear los mtodos grficos que podemos
encontrar en la documentacin correspondiente a java.awt.Graphics (en la documentacin del JDK disponible en
http://java.sun.comJ, para dibujar y pintar pixeles en el control JPanel. Podemos ver que casi todo el cdigo est dedicado
a la realizacin de los clculos; las nicas dos llamadas a mtodo que se encargan de manipular de hecho la pantalla son
setColor( J y drawLine( J. Probablemente se encuentre, al hacer sus propios programas que muestren datos grficos, con
una experiencia similar: invertir la mayor parte del tiempo tratando de determinar qu es lo que quiere dibujar, pero el pro-
pio proceso de dibujo ser bastante simple.
Cuando cre este programa, la mayor parte del tiempo lo invert en intentar que se visualizara la onda sinusoida1. Una vez
resuelto eso, pens que sera bastante atractivo poder cambiar dinmicamente el nmero de ciclos. Mis experiencias de pro-
gramacin intentando hacer cosas como sta en otros lenguajes haca que fuera un poco renuente a realizar esto, pero resul-
t ser la parte ms fcil del proyecto. Cre un control JSlider (los argumentos el valor ms a la izquierda del deslizador, el
valor ms a la derecha y el valor de inicio, respectivamente, pero tambin hay otros constructores) y lo insert en el marco
JFrame. Despus examin la documentacin del JDK y observ que el nico escucha era addChangeListener, que se dis-
paraba cada vez que se cambiaba el deslizador lo suficiente como para generar un nuevo valor. El nico mtodo para este
escucha era el denominado stateChanged( J, que proporcionaba un objeto ChangeEvent con el que se podia determinar el
origen del cambio y averiguar el nuevo valor. Invocar el mtodo setCycles( ) del objeto sines permita encontrar el nuevo
valor y redibujar el panel JPanel.
En general, podr encontrar que la mayor parte de los problemas basados en Swing pueden resolverse siguiendo un proce-
so similar, y adems ver que es un proceso bastante simple, incluso si no ha utilizado un componente concreto anterior-
mente.
Si el problema es ms complejo existen otras alternativas ms sofisticadas para el tema de dibujo, incluyendo componentes
JavaBeans de otras fuentes y la APl 2D de Java. Estas soluciones caen fuera del alcance de este libro, pero puede informar-
se acerca de ellas si el cdigo que est empleando para dibujar resulta demasiado complejo.
Ejercicio 21: (5) Modifique SineWave.java para transformar SineDraw en un componente JavaBean aadiendo mto-
dos "getter" y "setter".
Ejercicio 22: (7) Cree una aplicacin utilizando SwingConsole. La aplicacin debe tener tres deslizadores, que permi-
tan ajustar los valores rojo, verde y azul en java.awt.Color. El resto del fonnulario debe ser un control
JPanel que muestre el color determinado por los tres deslizadores. Incluya tambin campos de texto no
editables que muestren los valores RGB actuales.
898 Piensa en Java
Ejercicio 23: (8) Utilizando SineWave.java como punto de partida, cree un programa que muestre un cuadrado rotato-
rio en la pantalla. Un deslizador debe controlar la velocidad de rotacin y un segundo deslizador contro-
lar el tamao del recuadro.
Ejercicio 24: (7) Recuerda ese juguete de dibujo que tena dos mandos, uno para controlar el movimiento vertical del
punto de dibujo y otro para controlar el movimiento horizontal? Cree una variante de este jugoete, utili-
zando SineWave.java como punto de partida. En lugar de mandos, utilice deslizadores. Aada un botn
que permita borrar todo el dibujo.
Ejercicio 25: (8) Comenzando con SineWave.java, cree un programa (una aplicacin utilizando la clase Swing-
Console) que dibuje una onda senoidal animada que parezca deslizarse a lo largo de la pantalla, como si
fuera un osciloscopio, controlando la animacin con un temporizador java.utiI.Timer. La velocidad de la
animacin debe poder regolarse mediante un control javax.swing.JSlider.
Ejercicio 26: (5) Modifique el ejercicio anterior para que se creen mltiples paneles con ondas sinoidales dentro de la
aplicacin. El nmero de paneles con ondas sinoidales debe controlarse mediante parmetros de la lnea
de comandos.
Ejercicio 27: (5) Modifique el Ejercicio 25 para utilizar la clase javax.swing.Timer con el fin de dirigir la animacin.
Observe la diferencia entre esta clase y java.util.Timer.
Ejercicio 28: (7) Cree una clase que represente un dado (slo una clase sin interfaz GUl). Cree cinco dados y lncelos
repetidamente. Dibuje la curva que muestre la suma de los puntos obtenidos en cada tirada y muestre la
curva evolucionando dinmicamente a medida que se hacen ms tiradas.
Cuadros de dilogo
Un cuadro de dilogo es una ventana que emerge a partir de otra ventana. Su propsito es resolver algn tema especfico,
sin atestar la ventana original con los correspondientes detalles. Los cuadros de dilogo normalmente se emplean en entor-
nos de programacin basados en ventanas.
Para crear un cuadro de dilogo, hay que heredar de JDialog, que es simplemente otro tipo de objeto Wiodow, como
JFrallle. Un control JDialog tiene un gestor de disposicin (que de manera predeterminada es BorderLayout), y tenemos
que aadir escuchas de sucesos al cuadro para poder tratar los sucesos. He aqu un ejemplo muy simple:
JJ : gui JDialogs.java
II Creacin y uso de cuadros de dilogo.
i mport javax.swing.*
import java.awt.*;
impert java.awt.event.*
impert static net.mindview.util.SwingConsole.*
c l ass MyDial og extends JDialog {
public MyDialog(JFrame parent)
super (parent, "My dialog", true ) i
setLayout (new FlowLayout()) i
add (new JLabel ( IIHere is my dialog
ll
)) i
JButton ok = new JButton ( II OKII) i
ok .addActionListener (new ActionListener()
public void actionPerformed(ActionEvent el
dispose () II Cierra el cuadro de dilogo
}
} ) ;
add lok) ;
setSize(150, 125 ) ;
public class Dialogs extends JFrame {
private JButton bl = new JButton ("Dialog Box");
priva te MyDialog dlg = new MyDialog(null) i
public Dialogs () {
bl.addActionListener(new ActionListener()
public void actionPerformed(ActionEvent e)
dlg.setVisible(true) ;
}
} ) ;
addlbl) ;
public static void main (String [] args) {
run(new Dialogs() I 125, 75) i
22 Interfaces grficas de usuario 899
Una vez creado el control JDialog, hay que invocar setVisible(true) para mostrarlo y activarlo. Cuando se cierra el cuadro
de dilogo, es necesario liberar los recursos empleados por esa ventana, invocando dispose( ).
El siguiente ejemplo es algo ms complejo, el cuadro de dilogo est fonnado por una cuadricula (utilizando GridLayout)
de un tipo especial de botn que se define aqu mediante la clase ToeButton. Este botn dibuja un marco alrededor suyo y,
dependiendo de su estado, un espacio en blanco, una "x," o una "o" en la parte central. Inicialmente, el botn est en blan-
co y luego, dependiendo de a quin le corresponda el tumo cambia a una "x" o a una "o", Sin embargo, tambin alternar
entre "x" y "o" cuando se baga clic en el botn, para proporcionar una interesante variante del juego de tres en raya. Adems,
el cuadro de dilogo puede configurarse para tener cualquier nmero de filas ocultas, modificando los valores en la venta
de aplicacin principal.
ji: gui/TicTacToe.java
// Cuadros de dilogo y creacin de sus propios componentes.
import javax.swing.*
import java.awt.*;
import java.awt.event.*;
import static net.mindview.util.SwingConsole.*;
public class TicTacToe extends JFrame
private JTextField
rows = new JTextField (113
11
) ,
col s = new JTextField(!l3!1) i
private enum State { BLANK, XX, 00 }
static class ToeDialog extends JDialog
priva te State turn = State.XX; // Comenzar con el turno de x
ToeDialog (int cellsWide, int cellsHigh) {
setTitle("The game itselff!) i
setLayout(new GridLayout(cellsWide, cellsHigh))
for(int i = O i < cellsWide * cellsHigh i++)
add(new ToeButton())
setSize(cellsWide * 50, cellsHigh * 50) i
setDefaultCloseOperation(DISPOSE_ON_CLOSE) i
class ToeButton extends JPanel {
private State state = State.BLANK
public ToeButton() { addMouseListener(new ML()); }
public void paintComponent(Graphics g) {
super.paintComponent(g) i
int
xIO,yl=O,
x2 getSize () . width - 1,
y2 getSize() .height - 1;
g.drawRect(xl, yl, x2, y2);
xl = x2/4
yl = y2/4;
int wide = x2/2, high y2/2;
900 Piensa en Java
if (st ate =:= State. XX) {
g.drawLine(xl, yl, xl + wide, yl + high) ;
g .drawLine (xl , yl + high, xl + wide, yl );
f(state == State.OQ)
g.drawOval(xl, yl, xl + wide/ 2, yl + high/2l ;
clas s ML extends Mous eAdapter {
public vold mousePressed(MouseEvent el {
if (state == State .BLANK) {
state := turn
turn :=
(turn == State.XX ? State.OO State.XX) ;
eIse
state
(stat e
repai nt () i
State. XX ? Stat e .OO : State .XX) ;
class BL implemen ts ActionListener {
publ ic void actionPerformed(ActionEvent el {
JDi alog d := new ToeDial og (
new Integer(rows.getText()),
new Integer(cols.getText())) i
d .setVi s ible(true) i
public Ti cTacToe () {
Jpanel p = ne w JPanel()
p. setLayout (new GridLayout (2 ,2) ) i
p. add (new JLabel (IfRows ti, JLabel. CENTER) } i
p. add( rows) ;
p.add(new JLabel(uColumns
u
, JLabel.CENTER)};
p.add(cols) ;
add( p, BorderLayout .NORTH) i
JButton b = new JBu t ton {ligo" } j
b. addActionListener (new BL ()} j
add(b, BorderLayout.SOUTH} ;
public stat i c voi d main{String(] args}
run(new TicTacToe{} , 200 , 200);
Puesto que los valores estticos slo se pueden encontrar en el nivel interno de la clase, las ciases internas no pueden tener
datos estticos ni clases anidadas.
El mtodo paintComponent() dibuj a el cuadrado alrededor del panel y la indicacin "x" u "o". Este proceso est lleno de
tediosos clculos, pero resulta bastante sencillo.
Los clics de ratn se capturan mediante el escucha MouseListener, que primero comprueba si hay algo escrito en el panel.
Si no, se consulta la ventana padre para averiguar a qui n le corresponde el turno, lo que establece el estado del control
ToeButton. Gracias al mecanismo de la clase intema, el control ToeButton puede acceder a los datos del padre y pasar el
turno. Si el botn ya est mostrando una indicacin "x" u "o", entonces se invierte esa indicaci n. Podemos ver, dentro de
los clculos la"comodidad que proporciona el uso de la instruccin ir-else ternaria, descrita en el Captulo 3, Operadores.
Despus de cada cambio de estado, se redibuja el control ToeButton.
22 Interfaces grficas de usuario 901
El constructor para ToeDialog es bastante simple: aade a un gestor GridLayout tantos botones como solicitemos y luego
cambia el tamao para que cada botn tenga 50 pixeles de lado.
TIcTacToe configura la aplicacin completa creando los campos JTextField (para introducirlos en las filas y columnas de
la cuadricula de botones) y el botn "inicio" con su escucha ActionListener. Cuando se pulsa el botn, es necesario extraer
los datos de JtextField y, como estn en formato Strlng, transformarlos a formato int utilizando el constructor de Integer
que toma un argumento de tipo String.
Cuadros de dilogo de archivos
Algunos sistemas operativos tienen una serie de cuadros de dilogo predefinidos especiales para gestionar la seleccin de
cosas tales como tipos de fuentes, colores, impresoras, etc. Casi todos los sistemas operativos grficos penniten abrir y guar-
dar archivos, asi que un componente de Java, JfileChooser, encapsula estas operaciones para facilitar su realizacin.
La siguiente aplicacin ilustra dos formas de cuadros de dilogo JFileChooser, una para abrir un archivo y otra para guar-
darlo. La mayor parte del cdigo debera resultar familiar al lector, y todas las actividades de inters tienen lugar dentro de
los escuchas de accin asociados con los dos clies de ratn di stintos:
/1 : gui / Fi l eChooserTest .j ava
JI Ejemplo de cuadros de dilogo de archivos.
import javax.swing.*
import java . awt .*i
import java.awt.event.*i
import static net.mindview.ut il.SwingConsole. *;
public c lass FileChooserTes t extends JFrame {
privat e JTextField
fileName = new JTextField () ,
dir = new J TextField();
priva t e JButton
open :::: new JBu t t on( "Open" ),
s ave "" new JBut t on( "Save" ) ;
public Fi l eChooserTest () {
Jpanel p = new JPanel {);
open.addActionListener {new OpenL( ;
p.add(open) ;
save.addActionListener (new SaveL( )) i
p.add{save) ;
add(p , BorderLayout.SOUTH);
dir . setEditable (false) i
fil eName.setEditable(false) ;
p = new JPanel();
p.setLayout(new GridLayout(2,1;
p.add(fi l eName) ;
p.add(dir) ;
add(p, BorderLayout . NORTH) i
class OpenL impl ements ActionLi stener
public void actionPerforrned (Ac tionEvent e l {
JFileChooser e = new JFi leChooser( }
/ / Ejempl o de cuadr o de dilogo IIOpen " (abrir ) :
int rVal = c.showOpenDial og( Fi l e ChooserTest.thi s);
if(rVal == JFileChooser.APPROVE_OPTION) {
fi l eName . setText (c. getSel ectedFile ( ) . getName () ) ;
dir . setText (c.getCurrentDirectory() .toString( )) ;
if(rVal == JFileChooser . CANCEL_OPTIONl
fileName . setText ("You pressed cancel" );
dir.setText(nu) ;
902 Piensa en Java
class SaveL implements ActionListener {
public void actionPerformed(ActionEvent e} {
JFileChooser c = new JFileChooser();
II Ej emplo de cuadro de dilogo "Save" (guardar):
int rVal = c.showSaveDialog(FileChooserTest.this);
if(rVal == JFileChooser.APPROVE_OPTION) {
fileName.setText{c.getSelectedFile() .getName());
dir.setText(c.getCurrentDirectory() .toString());
if(rVal == JFileChooser.CANCEL_OPTION) {
fileName.setText(nyou pressed cancel") i
dir.setText("")
public static void main(String[] args) {
run(new FileChooserTest(), 250, 150)
Observe que hay muchas variantes que podemos aplicar a JFileChooser, incluyendo filtros para seleccionar los nombres de
archivo pennitidos.
Para un cuadro de dilogo de apertura de archivos ("open file"), invocamos showOpenDialog( ), y para un cuadro de di-
logo para guardar el archivo ("save file"), invocamos showSaveDialog( ). Estos comandos no vuelven hasta que se cierra
el cuadro de dilogo. El objeto JFileChooser sigue existiendo, as que podemos leer datos desde el mismo. Los mtodos
getSelectedFile( ) y getCurrentDirectory( ) son dos fonuas que penuiten preguntar por el resultado de la operacin. Si
devuelven null, quiere decir que el usuario ha cancelado el cuadro de dilogo.
Ejercicio 29: (3) En la documentacin del JDK correspondiente a javax.swing, busque el control JColorChooser.
Escriba un programa con un botn que haga aparecer en fonna de cuadro de dilogo este selector de color.
HTML en los componentes Swing
Cualquier componente que admita texto puede aceptar tambin texto HTML, que ser refonuateado de acuerdo con las
reglas del lenguaje HTML. Esto quiere decir que podemos aadir muy fcilmente texto fonuateado a un componente Swing.
Por ejemplo:
11: gui/HTMLButton.java
II Adicin de texto HTML en componentes Swing.
import javax.swing.*
import java.awt.*
import java.awt.event.*
import static net.mindview.util.SwingConsole.*;
public class HTMLButton extends JFrame
private JButton b = new JButton(
"<html><b><font size=+2>" +
n <center>Hello! <br><i>Press me now! n) ;
public HTMLButton() {
b.addActionListener{new ActionListener()
public void actionPerformed{ActionEvent e)
add(new JLabel ("<html>" +
"<i><font size=+4>Kapow! 11)) i
II Forzar una redisposicin para incluir una nueva etiqueta:
validate() i
) ) ;
set Layout(new FlowLayou t ()}
add{b) ;
publ ic s tat ic void main (String[J args)
run(new HTMLButton (), 200, 500) i
22 Interfaces graficas de usuario 903
Es necesario iniciar el texto con "<html>", despus de lo cual se pueden emplear marcadores HTML. Observe que no esta-
mos obligados a incluir los marcadores normales de cierre.
ActionListener aade una nueva etiqueta JLabel al formulario, que tambin contiene texto HTML. Sin embargo, esta eti-
queta no se aade durante la construccin, as que es necesario invocar el mtodo vaJidate( ) del contenedor para forzar una
nueva disposicin de los componentes (y con ello la visualizacin de la nueva etiqueta).
Tambin podemos utilizar texto HTML para J TabbedPane, JMenuItem, JToolTIp, JRadioButton, y J CheckBox.
Ejercicio 30: (3) Escriba un programa que ilustre el uso de texto HTML en todos los elementos mencionados en el prra-
fo anterior.
Deslizadores y barras de progreso
Un deslizador (que ya ha sido utilizado en SineWave.j ava) permite al usuario introducir datos moviendo un punto de un
lado a otro, lo cual resulta bastante intuitivo de algunas situaciones (como por ejemplo, en los controles de volumen). Una
barra de progreso muestra los datos en una forma relativa, entre lUla posicin "vaca" y una posicin 'llena" para que el
usuario pueda hacerse una idea del estado en el que se encuentra el proceso. Mi ejemplo favorito consiste en asociar el des-
lizador con la barra de progreso, de modo que cuando se mueve el deslizador, la barra de progreso modifica su aspecto
correspondientemente. El siguiente ejemplo tambin ilustra la clase Progr essMonitor, que es un cuadro de dilogo emer-
gente con una funcionalidad ms rica:
/1 : gui /Pr ogr ess . java
JI Utilizacin de deslizadores, barras de progreso y monitores de prog reso.
import j avax.swing.*
import javax.swing.border.*;
i mport javax.swing.event . *
i mpor t java.awt.*
import static net.mindview.util.SwingConsole.*
public class Progress extends JFrame {
prvate JProgressBar pb = new JProgressBar()
private ProgressMonitor pm = new ProgressMonitor(
this, nMonitoring Progress", "Test
n
, 0 , lOO};
private JSlider sb =
new JSlider (JSlider . HORIZONTAL, O, 100, 60);
p ublic Progress () {
setLayout (new GridLayout(2,1) ) ;
add {pb) ;
pm. setProgress(O)
pm. setMill isToPopup (1 000) ;
sb. setValue (O )
sb.setPai ntTicks(true};
sb.setMaj orTickSpacing (20 ) ;
sb. setMi norTickSpacing (5) i
sb. setBorder (new Titl edBorder ( nSlide Me!! )) i
pb.setModel{sb . getModel{); II Modelo compartido
add (sb) ;
sb.addChangeLi stener( new ChangeListener() {
publ ic void stateChanged{ChangeEvent e) {
pm.setProgress(sb.getValue( j
904 Piensa en Java
}
} ) ;
public stati c void mai n (String [1 args) {
run( n ew Progress() , 300, 200)
La clave para asociar los componentes deslizador y b'4Ta de progreso estriba en compartir sus modelos, en la lnea:
pb.setModel(sb.getModel()) ;
Por supuesto, tambin podramos controlar los dos utilizando un escucha, pero usar el modelo es mucho ms simple en algu-
nas situaciones senci llas. El control ProgressMonitor no tiene un modelo, por lo que es obligatorio utilizar el mtodo basa-
do en escucha. Observe que el control ProgressMonitor slo se mueve hacia adelante y que se cierra automticamente una
vez que alcanza el final.
La barra de progreso JProgressBar es bastante sencilla, pero el control J Slider tiene bastantes opciones, como por ejem-
plo las que fijan la orientacin y las marcas principales y secundarias del deslizador. Observe lo fcil que es aadir un borde
con ttulo.
Ejercicio 31: (8) Cree un "indicador asinttico de progreso" que vaya cada vez ms lento a medida que se aproxime al
final. Aada un comportamiento errtico aleatorio, de manera que el indicador parezca estarse acelerando
peridicamente.
Ejercicio 32: (6) Modifique Progress.java para que, en lugar de compartir los modelos, utilice un escucha para conec-
tar el deslizador y la barra de progreso.
Seleccin del aspecto y del estilo
El concepto "aspecto y estilo seleccionables" permite al programa emular el aspecto y el estilo de diversos sistemas opera-
tivos. Podemos incluso cambiar dinmicamente el aspecto y el estilo mientras el programa se est ejecutando. Sin embar-
go, generalmente haremos una de las dos cosas: o bien seleccionar el "aspecto y estilo interplataforma" (que es el diseo
"metal" de Swing), o seleccionar el aspecto y estilo del sistema en el que nos encontramos acttIalmente, de modo que el pro-
grama Java parezca haber sido creado especficamente para dicho sistema (est es casi siempre la mejor eleccin en la mayo-
ra de los casos, para evitar confundir al usuario). El cdigo para seleccionar uno de estos dos comportamientos es bastante
simple, pero es preciso ejecutarlo antes de crear ningn componente visual, porque los componentes se construirn basn-
dose en el aspecto y estilo actuales, y no se modificarn simplemente porque cambiemos el aspecto y el estilo a mitad de
programa (dicho proceso es ms complicado y resulta menos comn, por lo que remitimos al lector a los libros dedicados
especficamente a Swing).
De hecho, si queremos usar el aspecto y estilo interplataforma ("metal"), que es caracterstico de los programas Swing, no
tenemos que hacer nada, ya que se trata de la opcin predeterminada. Pero si queremos en su lugar emplear el aspecto y esti-
lo actuales del sistema operativ0
8
, basta con insertar el siguiente cdigo, normalmente al principio de main(), pero al menos
antes de aadir ningn componente:
try {
UIManager.set LookAndFeel (
UIManager.getSysternLookAndFeelCl assName())
catch(Exception e ) {
throw new RuntimeBxception (e) i
No hace falta incluir nada en la clusula cateh porque el gestor de la interfaz de usuario UIManager tomar como opcin
predetenninada el aspecto y estilo interplataforma si los intentos de seleccionar cualquiera de las otras alternativas fallan.
Sin embargo, durante la depuracin, la excepcin puede resultar muy til, as que podemos tratar de ver al menos algunos
resultados mediante.la clusula eateh.
8 Cabra discutir si las capacidades de visualizacin de Swing bacen justicia al sistema operativo.
22 Interfaces grficas de usuario 905
He aqu un programa que acepta un argumento de la lnea de comandos para seleccionar un detenninado aspecto y estilo
que muestra el aspecto de los distintos componentes con la opcin elegida:
//: gui/LookAndFeel.java
1/ Seleccin del aspecto y el estilo de la aplicacin.
// {Args, motif}
import javax.swing.*
import java.awt.*
import static net.rnindview.util.SwingConsole.*
public cIass LookAndFeel extends JFrame {
prvate String[] choices =
I1Eeny Meeny Minnie Mickey Moe Larry Curly",split(U u)
prvate Component[] samples = {
new JButton(lIJButton") 1
} ;
new JTextField (lIJTextField") I
new JLabel (lIJLabel n) I
new JCheckBox ( 11 JCheckBox 11) I
new JRadioButton (lIRadio") I
new JComboBox{choices),
new JList(choices),
public LookAndFeel() {
super ("Look And Feel");
setLayout(new FlowLayout());
for(Component component : samples)
add (component) ;
private static void usageError() {
System.out.println(
"Usage : LookAndFeel [cross I systeml motif] !l) ;
System. exit (1) ;
public static void main(String[] args)
if(args.length == O) usageError();
if(args[O] .equals(!lcross")) {
try {
UIManager.setLookAndFeel(UIManager.
getCrossPlatforrnLookAndFeelClassName()) j
catch(Exception e) {
e.printStackTrace() j
el se if(args[O] .equals("systern
ll
))
try {
UIManager.setLookAndFeel(UIManager.
getSystemLookAndFeelClassName()) ;
catch (Exception e) {
e.printStackTrace() ;
else if (args [O] .equals("motif"))
try {
UIManager. setLookAndFeel (11 com. sun. java. !I +
"swing.plaf.motif.MotifLookAndFeel") ;
catch (Exception e) {
e.printStackTrace() ;
} el se usageError() j
II Observe que el aspecto y estilo deben configurarse
11 antes de crear cualquier componente.
906 Piensa en Java
r un(new LookAndFeel(), 300, 300) i
Puede ver que una opcin consiste en crear explcitamente una cadena de caracteres para definir el aspecto y el estilo, como
se ve con MotifLookAndFeel. Sin embargo, esa opcin y la opcin predeterminada "metal" son las nicas que pueden
emplearse legalmente en todas las plataformas, aunque hay otras cadenas de caracteres que definen el aspecto y estilo para
Windows y Macintosh, dichas cadenas slo pueden usarse en sus respectivas plataformas (puede obtener estas cadenas invo-
cando getSystemLookAndFeelClassNamc() mientras se encuentre en la plataforma deseada).
Tambin es posible crear un paquete personalizado de aspecto y estilo, por ejemplo si estamos diseando un sistema para
una compaa que quiera disponer de una apariencia distintiva en sus aplicaciones. Se trata de una tarea compleja y que
queda fuera del alcance de este libro (de hecho, ver que queda fuera del alcance de muchos libros dedicados a Swing).
rboles, tablas y portapapeles
Podr encontrar una breve introduccin y diversos ejemplos sobre estos temas en el suplemento en linea para este captulo
(en ingls) disponible en www. MindView.net.
JNLP Y Java Web Start
Resulta posible .firmar un applet por razoneS de seguridad. Esto se muestra en el suplemento en lnea de este captulo (en
ingls) disponible en www.MindView.nel. Los applets firmados son potentes y pueden tomar el lugar de una aplicacin, pero
deben ejecutarse dentro de un explorador web. Esto requiere el sobrecoste adicional que el explorador se est ejecutando en
la mquina cliente, y significa tambin que la interfaz de usuario del applet est limitada y es, a menudo, visualmente con-
fusa. Elexplorador web tiene su propio conjunto de mens y barras de herramientas, que aparecern por encima del applet
9
El protocolo lNLP (Java Network Lmmch Protocol, protocolo Java de inicio a travs de red) resuelve el problema sin sacri-
ficar las ventajas de los applets. Con mla aplicacin lNLP, podemos tratar de descargar e instalar una aplicacin Java aut-
noma en la mquina del cliente. Esta aplicacin puede ejecutarse desde la lnea de comandos, desde un icono de escritorio
o a travs del gestor de aplicaciones instalado con la implementacin JNLP. La aplicacin puede incluso ejecutarse desde el
sitio web desde el que fue inicialmente descargada.
Una aplicacin lNLP puede descargar dinmicamente recursos de Internet en tiempo de ejecucin y se puede comprobar
automticamente la versin si el usuario est conectado a Internet. Esto significa que proporciona todas las ventajas de un
applet junto con las ventajas de las aplicaciones autnomas.
Al igual que los applets, las aplicaciones JNLP necesitan tratarse con cierta precaucin por parte del sistema cliente. Debido
a esto, las aplicaciones JNLP estn sujetas a las mismas restricciones de seguridad que los applets. Al igual que los applets,
pueden implantarse mediante archivos JAR firmados, dando as al usuario la opcin de confiar en quien firma. A diferencia
de los applets, si se implantan mediante un archivo JAR no firmado, pueden seguir teniendo acceso a ciertos recursos del
sistema cliente por medio de diversos servicios de la API JNLP. El usuario deber aprobar estas solicitudes durante la eje-
cucin del programa.
lNLP describe un protocolo, no una implementacin, as que hace falta una implementacin para poder usarlo. Java Web
Star!, o JAWS, es la implementacin de referencia oficial de Sun, disponible de forma gratuita y distribuida como parte de
Java SES. Si la est utilizando para desarrollo, deber asegurarse de que el archivo JAR (javaws.jar) se encuentre en su ruta
de clases; la solucin ms cmoda es aadir javaws.jar a la ruta de clases a partir de su ruta de instalacin normal en jre/lib.
Si est implantado la aplicacin JNLP desde 1m servidor web, deber comprobar que el servidor recononozca el tipo MIME
applicationlx-java-jnlp-fIle. Si est empleando una versin reciente del servidor Torneat (http://jakarta.apache.org/tomcat)
este tipo MIME ya estar configurado. Consulte la gua de usuario de su servidor concreto.
Crear una aplicacin lNLP no es dificil. Lo que hacemos es crear una aplicacin estndar que se archva en un archivo lAR,
y luego proporcionar un archivo de arranque, que es XML sinlple que proporciona al sistema cliente toda la informacin
necesaria para descargar e instalar la aplicacin. Si decide no firmar el archivo JAR, entonces deber emplear los servicios
suministrados por la API JNLP para cada tipo de recurso de la mquina del usuario al que quiera acceder.
9 Jererny Meyer ha desarrollado esta seccin.
22 Interfaces grfi cas de usuario 907
He aqu una variante FileChooserTest.java util izando los servicios JNLP para abrir el selector de archivos, de modo que la
clase pueda implantarse como una aplicacin JNLP en un archivo JAR no firmado.
1/: gui/ j nlp/JnlpFi l eChooser.java
// Apertura de archivos en una mquina local con JNLP.
11 {Requires: j avax.jnlp.FileOpenServics
11 Hay que tener javaws.j a r en l a ruta de clases }
1/ Para crear e l archivo j nlpfilechooser. j ar, haga esto:
11 cd ..
11 cd ..
// j ar cvf guijjnlpjjnlpfilechooser .jar gui /jnlp/*.class
package gui.jnlpi
i mport javax. j nlp .* i
import javax.swing.*
import java.awt . *;
i mport java.awt .eve nt . * i
import java.io .*
public class JnlpFileChooser extends JFrame {
prvate JTextFi e l d f i l eName = new JTextFi eld()
pri vate JButton
open = new JButt on ( nopen" ),
save = new JButton (nSave
n
)
pri vate JEdi t orPane ep = new JEdi t orPane();
private JScr ollPane jsp = new JScrollPane()
private FileCont ents fil eContents
public JnlpFileChooser () {
Jpanel p = new J Panel()
open . addActionListener{new OpenL()
p.add(open) ;
save.addActionListener(new SaveL( j
p . add(save) ;
jsp.getViewport( ) .add (epl;
add (j sp, BorderLayout.CENTER);
add (p, BorderLayout . SOUTH)
f i l eName.setEdi t abl e(false) ;
p = new JPanel () ;
p.setLayout(new Gr idLayout(2, l
p.add(fileName )
add(p, BorderLayout.NORTH);
ep . setContentType (ntext
n
) ;
save. setEnabled (falsel
class OpenL implement s ActionListener
public void act i onPerformed{ActionEvent el {
Fi leOpenService fs = null
try {
fs = (Fi l eOpenService ) ServiceManager.lookup(
"javax. jnl p. FileOpenService " ) i
catch(Unavail abl eServiceExcept i on use) {
throw new RuntimeException(use)
i f (fs ! = nul l )
try (
fi l eCont ent s = fs.openFi l eDialog{".",
new St r i ng[] {ntx t
ll
, n*n}) ,;
if{fi l e Contents == nul l l
r e turn;
fileName.setText(fileContents.getName( ;
ep.read(f i leContents.getlnputStr eam(), nu l l l ;
908 Piensa en Java
catch{Exception exc) {
t hrow new RuntimeException(exc);
save. setEnabl ed (truel ;
class SaveL implements ActionListener {
public void actionPerformed(ActionEven t el {
FileSaveService fs = null
try {
}
fs = (FileSaveService ) Servi ceManager.lookup(
11 j avax . j n l p . Fi leSaveServi ce ti) i
cat ch (Unavai l ableServi ceException use) {
throw new RuntimeExcept i on(u s e) i
if (fs 1 = null ) {
t ry {
f i l e Contents = fs. saveFi leDialog ( u.lI,
new String [] { "txt " } ,
new ByteArraylnputStream(
ep . getText () . getBytes()),
fileContents.getName( ;
if(fi leContents == null )
return
fileName.setText(fi leContents . getName( i
catch(Except ion exc) {
thr ow new Runt i meException(exc)
pub l i c stati c void mai n (Stri ng (] args ) {
JnlpFi l eChooser fe = new J n l pFi leChooser () i
fe. setSize (400, 300);
fc.setVisible{true ) i
Observe que las clases FileOpenService y FileCloseService se importan del paquete javax.jnlp y que en ninguna parte del
cdigo se hace referencia directa al cuadro de dilogo JFileCbooser. Los dos servicios usados aqu deben solicitarse em-
pleando el mtodo ServiceManager.lookup(), y s6lo se podr acceder a los recursos del sistema cliente a travs de los obje-
tos devueltos por este mtodo. En este caso, los archivos del sistema de archivos del cliente estn siendo escritos y ledos
mediante la interfaz FileContent, proporcionada por lNLP. Cualquier intento de acceder a los recursos directamente utili-
zando, por ejemplo, un objeto File o un objeto FileReader hara que se generara una excepcin SecurityException de la
misma manera que si se intentaran emplear desde un applet no firmado. Si desea utilizar estas clases y no quedarse restrin-
gido a las interfaces de servicios de JNLP, tendr que firmar el archivo JAR.
El comando comentado jar en JnlpFileCbooser.java generar el archivo JAR necesario. He aqui un archivo de arranque
apropiado para el ejemplo anterior.
jj ,l gui j jnlpj f i l echooser. j nl p
<?xml version="1.0
n
encoding=" UTF-8"? >
<jnl p s pec = "l.O+'!
codebase::"fi l e : C: /AAA-TIJ4/code/gui/jnlp"
href :: I! f ilechooser . j nlp 11 >
<i nformation>
<ti tl e>FileChooser demo application</t i t l e>
<vendor>Mindview Inc. </vendor>
<descr iption>
Jnl p File chooser Applicat i on
</ description >
<descr ipt i on ki nd= 11 s hor t 11 >
22 Interfaces grficas de usuario 909
I l us tra l a apertura, lectura y escri t ura de un archivo de text o
</descr iption>
<1con href=umindview.gif
ll
/ >
<offl ine-al lowed/>
</information>
<resources>
<j2se version:::
t1
1 .3 +
1I
href='' ht tp: // j ava.sun.com/ products/ a ut odl / j 2se
Ol
/ >
<j a r href=" j n l pfilechooser.j ar " download="eager" / >
</ resources >
<appl ication- desc
main- c lass=" gui.jnlp.JnlpFil eChooser
u
/ >
</jnlp>
111 ,-
Puede encontrar este archi vo de arranque en el cdigo fuente descargable del libro (disponible en www.MindView.net) guar-
dado como filechooser.jnlp sin la primera y la ltima lnea, en el mismo directorio que el archivo JAR. Como puede ver,
se trata de un archivo XML con un marcador <jnlp>. Contiene muy pocos sub-elementos, que se explican por s mismos.
El atributo spec del elemento jnlp dice al sistema cliente con qu versin de JNLP puede ejecutarse la aplicacin. El atri-
buto codebase apunta a la URL donde se encuentran el archivo de arranque y los recursos. En este caso, apunta a un direc-
torio de la mquina local, lo que constituye una buena forma de probar la aplicacin. Observe que no tendr que cambiar
esta ruta, ya que indica el directorio correcto en su mquina, para que el programa se cargue con xito. El atributo bref
debe especificar el nombre de este archivo.
El marcador information contiene varios sub-elementos que proporcionan informacin acerca de la aplicacin, que utiliza
la consola administrativa de Java Web Start o equivalente, la cual instala la aplicacin JNLP y permite al usuario ejecutar-
la desde la lnea de comandos, utilizando atajos, etc.
El marcador resources sirve a un propsito similar que le marcador de appiel en un archivo HTML. El sub-elemento j2se
especifica la versin de J2SE requerida para ejecutar la aplicacin, y el sub-elemento jar especifica el archivo JAR en el
que est archivada la clase. El elemento jar tiene un atributo download, que puede tener los valores "eager" o "Iazy" que
indican a la implementacin de JNLP si se necesita o no descargar el archivo completo antes de poder ej ecutar la aplica-
cin.
EL atributo application-desc indica a la implementacin JNLP qu clase es la clase ejecutable, o punto de entrada, al archi-
vo JAR.
Otro til sub-elemento del marcador jnlp es el marcador security, que no se ha mostrado en este ejemplo. He aqu, el aspec-
to de un marcador security:
<security>
<al l - permissions/ >
<secur i ty/>
Utilice el marcador sccurity cuando la aplicacin est implantada en un archivo JAR firmado. En el ejemplo anterior no era
necesario porque es posible acceder a todos Jos recursos locales a travs de los servicios JNLP.
Hay disponibles unos pocos ms marcadores, cuyos detalles puede consultarlos en la especificacin di sponible en
http://java.sun. comlproductsljavawebstartldownioad-spec.hImi.
Para ejecutar el programa, es necesaria una pgina de descarga que contenga un vnculo de hipertexto al archivo .jnlp. He
aqu un ejemplo (sin la primera y ltima lnea):
ji :! gui/jnlp/ f i lechooser . htrnl
<htrn!>
Follow the i nstruc tions in JnlpFil e Chooser.j ava t e
build jnlpfilechooser.jar, t he n:
<a href=" fi lec hooser.jnlpll>cl ick here</a>
910 Piensa en Java
Una vez que haya descargado la aplicacin, podr configurarla utilizando la consola de administracin. Si est empleando
Java Web Start sobre Windows, entonces se le solicitar un acceso directo a su aplicacin la segunda vez que lo use. Este
comportamiento es configurable.
Aqu slo se cubren dos de los servicios JNLP, aunque existen siete servicios en la versin actual. Cada uno de ellos est
di seado para realjzar una tarea especfica, como o cortar y pegar en el portapapeles, Puede encontrar ms infor-
macin en http://java.sun.com.
Concurrencia y Swing
Al programar con Swing se emplean hebras. Hemos visto esto al principio del captulo al estudiar que todo debera ser envia-
do a la hebra de despacho de sucesos de Swing a travs de SwingUtilities.invokeLater( j. Sin embargo, el hecho de que no
se haya creado explcitamente 1m objeto Thread sigaifica que los problemas del mecanismo de hebras puede aparecer por
sorpresa. Debemos recordar siempre que existe una hebra de despacho de sucesos en Swing, la cual est siempre all, ges-
tionando tdos los sucesos Swing extrayndolos uno a uno de la cola de sucesos y ejecutndolos. Recordar que la hebra de
despacho de suoesos est presente le ayudar a garantizar que la aplicacin no se vea sometida a interbloqueos o condicio-
neS de carrera.
En esta seccin se analizan las cuestiones relativas al mecanismo multihebra que pueden surgir a la hora de trabajar con
Swing.
Tareas de larga duracin
Uno de los errores ms fundamentales que podemos cometer al programar con una intenaz grfica de usuario consiste
en emplear accidentalmente la hebra de despacho de sucesos para ejecutar una tarea de larga duracin. He aqu un ej emplo
simple:
11: 911.i / LongRunningTask. java
II Un mal diseado.
mport javax.swing.*;
i mport j a,va. . * i
import java,awt.event.*;
import java.util.concurrent.*i
import static net. mindvi ew,util.SwingConsole.*;
public class LongRunningTask extends JFrame {
private JButton
bl = new JButton(UStart Long Running Task
n
) I
b2 = new JButton ("End Long Running Task") i
public LongRunningTask() {
bl .addActionListener(new ActionListener( )
pl,1blic void act- i QnPerfQrmed(ActionEvent evt)
}
} ) ;
try {
TimeUnit ,SECONDS.sleep (3) i
catch(InterrupteQExcption el {
System. out .println (IITask int.errupted 11 ) ;
r eturni
System.out .println (n Task completed");
b2.aqdActionListener(new ActionListener(}
public void. actionPerformed(ActionEvent evt)
1/ Interrumpirse a s mismo?
Thread. curre-ntThread (1 . interrupt () i
1
1 )
setLayout(new FlowLayout {));
addlbl)
addlb2)
public static void main (String [] args) {
run(new LongRunningTask(), 200, 150);
22 Interfaces grficas de usuario 911
Cuando se pulsa bi, la hebra de despacho de sucesos se ve de repente ocupada en realizar la tarea de larga duracin. Podr
ver que el botn ni siquiera vuelve a salir hacia afuera, porque la hebra de despacho de sucesos que se encarga nonnalmen-
te de repintar en la pantalla est ocupada. Y no podemos hacer ninguna otra cosa, como pulsar b2, porque el programa no
responder hasta que se haya completado la tarea de bl y la hebra de despacho de sucesos vuelva a estar disponible. El cdi-
go de b2 es un intento incorrecto del problema, interrumpiendo la hebra de despacho de sucesos.
Por supuesto, la respuesta consiste en ejecutar los procesos de larga duracin en hebras separadas. Aqu, se emplea el obje-
to ejecutor con la hebra Executor, que pone automticamente las tareas pendientes en cola y las ejecuta de una en una.
1/: gui /InterruptableLongRunningTask. java
ji Tareas de larga duracin dentro de hebras.
import javax.swing.*
import java.awt.*
impo rt java.awt.event.*
import j ava.util .concurrent.*;
import stat i c net.mindview.util.SwingConsole.*
cIass Task i mplement s Runnable {
prvate static int count er : O;
pr ivate f inal int id = counter++;
public void run() {
} ;
System. out. println(this + n star ted")
try {
TimeUnit . SECONDS . sleep (3 ) ;
catch (Interrupt edExcept i on e) (
System.out.pri nt ln(this T 11 i nterrupted
ll
)
return
System.out.println(this + !I complet ed
ll
)
public String toString () { return !lTask 11 + id; }
public l ong id I ) { return id }
public c lass InterruptableLongRunningTask extends JFrame {
private JButton
bl =- new JButton ( "Start Long Runni ng Task"),
b2 = new JButton ( "Bnd Long Running Task
ll
) ;
ExecutorService executor =
Execut ors.newSingleThreadExecuto r( ) ;
public InterruptableLongRunni ngTask() {
bl .addActionListener(new ActionListener( )
public voi d actionPerfo'rmed (Act i onEvent e )
Task task = new Task()
executor. execute( task) ;
}
})
System.out.println( task + 11 added to the queue");
b2.addActionListener(new Acti onLi s tener() {
912 Piensa en Java
publ ic void actionPerformed (Act ionEvent el {
executor.shutdownNow(); JI Solucin drstica
}
} )
setLayout(new FlowLayout {)) ;
addlbl)
addlb2)
public static void main (String (] args) {
run(new InterruptableLongRunningTask () , 200, 150);
Esta versin es mejor, pero cuando pulsamos b2, se llama a shutdownNow( ) sobre el objeto ExeclltorService, con lo que
se desactiva. Si intentamos aadir ms tareas, se genera una excepcin. Por tanto, pulsar b2 hace que el progTanla deje de
funcionar correctamente. Lo que nos gustara es poder lenninar la tarea actual (y cancelar las lareas pendientes) sin dete-
ner nada. El mecanismo de Callable/Futllre de Java SES descrito en el Captulo 21, Concurrencia, es justo lo que necesi-
tamos. Definiremos una nueva clase denominada TaskManager, que contienen tuplas que almacenan el objeto Callable
que representa la tarea y el objeto Future devuelto por el objeto C.ll.ble. La razn de que sea necesaria la tupla es que nos
pennite mantener el control de la tarea original, con lo cual podemos tener informacin adicional que no est disponible en
el objeto Future. He aqui cmo se haria:
1/: net /minctview/util/Taskltem.j ava
1/ Un objeto Future y el objeto Callable que l o produce.
package net.mindview.uti l
import java .ut il .concurrent.*;
public class Taskltem<R,C extends Callable<R {
public final Future<R> future
public final C task
public TaskItem(Future<R> future. e task) {
this . future f uture
this . task t ask
En la biblioteca java.lltil.collcurrent, la tarea no est disponible a travs del objeto Future de manera predeterminada, por-
que la tarea no tendra por qu seguir necesariamenle existiendo cuando oblengamos el resultado del objeto Future. Aqu,
obligamos a que la tarea siga existiendo por el procedimiento de almacenarla.
TaskManager ha sido incluida en net.mindview.util para que est disponible como utilidad de propsito general:
1/: net/mindview/uti l /TaskManager . java
1/ Ges t i n y ejecucin de una cola de tareas .
package net.mindview.util
import java .util .concurrent.*
i mport java.util.*
public c Iasa TaskManager <R,C extends Callable<R
ext ends ArrayList<TaskItem<R,C {
prvate ExecutorService exec
Executors.newSingleThr eadExecutor( )
public void add(C task) {
add(new TaskItem<R,C>{exec.submit(task),task);
pubIic Li s t <R> getResuIts () {
Iterator<TaskI tern<R,C items = iterator ();
Li s t <R> results = new ArrayList <R> ()
whi le {i tems . hasNext ( {
TaskItem<R,C> item = items.next( )
if (item. future. i sDone () {
try {
results.add(item.future.get()) i
catch (Exception el {
throw new RuntimeException(e);
items.remove() i
return resul ts i
public List<String> purge () {
Iterator<TaskItem<R,C items = iterator(} i
List<String> results = new ArrayList<String>() i
while(items.hasNext()) {
Taskltem<R,C> tem = items.next()
22 Interfaces grficas de usuario 913
ji Dejar las tareas completadas para insertar los resultados:
if(!item.future.isDone()) {
results.add("Cancelling !1 + item.task);
item.future.cancel(true) JI Puede interrumpir
items.remove() i
return results i
TaskManager es un contenedor ArrayList de elementos TaskItem. Tambin contiene un ejecutor monohebra Executor,
de modo que cuando invocamos add() con un objeto Callable, se ejecuta el objeto Callable y se almacena el objeto Future
resultante junto con la tarea original. De esta forma, si necesitamos hacer algo con la tarea, disponemos de una referencia a
la misma. Como ejemplo simple, en purge( ) se utiliza el mtodo toString( ) de la tarea.
Ahora podemos utilizar este mecanismo para gestionar las tareas de larga duracin de nuestro ejemplo:
JI: gui/lnterruptableLongRunningCallable.java
JI Utilizacin de de objetos Callable para tareas de larga duracin.
import javax.swing.*
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.*
import net.mindview.util.*;
import static net.mindview.util.SwingConsole.*;
cIass CallableTask extends Task
implements Callable<String>
publie Strng eaH () {
run () ;
return "Return value of 11 + this
public cIass
InterruptableLongRunningCallable extends JFrame {
private JButton
bl new JButton(UStart Long Running Task!l),
b2 "" new JButton("End Long Running Taskl!),
b3 "" new JButton(ITGet results
lT
) i
private TaskManager<String,CallableTask> manager
new TaskManager<String,CallableTask>() i
public InterruptableLongRunningCallable() {
bl.addActionListener(new ActionListener()
914 Piensa en Java
publi c voi d actionPerf ormed(ActionEvent el
Callabl e Task task n ew CallableTask(};
manager.add(task );
}
} ) ;
system.out .pri n t ln ( t ask + ., a dded to t he queue
n
) ;
b2.addActionListener(new ActionListener()
public void actionPerformed(ActionEvent el
for (String result : manager .purge(l)
System.out.print ln(result) ;
}
}) ;
b3.addActionListener (new Act ionLis tener{)
public void act ion Performed(ActionEvent el
}
} ) ;
II Ll amada de e jemplo a un mtodo de una tarea :
for( Ta skltem<String, CallableTask> tt :
manager )
tt.task.id(); JI No se requi ere proyeccin
for(St ring result : manager.ge tRe sul ts()
System.ou t .pri n tln(resul t) ;
setLayout(new FlowLayout{) i
add (bl ) ;
add(b2) ;
add(b3 ) ;
public static void mai n (String[) args ) {
run(new I nterruptabl eLongRunningCallable( ) , 200, 150)
}
/// ;-
Como podemos ver, CaUableTask hace exactamente lo mismo que Task excepto en que devuelve un resultado (en este caso,
un objeto String que identifica la tarea).
Se han creado utilidades no Swing (que no fonnan parte de la distribucin estndar de Java) denominadas SwingWorker
(disponibles en el sitio web de Sun) y Foxlrol (disponibles en http://foxtrol.sourceforge.net) para resolver un problema simi-
lar, pero en el momento de escribir estas lneas, dichas utilidades no han sido modificadas para aprovechar el mecanismo
CaUable/Future de Java SES.
A menudo, es importante proporcionar al usuario [mal algn tipo de indicacin visual de que una tarea se est ejecutando,
y de su progreso. Nonnalmente, esto se hace mediante una barra de progreso JProgressBar o un monitor de progreso
ProgressMonitor. Este ejemplo utiliza un monitor de progreso ProgressMonitor:
11 : guiJMoni toredLongRunni ngCallable .java
II Visualizacin de l progreso de la t area con Pr ogressMonitors.
import j avax . s wi ng.* ;
i mpo r t java. awt. *i
i mport java . awt . event .*;
import java.ut il.concurrent.*;
import net.mindv iew.util.*;
import s tat i c net.mindview.ut il.SwingConsol e.*
class Moni toredCal l able i mplements Callab le<Stri ng>
private s t ati c int counter = O;
pr vate fi na l i n t id counter++
private fina l Pr ogressMonitor moni tor;
prvate final static int MAX = 8;
public Monit oredCallable(progressMonitor monit or) {
this.mon i tor monitor
monitor . setNote(toString ()} i
};
monitor . setMaximum(MAX - 1);
monitor.set Mi l lisToPopup (SOO);
public Stri ng call () {
System.out.println{th is T 1I started
U
) i
try {
for(int i = O; i < MAX; i ++) {
TimeUni t .MILLISECONDS.sl eep (SOO) ;
if (monitor .isCanceled()}
Thread. curr entThread () .interrupt (} ;
f i nar int progr e s s = i;
Swi ngUtili ties . i nvokeLater(
) ;
new Runnab l e () {
}
public void run ( ) {
monitor.setProgress( progress) ;
cat ch (I nterruptedException e ) {
monitor.close ( ) ;
System.out.print l n(this + It interruptedll);
return "Res ul t: 11 + this + 11 interrupted" i
System. out .println(this + 11 campl eted");
return "Result: 11 + this + 11 compl eted";
publ i c String t oString () { return II Task 11 + id; }
publ ic c l ass MonitoredLongRunningCallable extends JFrame {
private JButton
bI new JButton ("Start Long Running Task
ll
),
b2 = new JBut ton ( 11 End Long Running Task 11) ,
b3 = new JBut ton("Get results
n
);
private TaskManager<String,MonitoredCal lable> manager
new TaskManager<String,MonitoredCallabl e> () ;
public Moni toredLongRunni ngCal l abl e() {
bl.addActionListener(new ActonLstener() {
public vad a ctionPerformed(Act ionEvent e)
MonitoredCallable task = new MonitaredCal lable(
new ProgressMonitor(
MonitoredLongRunningCallabl e .this,
"Long-RUlUling Task", "", O, O)
}
} ) ;
) ;
manager . add (task) i
Syst em.out.println(task + 11 added to the queue");
b2.addAct i onLi stener(new Act ionListener()
public void act ionPerformed(ActionEvent e)
for(String result : manager.purge (
System. out . print ln (resul t ) ;
}
} ) ;
b3 . addActionListener (new ActianListener () {
public void actionPerformed(ActionEvent e)
f or(String result : manager .getResults()}
System.out.println(result ) i
22 Interfaces grficas de usuario 915
916 Piensa en Java
}
});
setLayout(new FlowLayout(})
add(b1);
add(b2) ;
add(b3) ;
publ ic static void main(St ring [J args) {
run(new MonitoredLongRunningCallable{) I 200 , 500) j
}
///:-
El constructor MonitoredCallable toma un objeto ProgressMonitor como argumento, y su mtodo eall( ) actualiza ese
objeto cada medio segundo. Observe que un objeto MonitoredCalloble es una tarea separada y no debe, por tanto, intentar
controlar la interfaz de usuario directamente, asi que se utiliza SwingUtilities.invokeLater() para enviar los cambios en la
informacin de progreso al monitor. El tutoral de Swing elaborado por Sun (disponible en hup://java.sulI.com) muestra
una tcnica alternativa, consistente un utilizar el temporizado Tlmer de Swing, el cual comprueba el estado de la tarea y
actualiza el mortitor.
Si se pulsa el botn de "cancelacin" para el monitor, monltor.isCanceled( ) devolver true. Aqu, la tarea se limita a invo-
car interrupt( ) sobre su propia hebra, lo que ahora hace que entre en la clusula eateh donde se termina el monitor con el
mtodo elose( ).
El resto del cdigo es, en la prctica, igual que antes salvo por la creacin del objeto ProgressMonitor como parte del cons-
tructor MonitoredLongRunningCallable.
Ejercicio 33: (6) Modifique InterrnptableLongRunningCallable.java para que se ejecuten todas las tareas en parale-
lo en lugar de secuencialmente.
Hebras visuales
El siguiente ejemplo defme una clase JPanel de tipo Runnable que dibuja diferentes colores en el panel. Esta aplicacin
est preparada para tomar valores de la lnea de comandos con el fin de determinar el tamalo de la cuadrcula de colores y
cunto tiempo hay que dormir (con sleep( entre los sucesivos cambios de color. Jugando con estos valores, podemos des-
cubrir algunas caractersticas interesantes y posiblemente inexplicables en la implementacin del mecartismo multihebra en
nuestra plataforma:
JI ! guijColorBoxes .java
JI Demostracin visual del mecanismo multihebra.
/ / {Args: 12 5 0}
i mport javax.swing.*;
import java.awt .*i
import j ava .util.concurrent.*
import j ava . util.*
import static net.mindview. uti l .SwingConsole.*
class CBox extends JPanel i mpl ements Runnable
pr i vate int pause;
private stat i c Random rand = new Random()
private Col or color = new Color(O)
publ ic void paintComponent(Graphics g) {
g.setColor(col or)
Dimension s = getSize()
g . fillRect(O, O, s.width, s.height};
public C8ox(int pause) { this.pause = pause;
public void run( ) {
try {
whi l e(!Thread.interrupted() )
color = new Color( rand.nextlnt(OxFFFFFF i
repaint(); JI Solicitar asncronamente el redibujo
TimeUnit.MILLISECONDS.sleep(pause) i
catch(InterruptedException el
JI Forma aceptable de salir
public cIass ColorBoxes extends JFrame {
private int grid = 12;
private int pause = 50;
private static ExecutorService exec
Executors.newCachedThreadPool() i
public ColorBoxes () {
setLayout(new GridLayout(grid, grid));
for (int i = O i i < grid * grid i++) {
CBox eb = new CBox(pause) i
add (eb) ;
exec.execute(cb) i
public static void main(String[] args)
ColorBoxes boxes = new ColorBoxes();
if(args.length > O)
boxes.grid = new Integer(args[O])
if(args.length > 1)
boxes.pause = new Integer(args[l]) i
run(boxes, 500, 400);
22 Interfaces grficas de usuario 917
ColorBoxes configura un objeto GridLayout de tal manera que tenga una serie de celdas grid en cada dimensin. A con-
tinuacin aade el nmero apropiado de objetos CBox para rellenar la cuadrcula, pasando el valor pause a cada uno de esos
objetos. En maiu( l podemos ver que pause y grid tienen valores predeterminados que pueden modificarse proporcionan-
do los correspondientes argumentos a travs de la lnea de comandos.
Todo el trabajo se realiza en CBox. Esta clase hereda de JPanel e implementa la interfaz Ruunable, de tal forma que cada
panel JPanel tambin puede ser una tarea independiente. Estas tareas estn dirigidas por un conjunto de hebras
ExecutorService.
El color de la celda actual es cColor. Los colores se crean utilizando un constructor Color que admite nmeros de 24 bits,
que en este caso se crean aleatoriamente.
paintComponeut( l es bastante simple; slo asigna un color a cColor y rellena el JPanel completo con dicho color.
En run( l, podemos ver el bucle infinito que asigna un nuevo color aleatorio a cColor y luego invoca repaint( l para mostrar-
lo. A continuacin, la hebra pasa a dormir (con sleep( II durante el intervalo de tiempo especificado en la lnea de comandos.
La llamada a repaint( l en run( l merece una cierta atencin. A primera vista, pudiera parecer que estamos creando una gran
cantidad de hebras, cada una de las cuales est forzando a que se produzca una operacin de dibujo. Pudiera parecer que
esto viola el principio de que s6lo debemos enviar hebras a la cola de sucesos. Sin embargo, estas hebras no estn en reali-
dad modificando el recurso compartido. Cuando llaman a repaint( l , eso no fuerza un redibujo en dicho instante, sino que
simplemente fija un "indicador de modificacin" para especificar que la siguiente vez que la hebra de despacho de sucesos
est lista para dibujar las cosas, ese rea es un candidato para el redibujo. Por tanto, el programa no provoca ningn proble-
ma de gestin de hebras con Swing.
Cuando la hebra de despacho de sucesos realiza verdaderamente un redibujo con paint( l, llama primero a
paintComponent( l, luego a paintBorder( l y paintChlldren( l. Si necesitramos sustituir paint( l en un componente deri-
vado, tenemos que acordamos de llamar a la versin de la clase base de paint( ), de modo que se sigan realizando las accio-
nes apropiadas.
918 Piensa en Java
Precisamente porque este diseo es fl exible y el mecanismo de hebras est asociado con cada elemento JPanel , podemos
experimentar creando tantas hebras como queramos (en realidad, hay una restriccin impuesta por el nmero de hebras que
la mquina lVM sea capaz de gestionar).
Este programa tambin constituye una interesante prueba comparativa de rendimiento, ya que puede mostrar enormes dife-
rencias entre prestaciones y comportamiento entre una implementacin multihebra de la NM y otra, as como entre unas
plataformas y otras.
Ejercicio 34: (4) Modifiqne ColorBoxes.java de modo que comience distribuyendo puntos ("estrellas") por el lienzo,
y luego cambiando aleatoriamente el color de esas "estrellas".
Programacin visual y componentes JavaBean
Hasta ahora, hemos visto en el libro lo til que resulta el lenguaje lava para la creacin de fragmentos de cdigo reutiliza-
ble. La unidad de cdigo "ms reutilizable" ha sido la clase, ya que comprende una unidad cohesionada de caractersticas
(campos) y comportamientos (mtodos) que pueden reutilizarse bien directamente, mediante composicin, o bien median-
te herencia.
La herencia y el polimorfismo son partes esenciales de la programacin orientada a objetos, pero en la mayora de los casos,
a la hora de construir una aplicacin, lo que realmente queremos es componentes que hagan exactamente lo que necesita-
mos. Lo que nos gustara es colocar estos componentes en nuestro diseo como si fueran los circuitos integrados que un
ingeniero electrnico dispone sobre una placa de circuito impreso. Parece que debera haber alguna forma de acelerar este
estilo de programacin basado en la "construccin modular".
La "programacin visual" consigui su primer xito (no gran xito) con Visual BASIC (VE) de Microsoft, seguido de un
diseo de segunda generacin representado por Delphi de Borland (que fue la principal inspiracin para el diseo de
lavaBeans). Con estas herramientas de programacin, los componentes se representan visualmente, lo que tiene bastante
sentido, ya que normalmente mostrar algn tipo de componente visual como nn botn o nn campo de texto. De hecho, la
representacin visual coincide a menudo con la fonna exacta que el componente tendr cuando el programa se ejecuta. Por
tanto, parte del proceso de programacin visual implica arrastrar no componente desde una paleta y depositarlo sobre el for-
mulario. El entorno integrado de desarrollo (IDE, Integra/ed Development Environment) Application Builder escribe auto-
mticamente el cdigo a medida que realizamos estas operaciones y dicho cdigo har que se cree el componente dentro del
programa.
Nonnalmente, para completar el programa no es suficiente con colocar el componente sobre un formulario. A menudo, es
preciso cambiar las caracterfsticas de un componente, como por ejemplo el color, el texto que se muestra, la base de datos
a la que est conectado, etc. Las caractersticas que se pueden cambiar en tiempo de diseo se denominan propiedades.
Podemos manipular las propiedades de nuestro componente dentro del entorno IDE y, cuando se crea el programa, estos
datos de configuracin se guardan para poder ser recuperados cuando el programa se ejecute.
El lector debera tener clara a estas alturas la idea de que nn objeto es ms que nna serie de caractersticas: tambin es un
conjunto de comportamientos. En tiempo de diseo, los comportamientos de un componente visual estn parciahnente
representados por sucesos, cada uno de los cuales constituye una declaracin del tipo: "He aqu algo que puedes hacer a este
componente". Normalmente, somos nosotros los que decidimos qu es lo que querernos que ocurra, asociando a ese suce-
so un cierto cdigo.
La parte crtica es la siguiente: el entorno IDE utiliza el mecanismo de reflexin para interrogar dinmicamente al compo-
nente y averiguar qu propiedades de sucesos soporta. Una vez que sabe cules son, puede mostrar las propiedades y per-
mitirnos modificarlas (guardando el estado cuando construyamos el programa), y tambin muestra los sucesos. En general,
lo que nosotros hacemos es no doble clic sobre un suceso y el entorno !DE crea un cuerpo de cdigo y lo asocia con el
suceso particular. Lo nico que hace falta en dicho pnnto es escribir el cdigo que tenga que ejecutarse cuando ocurra el
suceso.
Todo esto significa que el entorno IDE hace noa gran cantidad de trabajo por nosotros. Como resultado, podemos centrar-
nos en el aspecto del programa y en lo que se supone que el programa debe hacer, delegando en !DE la gestin de todos los
detalles de conex.in. La razn de que las herramientas de programacin visual hayan tenido tanto xito es que permiten ace-
lerar enortnemente el proceso de construccin de una aplicacin, por supuesto, la interfaz de usuario, pero a menudo tam-
bin se acelera la construccin de otras partes de la aplicacin.
22 Interfaces grficas de usuario 919
Qu es un componente JavaBean?
Un componente es. en definitiva, simplemente un bloque de cdigo que normalmente est encerrado dentro de una clase.
La cuestin clave es la capacidad del !DE para descubrir las propiedades y sucesos de cada componente. Para crear un com-
ponente VB, el programador tena originalmente que escribir un fragmento bastante complicado de cdigo, que tena que
respetar ciertos convenios para exponer las propiedades y sucesos (que se hizo ms fcil a medida que transcurrieron los
aos). Delphi era una herramienta de programacin visual de segunda generacin, y el lenguaje estaba diseilado especfica-
mente centrndose en la programacin visual, con lo que era mucho ms fcil crear un componente visual. Sin embargo,
Java ha llevado la creacin de componentes visuales a su estado ms avanzado con JavaBeans, ya que un componente Bean
es simplemente una clase. No hace falta escribir ningn cdigo adicional ni utilizar extensiones del lenguaje especiales para
poder transfonnar algo en un componente Bean. Lo nico necesario es, de hecho, modificar ligeramente la forma de deno-
minar los mtodos. Es el nombre del mtodo lo que le dice al entorno DE si se trata de una propiedad, de un suceso o de
un mtodo nanual.
En la documentacin del JDK, este convenio de denominacin se llama, de manera confusa, "patrn de diseo". Resulta
bastante desafortunado que esto sea as, ya que los patrones de diseo (consulte Thinking in Patterns en www.MindView.net)
ya son lo suficientemente complejos sin necesidad de que se introduzca confusin adicional. El convenio de denominacin
no es un patrn de diseilo, y resulta bastante sencillo:
1. Para una propiedad denominada xxx, normalmente creamos dos mtodos: getXxx( ) y setXxx(). La primera letra
despus de "get" o "ser' ser puesta automticamente en minscula por las herramientas que examinen los mto-
dos, con el fin de generar el nombre de la propiedad. El tipo producido por el mlodo "get" coincide con el tipo
del argumento del mtodo "sel". El nombre de la propiedad y el tipo de los mtodos "get" y "set" no estn rela-
cionados.
2. Para una propiedad de tipo boolean, podemos emplear la tcnica anterior basada en "get" y "set", pero tambin
podemos usar "is" en lugar de "gel. "
3. Los mtodos nonnales del componente Bean no se adaptan al convenio de denominacin anterior, pero son de
tipo publico
4. Para los sucesos, se utiliza la solucin Swing basada en escuchas. Es exactamente el mismo convenio que hemos
visto anterionnente: addBounccLstener(BonnceListener) y removeBounceLlstener(BounceListener) para
gestionar un suceso BounceEvent. La mayor parte de las veces, los sucesos y escuchas predefmidos satisfarn
completamente nuestras necesidades, pero tambin podemos crear nuestros propios sucesos e interfaces escucha.
Para crear un componente Bean simple, podemos emplear estas directrices:
// : frogbean/Frog. java
// Un componente JavaBean trivial.
package f rogbean
import java.awt .*
import java.awt.event.*;
c lass Spo t s {}
public c lass Frog
private int jumps;
priva te, Color color
prvate Spots spots;
privat e b oolean jmpr
public int getJumps () { return jumps;
public void setJumps(in t newJumps) {
jumps = newJumps
public Color getColor () { ret urn color
public v o id setColor(Color newColor) {
color = newColor
public Spot s getSpots () { return spotSi }
920 Piensa en Java
pub1ic void setSpots(Spots newSpots) {
spots newSpots
pub1ic boolean isJumper() { return jmpr }
public void setJumper(boolean j) { jmpr = j
public void addActionListener(ActionListener 1)
/ / ...
public void removeActionListener(ActionListener 1) {
/ / ...
public void addKeyListener(KeyListener 1) {
/ / ...
public void removeKeyListener(KeyListener 1) {
/ / ...
/ / Un mtodo pblico "normal 11 :
public void croak() {
System.out.println(!lRibbet! 11)
En primer lugar. podemos ver que se trata simplemente de una clase. Normalmente, todos los campos sern privados y slo
se podr acceder a ellos a travs de sus mtodos y propiedades. Siguiendo el convenio de denominacin, las propiedades
son jumps, color, spots y jumper (observe el cambio de maysculas a minsculas en la primera letra del nombre de la pro-
piedad). Aunque el nombre del identificador interno es el mismo que el nombre de la propiedad en los tres primeros casos,
en jumper podemos ver que el nombre de la propiedad no nos obliga a utilizar ningn identificador concreto para las varia-
bles internas (ni tampoco nos obliga, de hecho, ni siquiera a tener ninguna variable interna para dicha propiedad).
Los sucesos gestionados por este componente Bean son ActionEvent y KeyEvent, basados en la denominacin de los mto-
dos "add" "remove" para el escucha asociado. Finalmente, podemos ver que el mtodo normal croak( ) sigue siendo parte
de la Bean simplemente porque se trata de un mtodo pblico, no porque se adapte a ningn esquema de denominacin.
Extraccin de la informacin Beanlnfo con Introspector
Una de las partes ms crticas del esquema JavaBean tiene lugar cuando arrastramos una Bean de una paleta y la colocamos
en un formulario. El entorno IDE debe ser capaz de crear la Bean (lo cual puede hacerse si existe un constructor predeter-
minado) y luego, sin tener ningo acceso al cdigo fuente de la Bean, extraer toda la informacin necesaria para crear la
hoja de propiedades y las rutinas de tratamiento de sucesos.
Parte de la solucin resulta evidente a partir de lo comentado en el Captulo 14, Informacin de tipos: el mecanismo de refle-
xin de Java permite descubrir todos los mtodos de una clase desconocida. Esto resulta perfecto para resolver el problema
del diseo JavaBean sin requerir palabras clave del lenguaje como las que existen en otros lenguajes de programacin visual.
De hecho, una de las principales razones de que se aadiera el mecanismo de reflexin de Java fue para soportar JavaBeans
(aunque el mecanismo de reflexin tambin soporta la serializacin de objetos y la invocacin remota de mtodos, RMI,
que resulta til para la programacin normal). Por tanto, podriamos esperar que el creador del entorno IDE usara el meca-
nismo de reflexin con cada Bean y analizara sus mtodos para encontrar las propiedades y sucesos correspondientes.
Ciertamente, esto resulta posible, pero los diseadores de Java queran proporcionar una herramienta estndar, no slo para
hacer que los componentes Bean sean simples de usar, sino tambin para proporcionar una pasarela estndar para la crea-
cin de otros componentes Bean ms complejos. Esta herramienta es la clase Introspector, y el mtodo ms importante de
esta clase es el mtodo esttico getBeanInfo( ). Basta con pasar una referencia Class a este mtodo y el mtodo se encar-
gar de interrogar a fondo a dicha clase y de devolver un objeto BeanInfo que se puede diseccionar para determinar las pro-
piedades, los mtodos y los sucesos.
Normalmente, nosotros no tenemos que preguntarnos nada acerca de esto; 10 ms probable es que obtengamos la mayora
de nuestros componentes Bean ya diseados, y no tenemos por qu conocer todos los detalles subyacentes. Basta con arras-
trar los componentes sobre el formulario, configurar sus propiedades y escribir las rutinas de tratamiento para los sucesos
22 Interfaces grficas de usuario 921
de inters. Sin embargo, la utilizacin de Introspector para mostrar infonnacin acerca de una Bean constituye un ejerci-
cio educativo excelente. He aqu una herramienta que hace precisamente esto:
1/: guijBeanDumper.java
JI Obtencin de la informacin acerca de una Bean.
import javax.swing.*
import java.awt.*i
import java.awt.event.*i
import java.beans.*
import java.lang.reflect.*;
import static net.mindview.util.SwingConsole.*
public class BeanDumper extends JFrame {
private JTextField query = new JTextField(20) i
prvate JTextArea results = new JTextArea();
public void print(String s) { results.append(s + "\nll) i
public void dump{Class<?> bean) {
resul ts. setText (" ") i
Beanlnfo bi = null;
try {
bi = Introspector.getBeanlnfo(bean, Object.class);
catch (IntrospectionException el {
print (JI Couldn! t introspect 11 + bean. getName () ) ;
return;
for(PropertyDescriptor d: bi.getPropertyDescriptors()) {
Class<?> p = d.getPropertyType();
if(p == null) continue;
print (JI Property type: \n u +p. getName () +
JI Property name: \n 11 + d. getName () ) ;
Method readMethod = d.getReadMethod();
if(readMethod != null)
print(JlRead method:\n u + readMethod);
Method writeMethod = d.getWriteMethod();
if(writeMethod 1= null)
print(JlWrite method:\n u + writeMethod);
print("====================") ;
print(UPublic methods:");
for(MethodDescriptor m : bi.getMethodDescriptors())
print(m.getMethod() .toString()) i
print('!======================") i
print ("Event support:")
for(EventSetDescriptor e: bi.getEventSetDescriptors()) {
print (lIListener type: \n n +
e.getListenerType() .getName());
for(Method 1m : e.getListenerMethods())
print(UListener method:\n !1 + lm.getName()) i
for(MethodDescriptor lmd :
e.getListenerMethodDescriptors()
print("Method descriptor:\n n + lmd.getMethod());
Method addListener= e.getAddListenerMethod()
print(JlAdd Listener Method:\n u + addListener)
Method removeListener = e.getRemoveListenerMethod()
print (JlRemove Listener Method: \n U+ removeListener)
print("====================,!)
class Dumper implements ActionListener {
public void actionPerformed(ActionEvent el
922 Piensa en Java
String name = query . getText ();
Class<?> e = null
t ry {
e = Cl ass . forName(name)
catch(Clas sNotFoundException ex)
results. setText ( "Couldn' t find 11 + name ) ;
return
dump (c) ;
public BeanDumper() {
Jpanel p = new Jpanel()
p.set Layou t(new FlowLayout(;
p.add (new bean name:" ;
p .add(query) ;
add (Border Layout . NORTH, p);
add(new JScrol lPane (results;
Dumper dmpr = new Dumper( ) ;
query. addActionLi s tener (dmpr) ;
query.setText(lIfrogbean . Frogl! } ;
JI Forzar la evaluac i n
dmpr,action?erformed(new ActionEvent(dmpr , O, 1111 ) i
pUblic static void main(String[] args)
run(new BeanDumper(}, 600, 500) ;
)
!!! ; -
BeanDumper.dump( ) se encarga de realizar todo el trabajo. Primero trata de crear un objeto BeanInfo, y si tiene xito,
invoca los mtodos de BeanInfo que generan la infonnacin acerca de las propiedades, mtodos y sucesos. En
Introspeetor.getBeanlnfo(), podemos ver que hay un segundo argumento que le dice a Introspeetor dnde detenerse den-
tro de la jerarqua de berenci,,- En este ejemplo, se detiene antes de analizar todos los mtodos de Object, ya que no esta-
mos interesados en ellos.
Para las propiedades, getPropertyDescriptors( ) devuelve una matriz de objetos PropertyDescriptor. Para cada objeto
PropertyDescrlptor, podemos invocar getPropertyType( ) para encontrar la clase del objeto que se pasa a los mtodos de
propiedad o que estos mtodos devuelven. A continuacin, para cada propiedad, podemos obtener su seudnimo (extrado
de los nombres de los mtodos) con getName(), el mtodo de lectura con getReadMetbod() y el mtodo de escritura con
getWriteMethod( ). Estos dos ltimos mtodos devuelven un objetio Method que puede de hecho utilizarse para invocar
el mtodo correspondiente sobre el objeto (esto es parte del mecanismo de reflexin).
Para los mtodos pblicos (incluyendo los mtodos de propiedad), getMetbodDescriptors() devuelve una matriz de obje-
tos MethodDescriptors. Para cada uno, podemos obtener el objeto Method asociado e imprimir su nombre.
Para los sucesos, getEventSetDescriptors( ) devuelve una matriz de objetos EventSetDescrlptor. Cada uno de estos obje-
tos puede consultarse para averiguar la clase a la que pertenece el escucha, los mtodos de dicho escucha y los mtodos para
agregar y eliminar escuchas. El programa BeanDumper visualiza toda esta infonnacin.
En el arranque, el programa fuerza la evaluacin de frogbcan.Frog. La salida, despus de eliminar los detalles innecesa-
rios, es:
Property type :
Col or
Propert y name:
color
Read method:
publ ic Col or getColor()
Wr ite met hod:
public void set Col or(Col or )

Property type:
bool ean
Property name:
j umper
Read method:
publi c boolean isJumper()
Wr i te method:
publ ic void setJumper (boolean)
Property type:
i n t
Property name:
jumps
Read method:
public int getJump s ()
Wr i te method:
publ ic void setJumps(int)
Property type:
frogbean.Spots
Prope rty name:
s p ots
Read method :
publ ic frogbean . Spots get Spots()
Wr i t e method:
publ ic voi d setSpots(frogbean.Spotsl
Public methods:
publ ic void setSpots(frogbean . Spots)
public void set Col or(Col or )
publ ic voi d setJumps(int l
public boolean isJumper()
pub l ic frogbean . Spots getSpots()
public void c roak (l
publ ic void addActionLi stener(ActionListener)
public void addKeyListener(KeyListener)
publ i c Color getColor()
public void setJumper(bool ean)
publi c i nt getJumps()
publ ic void removeAc t ionLi stener(ActionLis tener)
public void removeKeyListener(KeyListener)
Event support:
Lis tener type:
KeyListener
Listener met hod :
keyPressed
List ener method:
keyRel eased
List ene r method:
keyTyped
Method descriptor:
publ ic abstract void keyPressed(KeyEvent )
Method descriptor :
publ i c abs tract void keyReleased (KeyEvent )
Method descri ptor:
public abstract void keyTyped(KeyEvent)
Add Listener Method:
publ ic void addKeyListener(KeyListener}
22 Interfaces grficas de usuario 923
924 Piensa en Java
Remove Listen er Method:
public void removeKeyListener(KeyListener )
~ ~ ~ ~
Listener t ype:
ActionListener
Listener method:
actionPerformed
Method descriptor:
public abstract void actionPerformed(ActionEvent)
Add Listener Method:
public void addActionListener (Act ionListener )
Remove Lis t ener Method:
public void removeActionListener(ActionListener)
Esta salida nos revela la mayor parte de la informacin que Introspector ve a medida que genera un objeto BeanInfo a par-
tir de la Bean. Podemos ver que el tipo de la propiedad y su nombre son independientes. Observe el uso de minscula en el
nombre de la propiedad (la nica vez que esto no sucede cuando el nombre de la propiedad comienza con ms de dos letras
maysculas seguidas). Y recuerde que los Dombres de mtodos que podemos ver aqu (como por ejemplo los de lectura y
escritura) SOD producidos por UD objeto Method que puede emplearse para invocar el mtodo asociado sobre el objeto.
La lista de los mtodos pblicos incluye los mtodos que no estn asociados con una propiedad o un suceso, como por ejem-
plo croak( ), asi como los que s estn asociados. Se trata de todos los mtodos que se pueden invocar mediante programa
para una Bean, y el entorno IDE puede facilitamos la tarea de programacin enumerando todos esos mtodos mientras rea-
lizarnos llamadas a mtodos.
Por ltimo, podemos ver que los sucesos se analizan completamente, extrayendo la informacin acerca del escucha, de sus
mtodos y de los mtodos para agregar y eliminar escuchas. Bsicamente, una vez que disponemos del objeto Beanlnfo,
podemos determinar toda la informacin importante acerca de la Bean. Tambin podemos invocar los mtodos para dicha
Bean, a pesar de no disponer de ninguna otra informacin, salvo el propio objeto (de nuevo, sta es una funcionalidad ofre-
cida por el mecanismo de reflexin).
Una Bean ms sofisticada
El ejemplo siguieute es ligeramente ms sofisticado aunque un poco frvolo. Se trata de un control JPanel que dibuja un
crculo alrededor del ratn cada vez que ste se mueve. Cuando apretamos el botn del ratn, aparece la palabra "Bang!"
en mitad de la pantalla y se dispara un escucha de accin.
Las propiedades que podemos modificar son el tamao del crculo y el color, el tamao y el texto de la palabra que se mues-
tra cuando se pulsa el botn del ratn. El componente BangBean tambin tiene sus propios mtodos addActionListener( )
y removeActionLlst ener( j, de modo que podemos asociar nuestro propio escucha que se disparar cuando el usuario haga
clic sobre el compoDente BangBean. Resulta sencillo identificar eD el ejemplo el soporte y propiedades del suceso:
JI : bangbean/BangBean.java
JI Una Bean grfica.
package bangbean;
import javax.swing .*
import java.awt.*
import j ava.awt.event.*i
import java.io.*
import java.util.*;
public class
BangBean extends JPanel implements Serial izable {
private i nt xm, ym
private int cSize = 20; /1 Tamao del c i rculo
private String text = lIBangl
ll

private int fontSize = 48;
private Color teolor = Col or.RED;
private ActionListener actionListener
public BangBean() (
a ddMouseLi stener (new ML () ;
a ddMouseMotionListener (new MML();
}
public int getCircl e Si z e () { return cSize;
public vaid setCircleSize(int newSize) {
cSize = newSize
publ ic String getBangText () { return text;
public void setBangText(String newText) {
text = newText
public i n t getFon t Size () { ret urn fontSize;
public void setFontSize(int newSize) {
fontSize = newSize;
public Color getTextColor() { return tColor ;
public void setTextColor(Color newColor ) {
teoI ar = newCol or ;
publ ic voi d paintComponent (Graphics g ) {
super.paintComponent (g) ;
g. setColor (Color.BLACK) ;
g.drawOval(xm - cSize/2, ym - cSize/2, cSize, cSize) j
1/ Se trata de un escucha unidifusin, que es l a
1/ forma ms s i mple de gestionar los escuchas :
publ ic void addActionLi stener(ActionLi stener 1)
t hrows TooManyLi s tenersException {
if(actionListener ! = null )
throw new TooManyListenersException();
actionListener = 1;
pub1ic void removeActionListener(ActionListener 1 )
actionListener = null;
class ML extends MouseAdapter
public void mousePressed (MouseEve nt el {
Graphics 9 = getGraphi cs ()
g.setColor(tColor)
g.setFont(
new Font( "TimesRoman", Font.BOLD, f ontSize
i nt widt h = g. getFontMetrics{) .stringWidth{text)
g.drawString(text , (getSizeO . widt h - width) /2 ,
getSize () . height/2) ;
g . d ispose () ;
II I nvocar el mtodo de l escucha :
if(actionListener != nul l)
actionListener.actionPerformed(
new ActionEvent(BangBean.this,
Act i onEvent .ACTION_PERFORMED, null i
class MML extends MouseMotionAdapter {
public void mouseMoved(MouseEvent e)
xm = e . getX () ;
ym = e.gety() ;
repaint () i
22 Interfaces grficas de usuario 925
926 Piensa en Java
public Dimension getPreferredSize()
return new Dimension(200, 200);
Lo primero que podemos observar es que BangBean implementa la interfaz Serializable. Esto quiere decir que el entorno
lDE puede extraer toda la informacin de BangBean utilizando seriali zacin despus de que el diseador haya ajustado los
valores de las propiedades. Cuando se crea la Bean como parte de la aplicacin que se est ej ecutando, estas propiedades
extradas se restauran de modo que podamos obtener exactamente 10 que deseamos.
Si examinarnos la signatura de addActionListener(), vemos que puede generar una excepcin TooMany-
ListenersException. Esto indica que se trata de un escucha de unidifilSin, lo que quiere decir que enva una notificacin a
un nico escucha en el momento de producirse el suceso. Normalmente, lo que usamos son sucesos de multidifusin, de
modo que muchos escuchas puedan ser notificados por un suceso. Sin embargo, eso nos hara tropezar con cuestiones de
programacin multihehra, por lo que volveremos sobre el tema en la siguiente seccin, "Sincronizacin en JavaBeans".
Mientras tanto, un suceso de unidifusin nos permite resolver momentneamente el problema.
Cuando hacemos clic con el ratn, el texto aparece en la parte central del componente BangBean, y si el campo
actionListener es distinto de null, se invoca su mtodos actionPerforrned( ) creando un nuevo obj eto ActionEvent en el
proceso. Cada vez que se mueve el ratn se capturan sus nuevas coordenadas y se vuelve a dibujar el lienzo (borrando el
texto que hubiera en el lienzo, como se ver al ejecutar el programa).
He aqu la clase BangBeanTest para probar la Bean:
11: bangbean/BangBeanTest .java
II {Tmeout: s} Abortar despu s de 5 segundos duran te las pruebas
package bangbean
import javax.swng.*i
i mport java . awt . *;
import java.awt.event.*;
i mport java.util . *
import static net.mindview.uti l.SwingConsole.*
public class BangBeanTest extends JFrame {
private JTextField txt = new JTextField(20)i
II Durante las pruebas, infor mar de las acciones:
class BBL i mp l ements Ac tonListener {
private int count = Di
publ ic void actionPerf ormed(ActionEven t e ) {
txt.set Text {IIBangBean act ion 11+ count++ );
public BangBeanTest()
BangBean bb = new BangBean( )
try (
bb.addActionListener(new BBL())
catch{TooManyListenersException e)
t xt. setText {"Too many listeners!l } ;
add (bb) ;
add(Border Layout .SOUTH, txt} i
public static void main (String{] args)
run( new BangBeanTest(}, 4 00, 500) i
Cuando se emplea la Bean en un entorno IDE, esta clase no se usar, pero es til proporcionar un mtodo de prueba rpido
para cada Bean que diseemos. BangBeanTest coloca un componente BangBean dentro del marco JFrame, asociando un
escucha ActionListener simple con el componente BangBean con el fm de imprimir un recuento de sucesos en el campo
22 Interfaces grficas de usuario 927
JTextField cada vez que se produzca un suceso ActionEvent. Por supuesto, normalmente el IDE creara la mayor parte del
cdigo que utilice la Bean.
Cuando ejecute el componente BangBean a travs de BeanDumper o lo incluya dentro de un entorno de desarrollo prepa-
rado para componentes Bean, observar que hay muchas ms propiedades y acciones de las que el cdigo precedente per-
mite incluir. Eso se debe a que 'BangBean hereda de JPanel, y JPanel tambin es un componente Bean, as que se mostrarn
tambin sus propiedades y sucesos.
Ejercicio 35: (6) Localice y descargue uno o ms de los entornos gratuitos para el desarrollo de interfaces GUI dispo-
nibles en Iuternet, o utilice algn producto comercial que posea. Descubra qu es lo que hace falta para
aadir un BangBean a ese entorno y adalo.
Sincronizacin en JavaBeans
Siempre que creemos una Bean, deberemos asumir que sta ser ejecutada dentro de un entorno multihebra. Esto quiere
decir que:
1. Siempre que sea posible, todos los mtodos pblicos de una Bean deben ser sincronizados. Por supuesto, esto
implica que tendremos que pagar el coste de la sincronizacin en tiempo de ejecucin (que se ha reducido signi-
ficativamente en las versiones recientes del JDK). Si esto es un problema, podemos dejar sin sincronizar los mto-
dos que no vayan a provocar problemas en las secciones crticas, pero recuerde que dichos mtodos no resultan
siempre obvios. Los mtodos que podran caer dentro de esta categora tienden a ser pequeos (tal como
getCirc\eSize() en el ejemplo signiente) y/o "atmicos"; es decir, la llamada al mtodo se ejecuta utilizando una
cantidad de cdigo tan pequea que el objeto no puede modificarse durante la ejecucin (pero recuerde del
Captulo 21, Concurrencia, que aquello que pensemos que es atmico puede en realidad no serlo). Dejar sin sin-
cronizar dichos mtodos puede no tener un efecto significativo sobre la velocidad de ejecucin del programa. Lo
mejor es que todos los mtodos pblicos de la Bean sean sincronizados y eliminar la palabra clave synchronized
de un mtodo nicamente cuando estemos completamente seguros de que eso va a aumentar la velocidad y pode-
mos eliminar la palabra con segmidad.
2. Cuando se dispara un suceso multidifusin para notificar a un conjunto de escuchas interesados en dicho suceso,
debemos asumir que se pueden aadir o eliminar escuchas mientras estemos recorriendo la lista.
El primer punto es bastante sencillo de entender, pero el segundo requiere algo ms de reflexin. En BangBean.java evita-
mos los problemas de concurrencia ignorando la palabra clave synchronized y haciendo que el suceso fuera de unidifusin.
He aqu una versin modificada que trabaja en un entorno multihebra y utiliza el mecanismo de multidifusin para los suce-
sos:
IJ: gui/BangBean2.java
IJ Deera escribir sus componentes Beans de esta forma para
IJ poder ejecutarlos en un entorno multihebra.
import javax.swing.*
import java.awt.*
import java.awt.event.*
import java.io.*
import java.util.*
import static net.mindview.util.SwingConsole.*
public class BangBean2 extends JPanel
implements Serializable {
private int xm, ym
private int cSize 20 JI Tamao del crculo
pri vate String text "Bang!!l
private int fontSize 48
private Color teolor = Color.RED
private ArrayList<ActionListener> actionListeners
new ArrayList<ActionListener>()
public BangBean2() {
addMouseListener(new ML())
928 Piensa en Java
addMouseMotionListener(new MM());
public synchronized int getCircleSize () { return cSize
public synchronized void setCircleSize(int newSize) {
cSize = newSize
public synchronized String getBangText() { return text
public synchronized void setBangText(String newText) {
text = newText;
public synchronized int getFontSize{) { return fontSize;
public synchronized void setFontSize(int newSize) {
fontSize = newSize
public synchronized Color getTextColor() { return teoIar;}
public synchronized void setTextColor(Color newColor) {
teoIar = newColor
public void paintComponent(Graphics g)
super.paintComponent(g) j
g.setColor(Color.BLACK) i
g.drawOval(xm - cSize/2, ym - cSize/2, cSize, cSize);
II ste es un escucha de multidifusin, que se usa ms
II a menudo que la solucin unidifusin empleada en BangBean.java:
public synchronized void
addActionListener(ActionListener 1)
actionListeners.add(l)
public synchronized void
removeActionListener(ActionListener 1)
actionListeners.remove(l) i
II Observe que esto no est sincronizado:
public void notifyListeners() {
ActionEvent a = new ActionEvent(BangBean2.this,
ActionEvent.ACTION_PERFORMED, null) i
ArrayList<ActionListener> Iv = null
II Hacer una copia somera de la lista por si alguien
II aade un escucha mientras estamos
II invocando los escuchas:
synchronized(this) {
Iv = new ArrayList<ActionListener> (actionListeners)
II Invocar todos los mtodos escucha:
for(ActionListener al : Iv)
al. actionPerformed (a) ;
class ML extends MouseAdapter
public void mousePressed(MouseEvent e)
Graphics 9 = getGraphics() i
g.setColor(tColor) ;
g.setFont(
new Font ("TimesRoman", Font .BOLD, fontSize)) j
int width = g.getFontMetrics() .stringWidth(text)
g.drawString(text, (getSize() .width - width) 12,
getSize () .height/2)
g.dispose() ;
notifyListeners() ;
class MM extends MouseMotionAdapter
public void mouseMoved(Mou seEvent e)
xm = e.getX()
ym = e.gety();
repaint() i
public static void main (S t ring [J args) {
BangBean2 bb2 = new BangBean2();
bb2. addActionListener(new Act ionListener()
public void actionPerformed(ActionEvent el
System.ou t.println("ActionEvent " + e l i
}
} ) ;
bb2. addActionListener (new ActionLi stener () {
public void actionPerformed (ActionEvent e )
System. out. println (IIBangBean2 action rr) ;
}
}) ;
bb2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e l
System. out .println ("More actionO!) j
}
} ) ;
JFrame frame = new JFrame()
trame. add (bb2 ) ;
run(frame, 300, 300) i
}
/// ,-
22 Interfaces grficas de usuario 929
Aftadir la palabra clave synchronized a los mtodos es un cambio sencillo. Sin embargo, observe en addActionListener( )
y removeActionListener( ) que los escuchas ActionListener se aaden y eliminan ahora en un contenedor ArrayList, por
lo que podemos tener tantos como queramos.
Podemos ver que el mtodo notifyListeners( ) no est sincronizado. Este mtodo puede ser invocado desde ms de una
hebra a la vez. Tambin resulta posible invocar addActiollListener( ) o removeActionListener( ) en mitad de una llama-
da a notifyListeners( ), lo que constituye un problema, porque este ltimo mtodo recorre el contenedor actionListeners
de tipo ArrayList. Para alivi ar el problema, el contenedor ArrayList se clona dentro de una clusula synchronized, y lo
que se hace es recorrer el clan (consulte los suplementos (en ingls) en lnea de este libro para conocer ms detalles sobre
la clonacin). De esta forma, podemos manipular el contenedor ArrayList original sin que ello suponga ningn impacto
sobre notifyListeners().
El mtodo paintComponent() tampoco est sincronizado. Decidir si sincronizar los mtodos sustituidos no resulta tan claro
como cuando estamos aadiendo nuestros propios mtodos. En este ejemplo, resulta que paintComponent() parece fun-
cionar correctamente independientemente de si se sincroniza o no. Sin embargo, las cuestiones que hay que tener en cuen-
ta son las siguientes:
1. Modifica el mtodo del estado de las variables "crticas" dentro del objeto? Para descubrir si las varables son
"crticas", hay que determinar si esas variables sern ledas o escritas por otras hebras del programa (en este caso,
la lectura o escritura se hace casi siempre a travs de mtodos sincronizados. por lo que nos podemos limitar a
examinar esos mtodos). En el caso de paintComponent( ), no se realiza ninguna modificacin.
2. Depende el mtodo del estado de estas variable "crticas"? Si un mtodo sincronizado modifica una variable que
nuestro mtodo utilice, entonces conviene hacer que nuestro mtodo tambin est sincronizado. Basndonos en
esto, podemos observar que cSize es modificado por mtodos sincronizados y, por tanto, paintComponent( )
debera estar sincronizado. Sin embargo, en este caso, podemos preguntamos: "Qu es lo peor que puede suce-
der si se cambia eSize durante la ejecucin de paintComponent( )?" Si la respuesta a esta pregunta es que no
puede suceder nada catastrfico, y que no sucede ms que un efecto transitorio, debemos decidir dejar sin sincro-
nizar paintCornponent( ) para evitar el coste adicional asociado a la llamada al mtodo sincronizado.
930 Piensa en Java
3. Una tercera clave consiste en analizar si la versin de la clase base de paintComponent( ) est sincronizada, lo
que no es as. Este elemento no tiene mucho peso, pero s que nos proporciona una pista. En este caso, por ejem-
plo, un campo que s que se cambia mediante mtodos sincronizados (eSize) se ha mezclado en la frmula de
paintComponent( ) y podria haber modificado nuestras conclusiones. Sin embargo, observe que el carcter de
sincronizado no se hereda, es decir, si un mtodo est sincronizado en la clase base, no se sincroniza automtica-
mente en la versin sustituida de la clase derivada.
4. paint() y paintComponent( ) son mtodos que deben ser lo ms rpidos posible. Cualquier cosa que permita
reducir el coste de procesamiento de estos mtodos resultar altamente recomendable, por lo que si cree que nece-
sita sincronizar estos mtodos, eso ser un indicador de que el diseo no es demasiado bueno.
El cdigo de prueba en main( ) ha sido modificado con respecto al que se muestra en BangBeanTest para ilustrar las capa-
cidades de multidifusin de BangBean2 aadiendo escuchas adicionales.
Empaquetado de una Bean
Antes de poder incluir componentes JavaBean en un entorno IDE preparado para ese tipo de componentes, es necesario
incluirlo en un contenedor Bean, que es un archivo JAR que incluye todas las clases Bean, junto con un archivo de "mani-
fiesto" que dice: "Esto es una Bean". Un archivo de manifiesto es simplemente un archivo de texto que se ajusta a un for-
mato concreto. Para el componente BangBean, el archivo de manifiesto tendra el aspecto siguiente:
Manifest-Version: 1.0
Name: bangbeanjBangBean.class
Java-Bean: True
La primera lnea indica la versin del esquema de manifiesto, que ser la 1.0 en tanto que no se produzca una modificacin
de Sun en sentido contrario. La segunda linea (las lneas vacas se ignoran) proporciona el nombre del archivo
BangBean.class, y la tercera dice: "'Esto es una Bean". Sin la tercera lnea, la herramienta de construccin de programas no
podra reconocer esa clase como una Bean.
La nica parte complicada es que tenemos que aseguramos de incluir la ruta adecuada en el campo "Name:". Si volvemos
a examinar BangBean.java, veremos que se encuentra en el paquete bangbean (y por tanto en un subdirectorio denomina-
do bangbean que est fuera de la ruta de clases), y el nombre del archivo de manifiesto deber incluir esta informacin de
paquete. Adems, es necesario colocar el archivo de manifiesto en el directorio situado encima de la raz de la ruta del paque-
te, lo que en este caso significa colocar el archivo en el directorio situado encima del subdirectorio "bangbean". Entonces,
deberemos invocar jar desde el mismo directorio en el que se encuentre el archivo de manifiesto, de la forma siguiente:
jar cfm BangBean.jar BangBean.mf bangbean
Esto presupone que queremos que el archivo JAR resultante se denomine BangBean.jar, y que hemos colocado el archivo
de manifiesto en un archivo denominado BangBean.mf.
Podramos preguntarnos, "Qu sucede con las dems clases que se generaron en el momento de compilar
BangBean.iava?" Todas las clases han terminado incluidas dentro del subdirectorio bangbean, y como puede ver, el lti-
mo argumento de la lnea de comandos iar anterior es un subdirectorio bangbean. Cuando se da a iar el nombre de un sub-
directorio, la herramienta empaqueta dicho subdirectorio completo dentro del archivo JAR (incluyendo, en este caso, el
archivo de cdigo fuente original BangBean.java; no podemos incluir el cdigo fuente con nuestros propios componentes
Beans). Adems, si invertimos el proceso y desempaquetamos un archivo JAR recin creado, descubriremos que el archivo
de manifiesto no se encuentra en su interior, sino que jar ha creado su propio archivo de manifiesto (basado parcialmente
en el nuestro) denominado MANIFEST.MFy colocado dentro del subdirectorio META-INF ("meta-informacin"). Si abre
este archivo de manifiesto. ver tambin que iar ha aadido informacin de firma digital para cada archivo, de la forma:
Digest-Algorithms: SRA MD5
SHA-Digest: pDpEAG9NaeCx8aFtqPI4udsxjOO=
MDS-Digest: 04NcSlhE3Smnzlp2hj6qeg==
En general, no tenemos que preocuparnos por nada de esto, y si realizamos modificaciones, basta con cambiar nuestro archi-
vo de manifiesto original y volver a invocar jar para crear un nuevo archivo JAR para nuestra Bean. Tambin podemos aa-
dir otros componentes Bean al archivo JAR simplemente aadiendo la informacin correspondiente al manifiesto.
22 Interfaces graficas de usuario 931
Un aspecto que hay que resaltar es que nonnalmente conviene colocar cada Bean en su propio subdirectorio, ya que en el
momento de crear tul archivo l AR le entregamos a la uti lidad jar el nombre de un subdirectorio y esta utilidad se encarga
de colocar todo lo que ese subdirectorio contenga dentro del archivo JAR. Podr observar que tanto Frog como BangBean
se encuentran en sus propios subdirectorios.
Una vez que hemos incluido adecuadamente nuestra Bean dentro de un archivo JAR, podemos integrarla dentro de un entor-
no !DE de desarrollo con componentes Bean. La forma de hacerlo vara de una herramienta a otra, pero Sun proporciona
una herramienta de prueba gratuita para JavaBeans, denominada "Bean Builder" (puede descargarla en http://java.
sun.com/beans). Podemos inserlar una Bean dentro de Bean Builder simplemente copiando el archivo JAR en el subdirec-
torio correcto.
Ejercicio 36: (4) Aada Frog.class al archivo de manifiesto de esta seccin y ejecute ar para crear un archivo JAR que
contenga tanto Frog como BangBean. Ahora, descargue e instale la herramienta Bean Builder de Sun, o
utilice su propia herramienta de constrnccin de programas con Beans y aada el archivo JAR al entorno,
para poder probar los dos componentes Bean.
Ejercicio 37: (5) Cree su propia JavaBean denominada Valve que contenga dos propiedades: un valor de tipo boolean
denominado "on" y un valor nt denominado "leve}", Cree un archivo de manifiesto, utilice jar para empa-
quetar la Bean, y luego cargue Bean Builder o alguna herramienta de construccin de programas basada
en Bean para poder probar el componente.
Soporte avanzado de componentes Bean
Podemos ver lo fcil que resulta construir una Bean, pero en realidad no estamos limitados a las operaciones que se han des-
crito aqu. La arquitectura JavaBeans proporciona un punto de entrada muy simple para poder aprender los fundamentos,
pero tambin peImite escalar las soluciones para adaptarlas para situaciones ms complejas. Dichas situaciones quedan fuera
del alcance de este libro, pero las vamos a presentar aqu de forma breve. Puede encontrar ms informacin en
http://java.swl.com/beans.
Un aspecto en el que se puede aadir sofisticacin es el relativo a las propiedades. Los ejemplos que hemos visto hasta ahora
mostraban nicanlente propiedades simples, pero tambin es posible representar mltiples propiedades mediante una matriz.
Esto es lo que se denomina propiedades indexadas. Simplemente basta con proporcionar los mtodos apropiados (que de
nuevo debern ajustarse a un convenio de denominacin para los nombres de mtodos) e Introspector reconocer las pro-
piedades indexadas para que el entorno !DE pueda responder adecuadamente.
Las propiedades puedan estar acopladas, lo que significa que enviarn notificaciones a otros objetos mediante un suceso
PropertyCbangeEvent. Los otros objetos pueden entonces decidir modificarse a s mismos basndose en el cambio sufri-
do por la Bean.
Las propiedades pueden estar restringidas, lo que significa que otros objetos pueden vetar una cierla mo<lificacin de la pro-
piedad si sta resulta inaceptable. Los restantes objetos reciben una notificacin utilizando un suceso PropertyChange-
Event, y pueden generar una excepcin PropertyVetoException para impedir que ese cambio tenga lugar y para restaurar
los antiguos valores.
Tambin se puede mo<lificar la forma en que la Bean est representada en tiempo de diseo:
1. Podemos proporcionar una hoja de propiedades personalizada para nuestra Bean concreta. La hoja de propieda-
des normal se emplear para todos los restantes componentes Bean, pero cuando se seleccione nuestra Bean se
invocar automticamente la hoja personalizada.
2, Podemos crear un editor personalizado para una propiedad concreta, de modo que se utilice la hoj a de propieda-
des normal, pero que cuando se erute la propiedad especial, se invoque automticamente el editor especificado.
3. Podemos proporcionar una clase BeanInfo personalizada para nuestra Bean que genere informacin distinta de
la clase predeterminada creada por Introspector.
4. Tambin es posible activar y desactivar el modo "experto" en todos los descriptores FeatureDescriptor, para dis-
tinguir entre caractensticas bsicas y otras ms complicadas.
Ms informacin sobre componentes Bean
Hay disponibles varios libros acerca de JavaBeans; por ejemplo, JavaBeans de Elliotle Rusty Harold (lDG, 1998).
932 Piensa en Java
Alternativas a Swing
Aunque la biblioteca Swing es la GUI recomendada por Sun, no es en modo alguno la nica forma de crear interfaces gr-
ficas de usuario. Dos alternativas importantes son Macromedia Flash, que usa el sistema de programacin Flex, para inter-
faces GUI del lado del cliente a travs de la Web, y la biblioteca de cdigo abierto SWT (Standard Widget Too/kit) de Eclipse
para aplicaciones de escritorio.
Por qu merecera la pena considerar otras alternativas? Para los clientes web. podemos sostener con cierta rotundidad que
los app/els han fallado. Considerando el tiempo que ha transcurrido desde que aparecieron (desde el principio de Java) y
todas las promesas y expectativas que levantaron, y sigue siendo una sorpresa encontrarse con una aplicacin web basada
en app/els, ni siquiera Sun usa app/ets en todas partes. He aqu un ejemplo:
http://java.sun. com/deve/oper/onlineTraining/new2java/jovamap/intro.htm/
Un mapa interactivo de las caractersticas de Java en el sitio de Sun parecera un candidato bastante apropiado para cons-
truir un opp/el Java, a pesar de lo cual han construido este papel interactivo en Flash. Esto parece ser un reconocimiento
tcito de que las app/ets no han sido precisamente un xito. Adems, el reproductor Flash Player est instalado en ms del
98 por ciento de las platafonnas informticas, por lo que cabe considerarlo como un estndar de facto. Como veremos, el
sistema Flex proporciona un entorno de programacin del lado del cliente muy potente, ciertamente ms potente que
JavaScript y con un aspecto y estilo que resultan a menudo preferibles a los de un applet. Si queremos emplear opplets,
debemos seguir convenciendo al cliente de que descargue el entorno JRE, mientras que Flash Player es de pequeo tamao
y se descarga muy rpidamente.
Para aplicaciones de escritorio, un problema con Swing es que los usuarios se dan cuenta de que estn utilizando un tipo de
aplicacin distinto, porque el aspecto y estilo de las aplicaciones Swing es diferente del que tiene el escritorio normal. Los
usuarios no estn, generalmente, interesados en nuevos aspectos y estilos de aplicaciones, lo que quieren es realizar su tra-
bajo y prefieren que el aspecto de una aplicacin se asemeje al de las restantes aplicaciones. SWT crea aplicaciones que se
asemejan a las aplicaciones nativas, y como la biblioteca utiliza componentes nativos siempre que es posible, las aplicacio-
nes tienden a ejecutarse ms rpidamente que las aplicaciones equivalentes Swing.
Construccin de clientes web Flash con Flex
Debido a la ubicuidad de la mquina virtual ligera Flash de Macromedia, la mayor parte de las personas podrn utilizar una
interfaz basada en Flash sin tener que instalar nada, y esa interfaz tendr el mismo aspecto y se comportar de la misma
forma en todos los sistemas y plataformas. 10
Con Macromedia Flex, podemos desarrollar interfaces de usuario Flash para aplicaciones Java. Flex consiste en un modelo
de programacin basado en XML y en scripts, similar a los modelos de programacin basados en HTML y JavaScript, junto
con una robusta biblioteca de componentes. Se emplea la sintaxis MXML para declarar la gestin de disposicin y los con-
troles de widget (componentes), y se usan scripls dinmicos para aadir mecanismos de tratamiento de sucesos y cdigo de
invocacin de servicios que enlazan la interfaz de usuario con clases Java, modelos de datos, servicios web, etc. El compi-
lador toma los archivos MXML y de script y los compila para generar cdigo intermedio. La mquina virtual Flash en el
cliente opera como la mquina virtual Java, en el sentido de que interpreta cdigo intermedio compilado. El formato del
cdigo intermedio se conoce como SWF, y el compilador Flex genera archivos SWF.
Observe que existe una alternativa de cdigo abierto a Flex disponible en http://openlasz/o.org; esta alternativa tiene una
estmctura similara la de Flex, pero puede resultar preferible para algunas personas. Existen tambin otras herramientas para
crear aplicaciones Flash de diferentes formas.
Helio, Flex
Considere el siguiente fragmento de cdigo MXML, que define una interfaz de usuario (observe que la primera y la ltima
lneas no aparecern dentro del cdigo que descargue como parte del cdigo fuente de este libro):
10 Sean Neville ha creado una pru.1e fundamental del material de esta seccin.
II , ! gui / flex/ helloflexl.mxml
<?xml version::::;: "l .on encoding",nutf - B"?>
<mx:Appl ication
xmlns:mx= .. ht t p: //www.macromedia .com/ 2003 / mxml ..
backgroundColor="#ffffff ">
<mx:Label id=11 output11 text=lIHello, Flex!lI 1>
</mx :Appli cation>
111>
22 Interfaces grficas de usuario 933
Los archivos MXML son documentos XML, por lo que comienzan con una directiva XML de versin/codificacin. El ele-
mento MXML ms externo es el elemento Applieation, que es el contenedor visual y lgico de mayor nivel para una inter-
faz de usuario Flex. Podemos declarar marcadores que presenten controles visuales, como por ejemplo la etiqueta Label del
ejemplo anterior, dentro del elemento AppUeation. Los controles se incluyen siempre dentro de UD contenedor, y los con-
tenedores encapsulan gestores de disposicin. entre otros mecanismos para poder gestionar la disposicin de los controles
incluidos en ellos. En el caso ms simple, como en el ejemplo anterior, AppUeation acta como el contenedor. El gestor de
dispositivo predeterminado de Applieation se limita a colocar los controles verticalmente en la interfaz en el orden que
hayan sido declarados.
ActionScript es una versin de ECMAScript, o JavaScript, que parece muy similar a Java y soporta clases y mecanismos
fuertes de tipado, adems de mecanismos de script dinmico. Aadiendo un script al ejemplo, podemos introducir un cier-
to comportamiento. Aqu, se utiliza el control MXML Seript para incluir cdigo ActionScript directamente dentro del archi-
voMXML:
II, ! gui /flex/hellofl ex2.mxml
<?xml version=1I1.0n encoding::: l!utf-8"?>
<mx:Appli cation
xmlns:mx:::::
ll
ht tp : //www . macromedia. com/ 2003/ mxml"
backgroundCol or ="#ffffff ">
<mx:Script>
<! [CDATA [
function updat eOutput()
output.text == "HelIo!
J J >
</ mx:Scrip t >
+ input.text
<mx:Text lnput i d=lI i nput" width=1I2 00
11
change::::"updat eOutput () 11 />
<mx:Label id::::"out put " text =" He l lo!!I / >
</mx:Application>
111 ,-
Un control TextInput acepta la entrada de usuario y un control Label muestra los datos a medida que se los escribe. Observe
que el atrbuto id de cada control est accesible dentro del scripl en forma de nombre de variable, por lo que el scripl puede
hacer referencias a instancias de los marcadores MXML. En el campo Textlnpllt, podemos ver que el atrbuto change est
conectado a la funcin updateOutput( ) de modo que se invoca la funcin cada vez que se produce cualquier tipo de
cambio.
Compilacin de MXML
La forma ms fcil de comenzar a trabajar con Flex es con la versin gratuita de prueba, que se puede descargar en
\vww.macromedia.comlsoftware/flexllrial. tt El producto est empaquetado en una serie de ediciones, desde versiones de
prueba gratuitas hasta versiones de servidor empresarial, y Macromedia ofrece herramientas adicionales para el desarrollo
de aplicaciones Flex. El empaquetado exacto de las distintas versiones est sujeto a cambios, por lo que deber comprobar
el sitio de Macromedia para conocer los detalles especficos. Observe tambin que puede que necesite modificar el archivo
jvm.eonfig situado en el directorio bin de la instalacin de Flex.
11 Observe que es preciso descargar Flex y no FlexEuilder. Esta ltima es una herramienta de diseo mE.
934 Piensa en Java
Para compilar el cdigo MXML y generar cdigo intermedio Flash, tenemos dos opciones:
1. Podemos insertar el archivo MXML en una aplicacin web Java, junto con pginas JSP y HTML en un archivo
WAR, y hacer que las solicitudes del archivo .mxml se compilen en tiempo de ejecucin cada vez que un explo-
rador solicite la URL del documento MXML.
2. Podemos compilar el archivo MXML utilizando el compilador de la linea de comandos Flex, mxmlc.
La primera opcin, la de la compilacin en tiempo de ejecucin basada en la Web, requiere un contenedor de servlet (como
Apache Tomcat) adems de Flex. El archivo WAR del contenedor servlet de actualizarse con la informacin de configura-
cin de Flex, como por ejemplo los mapeos de servlet que se aaden al descriptor web.xml, y debe incluir los archivos JAR
de Flex, todos estos pasos se gestionan automticamente cuando se instala Flex. Despus de configurado el archivo WAR,
podemos insertar los archivos MXML en la aplicacin Web y solicitar la URL del documento mediante cualquier explora-
dor. Flex compilar la aplicacin cuando se reciba la primera solicitud, de forma similar a la que sucede en el modelo JSP,
yen lo sucesivo suministrar el cdigo SWF compilado y almacenado en cach dentro de un envoltorio HTML.
La segunda opcin no requiere un servidor. Cuando se invoca el compilador mxmlc de Flex en la lnea de comandos, se
generan archivos SWF. Podemos implantar estos archivos de la forma que deseemos. El ejecutable mxmlc est ubicado en
el directorio bln de la instalacin Flex, y si lo invocamos sin ningn argumento nos proporcionar una lista de las opciones
vlidas de lnea de comandos. Normalmente, lo que haremos ser especificar la ubicacin de la biblioteca de componentes
de cliente Flex como valor de la opcin de la lnea de comandos tlexlib, pero en algunos ejemplos muy simples como los
dos que hemos visto hasta ahora, el compilador Flex presupondr la ubicacin de la biblioteca de componentes. Por tanto,
podemos compilar los dos primeros ejemplos de la forma siguiente:
mxmlc.exe helloflexl.mxml
mxmlc . exe helloflex2.mxml
Esto genera un archivo heUotlex2.swf que se puede ejecutar en Flash, o que se puede insertar junto con cdigo HTML en
cualquier servidor HTTP (una vez que Flash haya sido cargado en el explorador web, a menudo basta con hacer doble clic
sobre el archivo SWF para que ste se inicie en el explorador).
Para hellotlex2.swf, veremos la siguiente interfaz de usuario ejecutndose en Flash PI ayer:
I This was nol loo hard lo do... I
Helio, This was not loo hard to do ...
En aplicaciones ms complejas, podemos separar el cdigo MXML y ActionScript haciendo referencia a funciones en archi-
vos ActionScript externos. Desde MXML, se utiliza la siguiente sintaxis para el control Script:
<mx:Script source=!!MyExternal Script.as!1 />
Este cdigo permite a los controles MXML hacer referencia a funciones ubicadas en un archivo denominado MyExternal-
Script.as como si se encontraran en el propio archivo MXML.
MXML y ActionScript
MXML es una especie de abreviatura declarativa para las clases ActionScript. Cada vez que vemos un marcador MXML,
existe una clase ActionScript del mismo nombre. Cuando el compilador Flex analiza el cdigo MXML, primero transforma
el cdigo XML en cdigo ActionScript y carga las clases ActionScript referenciadas, despues de lo cual compila y monta el
cdigo ActionScript para crear un archivo SWF.
Podemos escribir una aplicacin Flex completa utilizando exclusivamente ActionScript, sin usar nada de MXML. Por tanto,
MXML es simplemente un lenguaje de utilidad. Los componentes de interfaz de usuario como los contenedores y contro-
les, se declaran tpicamente utilizando MXML, mientras que la lgica asociada, como por ejemplo las rutinas de tratamien-
to de sucesos y el resto de la lgica de cliente se gestionan mediante ActionScript y Java.
Podemos crear nuestros propios controles MXML y hacer referencia a ellos utilizando MXML, escribiendo clases
ActionScript. Tambin podemos combinar contenedores y controles MXML existentes en un nuevo docwnento MXML al
que luego podamos hacer referencia mediante un marcador en otro documento :MXML. El sitio web de Macromeclia con-
tiene ms informacin acerca de cmo hacer esto.
22 Interfaces grficas de usuario 935
Contenedores y controles
El ncleo visual de la biblioteca de componentes Flex es un conjunto de contenedores que gestionan la disposicin de los
elementos y una matriz de controles que se inserta en esos contenedores. Entre los contenedores se incluyen paneles, recua-
dros verticales y horizontales, mosaicos, acordeones, recuadros divididos, cuadrculas y otros. Los controles son elementos
de la interfaz de usuario, como por ejemplo, botones, reas de texto, deslizadores, calendarios, cuadrculas de datos, etc.
En el resto de esta seccin vamos a mostrar una aplicacin Flex que muestra y ordena una lista de archivos de audio. Esta
aplicacin ilustra el uso de contenedores y de controles y muestra cmo conectar Java desde Flash.
Comenzamos el archivo MXML colocando un control DataGrid (uno de los controles ms sofisticados de Flex) dentro de
un contenedor de tipo Panel:
//: 1 gui/flex/songs.mxml
<?xml version",;"l.O" encoding="utf-8"?>
<mx:Application
xmlns:mx= .. http://www.macromedia.com/2003/mxml
u
backgroundColor",;"#B9CAD2" pageTitle=!1Flex Song Manager"
initialize=lIgetSongs() u>
<tnX:Script source=!lsongScript.as
ll
/>
<mx:Style source="songStyles.css
u
/>
<mx:Panel id= U songListPanel "
titleStyleDeclaration=uheaderText
U
title=IIFlex MP3 Library">
<mx:HBox verticalAlign",;Ubottom">
<tnx:DataGrid id=!lsongGrid
l1
cellPress="selectSong(event) 11 rowCount=uB!l>
<mx:columns>
<mx:Array>
<mx:DataGridColumn colunmName=lIname"
headerText="Song Name
ll
width="120
11
/>
<mx:DataGridColunm colurnnName=lIartist"
headerText=IIArtist" width="lBon />
<tnx:DataGridColunm colurnnName=ualbum"
headerText="Album
ir
width:="160" />
</mx:Array>
</mx:colutnns>
</mx:DataGrid>
<mx:VBox>
<mx:HBox height="loon >
<mx:lmage id="albumlmage
U
source=UI!
height=uBOII width=1I1001l
mouseOverEffect=lIresizeBig"
ltIouseOutEffect=lIresizeSmall
ll
/>
<tnX:TextArea id="songlnfo
ll
styleName=lIboldText
ll
height=uIOO%1I width="120u
vScrollPolicy=lIoff
ll
borderStyle=lInonel! />
</mx:HBox>
<tnX:MediaPlayback id=lIsongPlayer
U
contentPath=urr
mediaType=IIMP3
11
height,,=u70
ii
width=1I230
11
controllerPolicy=."on
H
autoPlay= ir false li
visible=lIfalse
ll
/>
</mx:VBOX>
</mx:HBox>
<n1X: ControlBar horizontalAlign= 11 right rl >
<mx:Button id=urefreshSongsButtOl"
936 Piensa en Java
label ="Refresh Songs" width="lOQ"
toolTip=II Refresh song List"
c lick="songservice ,getSongs () 11 />
</mx:Cont rolBar>
</mx:Panel>
<ffiX:Ef fect>
<mx:Resize name=l'resizeBig" heightTO="lOOIl
duration=1I500
n
/>
<mx:Resize name="resizeSmall" heightTo="80"
duration="500"/ >
</mx:Effect>
<ffiX:RemoteObj ect i d="songService"
source="gui .flex.SongService"
result="onSongs( event.res ult)11
faul t ="alert (event . faul t. faul tstri ng, 'Error I ) ">
<mx. : method name ="getSongs"/>
</mx:RemoteOb ject>
</mx:Application >
// /,-
El control DataGrid contiene marcadores anidados para su matriz de columnas. Cuando vemos un atributo O un elemento
anidado de un control, sabemos que se corresponde con alguna propiedad, sucesos u objeto encapsulado dentro de la clase
ActionScript subyacente. El control DataGrid tiene un atributo id con el valor songGrid, por lo que ActionScript y los mar-
cadores MXML pueden hacer referencia a esa cuadrcula de datos mediante programa empleando songGrid como nombre
de variable. La cuadricula de datos DataGrid expone muchas ms propiedades que las que se muestran aqu; puede encon-
trar la API completa para los controles contenedores MXML en la direccin http://livedocs.macromedia.com/flex/15
/asdocs _ en/indexo html.
El control DataGrid est seguido de un control VBox que contiene una imagen Image para mostrar la eartnla del lbum
junto con la informacin acerca de la cancin, y un control MediaPlayback que permite reproducir archivos MP3. En este
ejemplo se descarga el flujo de contenido con el fin de reducir el tamao del archivo SWF compilado. Cuando se incluyen
imgenes o archivos de audio y vdeo en una aplicacin Flex, en lugar de descargar el fluj o de datos correspondiente, los
archivos pasan a formar parte del archivo SWF compilado y se suministran junto con la interfaz de usuario, en lugar de des-
cargarse bajo demanda en tiempo de ejecucin.
El reproductor Flash Player contiene codees integrados para reproducir y descargar audio y vdeo en una diversidad de for-
matos. Flash y Flex soportan el uso de los formatos de imagen ms comunes de la Web, y Flex tiene tambin la posibilidad
de traducir archivos SVG (scalable vector graphics) a recursos SWF que pueden integrarse en los clientes Flex.
Efectos y estilos
El reproductor Flash Player muestra los grficos utilizando tecnologa vectorial, as que puede realizar transfonmaciones
altamente expresivas en tiempo de ejecucin. Los efectos Flex proporcionan una pequea muestra de este tipo de animacio-
nes. Los efectos son transformaciones que pueden aplicarse a los controles y contenedores utilizando sintaxis MXML.
El marcador Effec! mostrado en el cdigo MXML produce dos resultados: el primer marcador anidado hace crecer dinmi-
camente una imagen cuando se desplaza el ratn sobre l, mientras que el segundo contrae dinmicamente dicha imagen
cuando el ratn se alej a. Estos efectos se aplican a los sucesos de ratn disponibles en el control Image para albumlmage.
Flex tambin proporciona efectos para animaciones comunes como transiciones, cortinillas y canales alfa modulados.
Adems de los efectos predefinidos, Flex soporta la API de dibujo de Flash para la definicin de animaciones verdadera-
mente innovadoras. Una exploracin detallada de este tema implicara muchos conceptos de diseo grfico y animacin, y
esto queda fuera del alcance de esta seccin.
La utilizacin de estilos estndar es posible gracias al soporte que Flex proporciona para la especificacin CSS (Cascading
Style Sheets, hojas de esti lo en cascada). Si asociamos un archivo CSS a un archivo MXML, los controles FJex se adapta-
rn a esos estilos. Para este ejemplo, songStyles.css contiene la siguiente declaracin CSS:
//:! gUi / flex/ songStyles . css
.headerText {
font-family: Arial, 11 sansl! i
font-size: 16;
font-weight: bold
.boldText
font-family: Arial, 11 sansl! i
font-size: 11;
font-weight: bold
22 Interfaces grficas de usuario 937
Este archivo se importa y se emplea en la aplicacin de la biblioteca de canciones a travs del marcador Style en el archi-
vo MXML. Despus de importada la hoja de estilo, sus declaraciones pueden aplicarse a los controles Flex en el archivo
MXML. Como ejemplo, el control TextArea utiliza la declaracin boldText de la hoja de estilo con songInfo id.
Sucesos
Una interfaz de usuario es una mquina de estados; realiza diversas acciones a medida que se producen cambios de estado.
En Flex, estos cambios se gestionan mediante sucesos. La biblioteca de clases Flex contiene una amplia variedad de con-
troles con numerosos sucesos que cubren todos los aspectos de movimiento del ratn y de la utilizacin del teclado.
El atributo click de un botn Bulton, por ejemplo, representa uno de los sucesos disponibles en dicho control. El valor asig-
nado a click puede ser una funcin o un pequeo scripl integrado. En el archivo MXML, por ejemplo, el control ControlBar
incluye el botn refreshSongsButton para refrescar la lista de canciones. Puede ver, analizando el marcador, que cuando se
produce el suceso click se invoca songService.getSongs( l. En este ejemplo, el suceso c1ick del control Bulton hace refe-
rencia al objeto RemoteObject que se corresponde con el mtodo Java.
Conexin con Java
El marcador RemoteObject situado al final del archivo MXML establece la coneXlOn con la clase Java externa
gni.flex.songService. El cliente Flex utilizar el mtodo getSongs( l de la clase Java para recuperar los datos con los que
rellenar la cuadrcula DataGrid. Para hacer esto, es necesario que la clase externa aparezca corno un servicio, es decir, como
un interlocutor con el que los clientes puedan intercambiar mensajes. El servicio definido en el marcador RemoteObject
tiene un atributo source que indica la clase Java del objeto RemoteObject, y especifica una funcin de retrollamada
ActionScript, onSongs( l, que hay que invocar cuando se vuelva del mtodo Java. El marcador method anidado declara el
mtodo getSongs( l, que hace que ese mtodo Java est accesible para el resto de la aplicacin Flex.
Todas las invocaciones de servicios en Flex vuelven asncronamente mediante sucesos disparados hacia esas funciones de
retrollamada. El objeto RemoteObject hace que se muestre un control de cuadro de dilogo y alerta, en caso de que se pro-
duzca un error.
Ahora podemos invocar el mtodo getSongs( l desde Flash utilizando ActionScript:
songService.getSongs() i
Debido a la configuracin de MXML, esto har que se invoque getSongs( l en la clase SongService:
JI: gui/flexjSongService.java
package gui.flex;
import java.util.*
public cIass SongService
private List<Song> songs = new ArrayList<Song>(}
public SongService () { fillTestData (); }
public List<Song> getSongs () { return songs; }
public void addSong(Song song) { songs.add(song);
public void removeSong(Song song) { songs.remove(song);
private void fillTestData() {
addSong (new Song (nChocolate" I "Snow Patrol" I
938 Piensa en Java
"Final St raw", "sp-f inal -straw. jpgU ,
"chocol ate.mp3
U
)) ;
addSong{new Song{lIConcerto No. 2 in El!, "Hi lary Hahn" f
"Bach: Violin Concertos" f "hahn. jpgl! ,
"ba chv iolin2.mp3
11
)} ;
addSong (new Song (" ! Round Midnight Ir f IIWes Montgomery",
uThe Artistry of Wes MontgomeryU,
"wesmontgomery. jpgll, flroundmidnight . mp3
11
) ) j
Cada objeto Song es simplemente un contenedor de datos:
11, gui / flex/Song.java
package gui.f l ex
publ ic class Song implements j ava.io .Ser ializable {
private String name;
private String artist
private String album
private String albumlmageUrl;
private String songMediaUrl
public Song () {}
public Song(String name, String arti s t, String album,
String a lbuml mageUrl, String songMediaUrl) {
this.name name;
this . artist = artist;
thi s .album = album
this.albumlmageUrl = albumlmageUr l
this.songMediaUr l = songMediaUrl
publ ic voi d setAlbum(String album) { this.album = album}
public String getAl bum() { return album; }
publ ic void set AlbumlmageUrl (String albumlmageUrl)
this.albumlmageUrl = albumlmageUrl
public Stri ng getAl bumlmageUr l {) { return albumlmageUrl }
public void setArti st (String artist ) {
this. a r tist = artist
publi c String getArtist () { return art i s t }
public void setName(String name) { this.name = name;
public String getName() { return name }
publi c void setSongMediaUrl(String songMediaUrl)
this.songMediaUrl = songMediaUrl
public String getSongMediaUrl{) { ret urn songMediaUrl }
/! 1,-
Cuando se inicializa la aplicacin o se pulsa un botn refreshSongsButton, se invoca getSongs( ) y, al volver del mtodo,
se llama al mtodo onSongs(event.result) de ActionScript para rellenar la cuadrcula songGrid.
He aqui el listado de cdigo ActionScript, que est incluido dentro del control Script del archivo MXML:
// : gui/f l ex/songScr ipt.as
function getSongs() {
songService.getSongs()
function selectSong(event) {
var song = songGrid.getltemAt(event.iteml ndex)
showSonglnfo{song)
func t ion showSonglnfo(song) {
s onglnfo.text = song.name + newline;
songlnfo.text += song.artist + newline;
songl nfo.text += song.album + newline
a lbumlmage.source = song.albumlmageUr l;
songPlayer.contentPath = song.songMediaUrl;
songPlayer.visible = true;
function onSongs(songs)
songGri d.dat aProvider
} 1// , -
songs;
22 Interfaces grficas de usuario 939
Para gestionar la seleccin de celdas de la cuadrcula de datos DataGrid, aadimos el atributo de sucesos cellPress a la
declaracin DataGrid en el archivo MXML:
cellPress="select Song (event ) 11
Cuando el usuario hace clic sobre una cancin en la cuadrcula de dalos DataGrid, se invoca selectSong( ) en el cdigo
ActionScript anterior.
Modelos de datos y acoplamiento de datos
Los controles pueden invocar directamente servicios, y las retro llamadas de suceso de ActionScript nos dan la posibilidad
de actualizar mediante programa los controles visuales cada vez que los servicios devuelven datos. Aunque el script que
actual iza los controles es bastante sencillo, puede resultar ser bastante largo y engorroso, y su funcionalidad es tan comn
que Flex gestiona el comportamiento automticamente, con acoplamiento de datos.
En su forma ms simple, el acoplamiento de datos permite a los controles hacer referencia directa a los datos, en lugar de
requerir cdigo de conexin para copiar los datos en un control. Cuando se actualizan los datos, tambin se actualiza auto-
mticamente que hace referencia a los mismos sin necesidad de intervencin del programador. La infraestructura de Flex
responde correctamente a los sucesos de cambio de Ios datos y actualiza todos los controles que estn acoplados a ellos.
He aqu un ejemplo simple de sintaxis de acoplamiento de datos:
<mx:Slider id=tlmySlider
n
/>
<mx: Text text="{myslider.value}n/>
Para realizar el acoplamiento de los datos, incluimos las referencias dentro de llaves: {}. Todo lo que se encuentre dentro de
esas llaves se considera una expresin que Flex debe evaluar.
El valor del primer control, que es un widget de tipo Slider, se visualiza mediante el segundo control, que es un campo Text.
A medida que el control Slider cambia, la propiedad text del campo Text se actualiza automticamente. De esta forma, el
desarrollador no necesita gestionar los sucesos de cambio del control Slider para actualizar el campo Text.
Algunos controles, como el control Tree y el control DataGrid de la aplicacin de la biblioteca de canciones, son ms sofis-
ticados. Estos controles tienen una propiedad dataprovider para facilitar el acoplamiento a colecciones de datos. La fun-
cin ActionScript onSongs( ) muestra cmo est acoplado el mtodo SongService.getSongs( ) al proveedor de datos
dataprovider del control Flex DataGrid. Tal como se declara en el marcador RemoteObject del archivo MXML, esta fun-
cin es la retrollamada que ActionScript invoca cada vez que vuelve el mtodo Java.
Una aplicacin ms sofisticada con un modelado de datos ms complejo, como por ejemplo una aplicacin empresarial que
hiciera uso del estndar DTO (Data Transfer Ob}ects) de objetos de transferencia de dalos o una aplicacin de mensajera
con datos que tuvieran que adaptarse a esquemas complejos, podra desacoplar todava ms el origen de los datos de los con-
troles. En el desarrollo con Flex, realizamos este desacoplamiento declarando un objeto "Modelo", que es un contenedor
MXML genrico para los datos. El modelo no contiene ninguna lgica. Se asemeja a los objetos DTO que podemos encon-
trar en las actividades de desarrollo empresarial y a las estructuras de otros lenguajes de programacin. Utilizando este
modelo, podemos acoplar nuestros controles al modelo, y hacer al mismo tiempo que el modelo acople sus propiedades con
entradas y salidas de servicio. Esto hace que se desacoplen los orgenes de datos, los servicios, de los consumidores visua-
940 Piensa en Java
les de los datos, facilitando el uso del patrn Modelo-Visto-Controlador (MVC). En aplicaciones ms sofisticadas y de
mayor envergadura, la complejidad inicial provocada por la insercin de un modelo constituye una desventaj a si la compa-
ramos con el valor que nos proporciona una aplicacin MVC limpiamente desacoplada.
Adems de acceder a objetos Java, Flex tambin puede acceder a servicios web basados en SOAP y a servicios HTTPuti-
lizando los controles WebService y HttpService, respectivamente. El acceso a todos los servicios est sujeto a restriccio-
nes de autorizacin por razones de seguridad.
Construccin e implantacin de aplicaciones
Con los ejemplos anteriores, podamos prescindir del indicador -flexlib en la lnea de comandos, pero para compilar este
programa, tenemos que especificar la ubicacin del archivo flex-config.xml mediante el indicador -flexlib. En mi instala-
cin, el comando que hay que utilizar es el siguiente, pero el lector tendr que modificarlo de acuerdo con su propia confi-
guracin (el comando es una nica lnea, que se presenta eu varias lneas debido a la limitacin de la anchura del libro ):
// :! gui /fl ex/ build- command .txt
mxmlc - flexlib C:/llprogram Files
ll
/ Macromedi a /Flex/jrun4/servers/defaul t/flex/WEB- INF/flex
songs. mxml
111 ,-
Este comando construir la aplicacin generando un archivo SWF que podemos ver en nuestro explorador, pero el archivo
de distribucin de cdigo del libro no contiene ningn archivo MP3 ni JPG, por lo que no ver nada salvo el marco conte-
nedor cuando ejecute la aplicacin.
Adems, deber configurar un servidor para poder comunicarse adecuadamente con los archivos Java desde la aplicacin
Flex. El paquete de prueba de Flex incluye el servidor JRun, y podemos arrancar este servidor desde los mens de la com-
putadora despus de instalar Flex, o a travs de la lnea de comandos:
jrun -start default
Puede verificar que el servidor ha arrancado adecuadamente abriendo hllp:lllocalhost:8700lsamples en un explorador web
y visualizando los diversos ejemplos (sta es tambi n una forma de familiarizarse con las capacidades de Flex).
En lugar de compilar la aplicacin en la lnea de comandos, podemos compilarla mediante el servidor. Para hacer esto, inser-
te los archivos fuente del ejemplo de las canciones, la hoja de CSS, etc., en el directorio jrun4/servers/defaultlflex y acce-
da a ellos desde un explorador abriendo http://localhost:8700/flexlsongs.mxml.
Para ejecutar adecuadamente la aplicacin, deber configurar tanto el lado de Java como el lado de Flex.
Java: los archivos compilados Song.java y SongServiee.java deben colocarse en el directorio WEB-INF/classes. Ah es
donde deben incluirse las clases WAR de acuerdo con la especificacin J2EE. Alternativamente, puede comprimir en un
archivo JAR los archivos y colocar el resultado en WEB-INF/lib. Debe estar en un directorio que se ajuste a su estructura
de paquetes Java. Si est usando, los archivos se colocarn en jrun4/servers/default/flex/WEB-INF/c\asses/guilflexl
Song.class y jrun4/servers/defaultlflex/WEB-INF/c\asses/guilflexlSongService.class. Tambin necesitar los archivos de
imagen y MP3 disponibles en la aplicacin web (para JRun, jrun4/servers/default/flex es la raz de la aplicacin web).
Flex: por razones de seguridad, Flex no puede acceder a objetos Java a menos que le demos permiso modificando el flex-
config.xml. Para JRun, este archivo se encuentra en jrun4/servers/default/flexlWEB-INF/flex/flex-eonfig.xml. Localice
la entrada <remote-objeets> en dicho archivo, y examine la seccin <whitelist> incluida en ella y vea la siguiente nota:
<!--
For seeurity, the whitelist is locked down by default. Uncomment the souree element below lo enable
access lo ail classes during deve/opment.
We strongly recommend no! alIowing access fa al! saurce files in production, since this exposes Java
and Flex system cJasses.
<saurce> *</source>
-->
Elimine el comentario de esa entrada <source> para pennitir el acceso, de modo que quede <source>*</source>. El signi-
ficado de sta y otras entradas se describe en los documentos de configuracin de Flex.
22 Interfaces grficas de usuario 941
Ejercicio 38: (3) Construya el "ejemplo simple de sintaxis de acoplamiento de datos" mostrado anterionnente.
Ejercicio 39: (4) La descarga de cdigo para este libro no incluye los archivos MP3 o JPG mostrados en
SongService.java. Localice algunos archivos MP3 y JPG, modifique SongScrvice.java para incluir los
correspondientes nombres de archivo, descargue la versin de prueba de Flex y construya la aplicacin.
Creacin de aplicaciones SWT
Como hemos indicado anteriormente, Swing adopt el enfoque de construir todos los componentes de la interfaz de usua-
rio pxel por pixel, con el fin de proporcionar todos los componentes deseados independientemente de si el sistema opera-
tivo subyacente dispona de ellos o no. SWT adopta una postura intermedia, utilizando componentes nativos si el sistema
operativo los proporciona, y sintetizando los componentes si no lo hace. El resultado es una aplicacin que para el usuario
se asemeja a una aplicacin nativa, y que a menudo tiene la velocidad bastante superior a la del programa Swing equivalen-
te. Adems, SWT tiende a ser un modelo de programacin menos complejo que Swing, lo cual puede resultar deseable en
un gran nmero de aplicaciones.
12
Puesto que SWT utiliza el sistema operativo nativo para realizar la mayor parte posible del trabajo, puede aprovechar auto-
mticamente algunas de las caractersticas del sistema operativo que pueden no estar disponibles con Swing; por ejemplo,
Windows tiene mecanismo de "representacin subpxel" que hace que las fuentes de caracteres parezcan ms ntidas en las
pantallas LCD.
Resulta incluso posible crear applets utilizando SWT.
Esta seccin no pretende ser una introduccin completa a SWT; simplemente se trata de proporcionar una panormica de
esta biblioteca y de comparar SWT con Swing. Descubrir que existe una gran cantidad de widgets SWT y que todos ellos
son bastante sencillos de utilizar. Puede analizar los detalles en la documentacin completa y los muchos ejemplos que podr
encontrar en www.eclipse.org. Tambin hay diversos libros de programacin con SWT, y posibl emente aparezcan ms en el
futuro.
Instalacin de SWT
Las aplicaciones SWT requieren que se descargue e instale la biblioteca SWT desarrollada en el proyecto Eclipse. Vaya a
www. eclipse.org/down/oads/y seleccioneunode los sitios espejo. Siga los vnculos hasta localizar la versin Eclipse actual
y localice un archivo comprimido con un nombre con "sw!" e incluya el nombre de su plataforma (por ejemplo, "win32").
Dentro de este archivo encontrar swt.jar. La fonna ms fcil de instalar el archivo swt.jar consiste en colocarlo en el direc-
torio jre/lib/ext (de esta fonna, no tendr que hacer ninguna modificacin en su ruta de clases). Cuando descomprima la
biblioteca SWT, puede que encuentre archivos adicionales que necesitar instalar en los lugares apropiados para su plata-
forma. Por ejemplo, la distribucin Win32 incluye archivos DLL que tienen que incluirse en algn lugar de la ruta
java.library.path (sta coincide usualmente con la variable de entorno PATH, pero puede ejecutar object/ShowProperties
.java para descubrir el valor real de java.llbrary.path). Una vez que haya hecho esto, deberia poder compilar y ejecutar
transparentemente la aplicacin SWT como si fuera cualquier otro programa Java.
La documentacin de SWT se encuentra en un archivo de descarga separado.
Una tcnica altemativa consiste en limitarse a instalar el editor Eclipse, que incluye tanto SWT como la documentacin de
SWT que se puede visualizar a travs del sistema de ayuda de Eclipse.
Helio, SWT
Comencemos con la aplicacin ms simple posible del estilo de la conocida aplicacin "helio world":
jj , swtjHelloSWT.j ava
II {Requires: org. ecl ipse.swt. widgets.Di splay; You rnust
II instal l the SWT library fram http: //www.eclipse.org }
import org .eclipse. swt.widgets.*;
public class HelloSWT {
12 Chris Grindstaff result de mucha ayuda a la hora de lTaducir los ejemplos a SWT y de proporcionar infonnacin acerca de SWT.
942 Piensa en Java
public static void main(String [] args)
Display display = new Display() i
Shell shell = new Shell(display)
shell.setText(!!Hi there, SWT!I!) II Barra de ttulo
shell.open()
while(!shell.isDisposed())
if(!display.readAndDispatch() )
display.sleep() ;
display.dispose() i
Si descarga el cdigo fuente de este libro, descubrir que la directiva de comentario "Requires" tennina siendo incluida en
el archivo build.xml de Ant como un pre-requisito para construir el subdirectorio swt; todos los archivos que importen
org.eclipse.swt requieren que se instale la biblioteca SWT de www.eclipse.org.
La clase Display gestiona la conexin entre SWT y el sistema operativo subyacente; fonua parte de un Puente entre el sis-
tema operativo y SWT. La clase Shell es la ventana principal de nivel superior. dentro de la que se construyen todos los
dems componentes. Cuando se invoca setText( ), el argumento se convierte en la etiqueta que aparecer en la barra de ttu-
lo de la ventana.
Para mostrar la ventana y luego la aplicacin, debe invocar open( ) sobre el objeto Shell.
Mientras que Swing oculta a nuestros ojos el bucle de tratamiento de sucesos, SWT nos obliga a escribirlo explcitamente.
En la parte superior del bucle, miramos si la shell ha sido eliroinada; observe que esto nos proporciona la opcin de inser-
tar cdigo para llevar a cabo actividades de limpieza. Pero esto quiere decir que la hebra main( ) es la hebra de la interfaz
de usuario. En Swing, se crea de manera transparente una segunda hebra de despacho de sucesos, pero en SWT, es la hebra
main( ) la que se encarga de gestionar la interfaz de usuario. Puesto que de manera predeterminada slo existe una hebra y
no dos, esto hace que sea algo menos probable que terminemos sobrecargando la interfaz de usuario con hebras.
Observe que no tenemos que preocuparnos de enviar tareas a la hebra de interfaz de usuario, a diferencia de 10 que suceda
en Swing. SWT no slo se encarga de esto por nosotros, sino que genera una excepcin si tratamos de manipular un widget
con la hebra errnea. Sin embargo, si necesitamos crear hebras para realizar operaciones de larga duracin, seguimos nece-
sitando enviar los cambios de la misma fonna que se hace en Swing. Para esto, SWT proporciona tres mtodos que pueden
invocarse sobre el objeto Display: asyncExec(Runnable), syncExec(Runnable) y timerExec(int, Runnable).
La actividad de la hebra main( ) en este punto consiste en llamar a readAndDispatch( ) para el objeto Display (esto signi-
fica que slo puede haber un objeto Display por cada aplicacin). El mtodo readAndDispatch() devuelve true si hay dos
sucesos en la cola de sucesos esperando ser procesados. En dicho caso, hay que volver a invocar un -mtodo inmediatamen-
te. Sin embargo, si no hay nada pendiente, invocamos el mtodo sleep( ) del objeto Display para esperar durante un perio-
do breve de tiempo antes de volver a consultar la cola de sucesos.
Una vez completado el programa, es necesario eliminar explcitamente el objeto Display con dispose( ). SWT requiere a
menudo que eliminemos explcitamente los recursos no utilizados, porque se trata usualmente de recursos del sistema ope-
rativo subyacente, que podran de otro modo agotarse.
Para demostrar que el objeto Shell es la ventana principal, he aqu un programa que construye una serie de objetos Shell:
11: swt/ShellsAreMainWindows.java
import org.eclipse.swt.widgets.*i
public class ShellsAreMainWindows
static Shell[] shells = new Shell[lO];
public static void main(String [] args)
Display display = new Display() i
for(int i = O; i < shells.length; i++)
shells[i] = new Shell(display)
shells[i] . setText("Shell W' + i) i
shells [i] . open () ;
while(lshellsDisposed())
if {!displ ay .readAndDispatch())
display.sl eep() ;
display .dispose( ) i
s tatic boolean shellsDisposed( )
f ar( int i = o; i < shells. length; i ++)
if(shells[il .isDisposed())
return true i
return false
22 Interfaces grficas de usuario 943
Al ejecutarlo, se obtienen diez ventanas principales. De la forma en que se ha escrito el programa, si se cierra una ventana,
se cerrarn todas.
SWT tambin emplea gestores de disposicin, que son distintos a los de Swing, pero que estn basados en la misma idea.
He aqu un ejemplo ligeramente ms complejo que toma el texto de System.getProperties( ) y lo aade a la ,he/!:
JI : s wt f DisplayProperties.java
import org.eclipse.swt.*i
import org.eclipse.swt.widgets.*
import org.eclipse .swt.layout.*;
import java.io.*;
public class DisplayProperties
public static void main(String [J argsl
Display display = new Display();
Shell shell = new Shell(display);
shell.setText{IIDisplay Properties!l);
shell. setLayout(new FillLayout{ );
Text tex t new Text(shell, SWT.WRAP I SWT.V_SCROLL)
StringWriter props = new Stri ngWri ter()
System.getProperties() .list(new Print Writer (props
text.set Text(props.toString(
shell.openO;
while(!shel l.isDisposed {
if {!display.readAndDispatch (
display.sleepl) ;
display.dispose{)
En SWT, todos los widgets deben tener un objeto padre de tipo general Composite, y hay que proporcionar este padre como
el primer argumento en el constructor de widget. Podemos ver esto en el constructor Text, donde sheU es el primer argu-
mento. Casi todos los constructores tambin toman un argumento indicador que permite proporcionar varias directivas de
estilo, dependiendo de lo que el widget concreto acepte. Las diversas directivas de estilo se combinan bit a bit mediante la
operacin OR, como puede verse en el ejemplo.
A la hora de configurar el objeto Text(), he aadido indicadores de estilo para que el texto efecte saltos de lnea autom-
ticos, y para que se aada automticamente una barra de desplazamiento vertical en caso necesario. Cuando trabaje con este
sistema, descubrir que SWT depende bastante de los constructores; existen muchos atributos de un widget que son difici-
les o imposibles de cambiar, excepto a travs del constructor. Compruebe siempre la documentacin del constructor del wid-
gel para ver qu indicadores acepta. Observe que algunos constructores requieren un argumento indicador, aun cuando la
docunlentacin no especifique ningn indicador "aceptado". Esto permite una futura expansin sin necesidad de modificar
la interfaz.
Eliminacin del cdigo redundante
Antes de continuar adelante, observe que hay que realizar ciertas cosas para cada aplicacin SWT, de la misma forma que
existan acciones duplicadas en los programas Swing. En SWT, siempre creamos un objeto Display, construimos un objeto
944 Piensa en Java
Shell a partir del objeto Display, creamos un bucle readAndDispatch( l , etc. Por supuesto, en algunos casos especiales,
puede que no hagamos esto, pero estas tareas son lo suficientemente comunes como para que merezca la pena intentar eli-
minar el cdigo duplicado, como hicimos con net.mindview.util.SwingCollsole.
Tendremos que obligar a cada aplicacin a adaptarse a una interfaz:
jj: swt jutil jSWTApplication.java
package swt.util
import org. eclipse .swt.widgets . *
public int erface SWTApplication
voi d creat eContents(Composite parent )
} /// ,-
A la aplicacin se le entrega un objeto Composite (SheU es una subclase) y la aplicacin debe usar este objeto para crear
todos sus contenidos dentro de createContcnts( l. SWTConsole.run( ) invoca createContents( ) en el punto apropiado,
establece el tamao de la sheU de acuerdo con lo que el usuario le haya pasado a run( l , abre la shell y luego ejecuta el bucle
de sucesos, eliminando finalmente la shell al salir del programa:
jI : swtjutil/SWTConsole . java
package swt.util
impor t org.eclipse.swt.widgets.*
public class SWTConsol e {
public sta tic void
run(SWTAppli cation swtApp, int width, int height) {
Displ ay display = new Di splay( ) ;
Shell s hell = new Shell(display);
s he l l.setText (swtApp.getClass () . getSimpleName( )
swtApp.createContents (shel l }
s hel l. setSize(width, hei ght)
shell . open () ;
whil e (! shell. isDisposed ()) {
if(! display.readAndDi spatch() )
di splay.sleep() ;
display .di s pose () ;
}
/// , -
Esto establece tambin la barra de ttulo, asignndole el nombre de la clase SWTApplication, y establece la anchura y altu-
ra de la SheU mediante los valores width y hcighl.
Podemos crear una variante de DisplayProperties.java que muestre el entorno de la mquina, usando SWTConsole:
ji : swt j DisplayEnvironment . java
i mport swt .util.*
impore org . eclipse .swt. * ;
i mport org.eclipse.swt . widget s .*
import org.eclipse.swt . l ayout. *
import java.util.*
public clase DisplayEnvironment implements SWTApplication
public void c reateContents (Composi t e parent ) {
parent.setLayout( new FillLayout( ))
Text text = new Text (pa r en t, SWT . WRAP I SWT. V_SCROLL)
for {Map.Ent ry entry: System.getenv() .entrySet( )) {
text.append(entry.get Key () + n: 11 +
entr y.get Value() + "\ n") i
public stat i c void main(St r ing [) args) {
22 Interfaces grficas de usuario 945
SWTConsole,run(new DisplayEnvironment() I 80 0, 600) i
///,-
SWTConsole nos permite centrarnos en los aspectos ms interesantes de una aplicacin, en lugar de tener que escribir el
cdigo repetitivo.
Ejercicio 40: (4) Modifique DisplayProperties.java para utilizar SWTConsole.
Ejercicio 41: (4) Modifique DisplayEnvironment.java para no utilizar SWTConsole.
Mens
Para ilustrar los fundamentos de los mens, el siguiente ejemplo lee su propio cdigo fuente y lo descompone en palabras,
rellenando luego los mens con estas palabras:
JI : swc/Menus.j ava
/1 Ejemplo de mens.
i mport swt.ut il.*i
import org.eclipse.swt.*i
import org .eclipse . swt.widgets .*
i mport j ava.util.*i
import net . mindview.uti l. *
public cI ass Menus implements SWTAppli cation {
private static Shell shell
public void createContents(Composite parent )
s hell parent.get Shell () ;
Menu bar = new Menu{shel l , SWT.BAR)
shell .setMenuBar( bar l i
Set<String> words = new TreeSet <String>(
new TextFi l e (lIMenus. j ava
U
, u\ \W+
n
));
Iterator<Stri ng> i t = words.iterator () ;
while (it. next () . matches ( 11 [O - 9 ] + " ) )
; II De splazarse hasta pasados los nmeros.
Menultem[] mltem = new Menul tem[ 7] ;
for(int i = O: i < mltem.length i++ ) {
mltem[i] = new Menultem(bar, SWT.CASCADE)
mltem [i ] . setText (it . next () ) ;
Menu submenu = new Menu(shell, SWT .DROP_DOWN) ;
mlt em[i] .setMenu(submenu)
int i = O;
while(it. hasNext())
addltem(bar, i t, mltem[i ] ) ;
i (i + 1 ) % mltem.length
s tat i c Listene r listener = new Listener()
pUbl i c void handleEvent(Event el {
System.out.println{e . toString{)) ;
}
};
voi d
addl tem(Menu bar , I terator<String> i t, Menul tem mltem) {
Menultem i tem = new Menultem(mltem.getMenu (),SWT .PUSH)
i tem. addLi stener {SWT.Selection, l i stener)
item. setText (it. next () ) ;
public static void main (String [] args ) {
946 Piensa en Java
sWTConsole .run(new Menus(), 600, 200)
)
/// ,-
Los objetos Menu deben colocarse dentro de una Shell, y Composite permite deteOllinar la shell mediante getShell( ).
TextFile procede de net.mindview.util y ya lo hemos descrito antes en el libro; aqu, se rellena un conjunto TreeSet con
palabras, de foOlla que aparezcan ordenadas. Los elementos inicales son nmeros, que se descartan. Utilizando el flujo de
palabras, se asigna un nombre a los mens de nivel superior de la barra de mens y luego se crean los submens y se relle-
nan con palabras hasta que no quedan ms palabras.
En respuesta a la seleccin de uno de los elementos de men, Listener simplemente imprime el suceso para que podamos
ver el tipo de informacin que contiene. Cuando se ejecute el programa, ver que parte de la infoOllacin incluye la etique-
ta del men, de manera que podemos pasar la respuesta del memi en dicha infoOllacin; o bien podemos proporcionar un
escucha diferente para cada men (que es un enfoque ms seguro, de cara a la intemacionalizacin).
Paneles con fichas, botones y sucesos events
SWT dispone de un rico conjunto de controles, denominados widgets. Examine la documentacin de org.eclipse.swt.wid-
gets para ver los controles bsicos y org.eclipse.swlcustom para ver otros ms sofisticados.
Para ilustrar algunos de los widgels bsicos, este ejemplo coloca una serie de subejemplos dentro de paneles con fichas.
Tambin podr ver cmo crear objeto Composite (aproximadamente equivalente a los paneles JPanel de Swing) para inser-
tar elementos dentro de otros elementos:
11 : swt/TabbedPane.java
II Colocacin de componentes SWT en paneles con fi chas.
i mport swt. uti l .*
i mport org.eclipse.swt.*;
import org.eclipse.swt.widgets.*
import org.eclipse.swt.events.*;
import org.ec l ipse.swt.graphics .*
import org. eclipse.swt.layout.*
import org.eclipse.swt.browser . *
public clas5 TabbedPane implements SWTApplication {
privat e static TabFolder folder
pri vate static Shell shell
publ i c void createContent s (Compos i te parent)
shell = parent .getShell();
parent.setLayout(new FilILayout() )
folder = new TabFolder(shell, SWT .BORDER)
labelTab( ) ;
directoryDialogTab()
buttonTab() ;
s liderTab ()
scribbleTab() i
browserTab ()
public static void label Tab () {
Tabltem tab = new Tabltem( folder, SWT.CLOSE)
tab. setText ("A Label ") II Texto de la ficha
tab. setTool TipText ( l/A simp le label " ) ;
Label label = new Label( folder. SWT.CENTER)
l abel. setText ( "Label text 11 ) ;
tab.setControl (label ) ;
public static vod directoryDialogTab()
Tabltem tab = new Tabltem( folder , SWT.CLOSE)
tab. setText ( I/Directory Dialog ")
tab.set ToolTipText ( lISe l ect a directoryU ) i
final Buttan b '" new Buttan{ f older, SWT.PUSH:) ;
b . setText(IISe l ect a Directaryt<) ;
b. addListener (SWT.MouseDawn, new Listener (} {
public vaid handleEvent(Event el (
}
BirectaryDialog dd = new DirectoryDi a l og{shell);
Stri ng path = dd.open();
if (path ! = null)
b. set'Text (path) i
} ) ;
tab.setCantrol (b).;
public static va id buttonTab ()
Tabltem tab = new Tabl tem(folder, SWT.CLOSE) i
tab.setText(IIButt ans
lr
) i
tab. s etTaalTipText (!!Different kinds of Buttons,u). i
Composit'e campos ite = new Composite (folder, SWT .NONE) ;
compos ite .setLayout (new GridLayoutf4, true) );
for fint di r : new' i nt [) {
SWT.UP, SWT. LEFT,
}) {
Buttan b = new Button (camposite, SWT.ARROW dir);
b. addLi stener (SWT.MouseDawn, listener );
newBut ton(compas ite, SWT.CHECK, nCheck buttan");
newButton(compas ite, SWT.PUSH, IIPush buttan!!);
newButton(composite, SWT . RADIO, IIRadi o buttonll)
newBut t on(compos ite
r
SWT.TOGGLE , II Toggle button
"
);
newButton(composite, SWT. FLAT, "Flat but ton
"
);
tab .se'tControl(composite) ;
private static Listener listener = new List ener () {
publ ic void handleEvent {Event e) {
}
} ;
MessageBox m = new MessageBox(shell, SWT.OK)
m.setMessage(e . toString()) ;
m.openO;
private stat i c void newButton{Composite composite,
int type, String labe! ) {
Button b = new Button(composite, type);
b.setText( l abell ;
b.addListener(SWT.MouseDown, l istener);
public static void sliderTab {)
Tabltem tab = new Tabl tem(folder, SWT . CLOSE) ;
tab . setText ( "Sliders and Progress bars
ll
);
tab. setToolTipText (IITied Slider to ProgressBar") i
Compos ite composi te new Composi te(folder, SWT.NONE)
composi te.setLayout{new GridLayout(2, true ;
final Slider slider =
new Slider(composite, SWT . HORIZONTAL}
fi nal ProgressBar pr ogre ss =
new ProgressBar{composite , SWT.HORIZONTAL) i
slider.addSe lectionListener {new SelectionAdapter()
publ i c void widgetSelected(SelectionEvent event}
progress.setSelection(slider.getSelection() ;
}
} l ;
tab.setControl(composite) i
22 Interfaces grficas de usuario 947
948 Piensa en Java
publ i c static void scribbleTab () {
Tabl tem t ab = new TabItem( f o l der, SWT,CLOSE)
tab. setText( lIScribble
ll
) i
tab.setToolTipText(lISimple graphics: drawing"} i
final Canvas canvas = new Canvas(folder, SWT.NONE) ;
ScribbleMouseListener sml = new ScribbleMouseListener();
canvas.addMouseListener(srnl) i
canvas.addMouseMoveLi s t ener (sml) ;
tab.setControl(canvas) i
pri va t e s t ati c class Scribbl eMouseListener
extends MouseAdapter i mplements MouseMoveListener
private Point p = new Point(O, O) j
public vo id mouseMove(MouseEvent el {
iflle.stateMask & SWT.BUTTON1) == O)
return
GC ge = new GC Canvas)e , widget )
gc.drawLine(p.x, p . y, e .x, e.y) i
gc . dispose() i
updat e Point (e ) i
publi c voi d mouseDown(MouseEvent el { updatePoint(el i
pri vat e void updatepoint(MouseEvent e l {
p.X e.Xi
p. y = e. y;
public st ati c void browser Tab () {
TabItem tab = new TabItem(folder , SWT.CLOSE) i
tab.setText ( "A Br owser " ) i
tab. setToolTipText("A Web browser ")
Browser browser = null
try {
browser = new Br owser(folder, SWT.NONE) i
catch( SWTError el {
Label label = new Label (f older , SWT.BORDER)
label.setText("Could not initialize browser");
t ab.setControl(label) j
if(browser 1= null) {
brows er. setUrl ( "http://www.mindview.net
ll
) i
tab .setControl (browser)
pUblic static void ma i n{String [] args) {
SWTConsole.run{new TabbedPane( ) , 800, 600);
Aqu, createContents( ) establece la disposicin de los elementos y luego invoca los mtodos que se encargan de crear las
distintas fichas. El texto de cada ficha se establece con setText() (tambin podemos crear botones y grficos en una ficha),
y cada una de las fichas establece tambin el texto de sugerencia. Al fmal de cada mtodo, puede ver una llamada a
setControl( ), que coloca el control que el mtodo ha creado dentro del espacio correspondiente a cada ficha concreta.
labelTab( ) ilustra un etiqueta simple de texto. directoryDialogTab( ) contiene ID' botn que abre un objeto estndar
DirectoryDialog para que el usuario pueda seleccionar un directorio. El resultado se asigna como texto del botn.
buttonTab( ) muestra los diferentes botones bsicos. sliderTab( ) repite el ejemplo Swing presentado anteriormente en el
captulo, que consista en asociar un deslizador con una barra de progreso.
22 Interfaces grficas de usuario 949
scribbleTab() es un divertido ejemplo de grficos. Se genera un programa de dibujo utilizando slo unas pocas lineas de
cdigo.
Por ltimo, browserTab() muestra la potencia del componente Browser de SWT, que es un explorador web completamen-
te funciona1 empaquetado en un nico componente.
Grficos
He aqu el programa SineWave.java de Swing traducido a SWT:
/1 : swt/SineWave.java
// Traduccin a SWT del programa Swing SineWave.java.
import swt.util . *
import org. e clipse.swt .*i
import org.ec lipse .swt.widget s .*
import org . eclipse .swt.event s.*;
i mport org.eclipse .swt.layout .*i
cIass SineDraw extends Canvas {
prvate static f ina l i nt SCALEFACTOR 200;
prvate i nt cycl es;
private int points;
private double[] sines;
private int[] pts ;
public SineDraw(Composit e parent, i nt s tyle)
super (parent, s tyl e ) ;
a ddPa i ntLi stener (new PaintLi stener () {
public void paintControl (Pa i nCEvent e l
i nt maxWidth "" -getSi ze() . Xi
)
double hstep "" (doublelmaxWi dth / (double)points i
int maxHeight "" get Size() .Yi
pts = new int[points];
for( i nt i = O; i < points i++)
pts [i ) = (i nt ) (sines[i) * maxHeight / 2 * .95 )
+ ImaxHeight / 2;
e.gc.setForeground(
e.di splay .getSystemColor (SWT. COLOR_RED))
for ( i nt i = l; i < points i ++ ) {
int xl (int) i - 1) * hstep);
int x2 (int) (i * hstep);
int yl pts[i - 11 ;
i nt y2 pts [i] ;
e.gc.dr awLine(xl , y1, x2, y2)
) ;
setCyc l es(S) j
public void setCycl es(int newCycles)
cycles = newCycles
points = SCALEFACTOR * cycles * 2;
sines = new double [poi nts)i
for (i nt i = O; i < points i ++)
double radians = (Math . PI / SCALEFACTOR) * i
sines( i] = Mat h . sin(radians ) i
redraw() ;
950 Piensa en Java
public class SineWave implements SWTApplication {
private SineDraw sines;
private Slider slider
public void createContents(Composite parent)
parent.setLayout(new GridLayout(l, true)) i
sines new SineDraw(parent, SWT.NONE);
sines.setLayoutData(
1
new GridData(SWT.FILL, SWT.FILL, true, true)) i
sines.setFocus() ;
Blider = new Slider(parent, SWT.HORIZONTAL);
slider.setValues(5, 1, 3D, 1, 1,1) i
slider.setLayoutData(
new GridData{SWT.FILL, SWT.DEFAULT, true, false));
slider.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
sines.setCycles{slider.getSelection{)) ;
)
l) ;
public static void main(String[] args) {
SWTConsole.run(new SineWave(), 700, 400)
En lugar de JPanel, la superficie de dibujo bsica en SWT es Canvas.
Si comparamos esta versin del programa con la versin Swing, veremos que SineDraw es prcticamente idntico. En SWT,
obtenemos el contexto grfico gc a partir del objeto suceso que se entrega al escucha PaintListener, y en Swing el objeto
Graphics se entrega directamente al mtodo paintComponent( l. Pero las actividades realizadas con el objeto grfico son
iguales y setCyc1es( l es idntico.
createContents( ) requiere algo ms de cdigo que la versin Swing, para disponer los elementos y configurar el desliza-
dor y su correspondiente escucha, pero de nuevo, las actividades bsicas son aproximadamente iguales.
Concurrencia en SWT
Aunque AWT/Swing es monohebra, resulta posible violar fcilmente esa caracteristica monohebra de manera que se obten-
ga un programa no determinista. Bsicamente, debemos evitar tener mltiples hebras escribiendo en la pantalla, porque las
unas escribirn sobre lo que hayan escrito las otras de manera sorprendente.
SWT no permite que esto suceda, puesto que genera una excepcin si tratamos de escribir en la pantalla empleando ms de
una hebra. Eso evitar que un programador inexperto cometa accidentalmente este error e introduzca errores dificiles de
localizar dentro de un programa.
He aqu la traduccin a SWT del programa Swing ColorBoxes.java:
1/: swt/ColorBoxes.java
1/ Traduccin a SWT del programa Swing ColorBoxes.java.
import swt.util.*
import org.eclipse.swt.*i
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.events.*
import org. eclipse. swt. graphics. * i
import org.eclipse.swt.layout.*;
import java.util.concurrent.*
import java.util.*;
import net.mindview.util.*;
class CBox extends Canvas implements Runnable {
class CBoxPaintListener implements PaintListener
publicvoid paintControl(PaintEvent e) {
Color color = new COlor(e.display, cColor) i
e.gc.setBackground(color) i
Point size = getSize ();
e.gc. fillRectangle (O , O, size.x, size.y);
color.dispose{) ;
prvate static Random rand = new Random()
private static RGB newCol or() {
return new RGB (rand.nextlnt (255) ,
rand.nextlnt(2ss), rand.nextlnt(2sS));
prvate i n t pause;
private RGB cColor = newColor () ;
public CBox{Compost e parent, int pause) {
super {parent, SWT.NONE) i
this.pause = pause;
addPaintListener(new cBoxpaintListener{)
)
public void run( ) {
try {
whi l e{!Thread. interrupted (
cColor = newColor () ;
getDisplay() .asyncExec(new Runnable()
pUblic void run() {
)
try { redraw (); ) catch (SWTException e) ()
II SWTException e,s OK cuando el padre es
II terminado desde debajo de nosotros.
J) ;
TimeUnit.MILLISECONDS.sleep(pause) ;
catch{Int erruptedException e)
II Forma aceptable de salir
catch(SWTExcepton e) {
II Forma aceptable de salir: nuestro padre
II ha sido terminado desde debajo de nosotros.
public class ColorBoxes implements SWTApplication {
private int grid = 12;
prvate int pause = SO;
public void parent) {
GridLayout gridLay_out = new GridLayout (grid, true) ;
gr i dLayout.horizontalSpacing = O
gridLayout. vertical Spacing = O i
parent.setLayout(gridLayout) ;
ExecutorService exec = new DaemonThreadPool 'Executor()
for(nt i = O; i < (grid * grid) i++) {
}
final CBox cb new CBox(parent, pause)
cb. setLayoutData (new GridData (GrdData. PIL,L_BOTH) ) i
exec. execute (e_b) ;
public static void main(string (] args)
ColorBoxes ,boxes = new ColorBoxes () ;
if{args. length > O)
boxes.grid = new Integer(args[Q]);
if(args.length > 1)
22 Interfaces grficas de usuario 951
952 Piensa en Java
boxes. pa u s e = new Integer( args [ l ]) ;
SWTCOnsole. r un (boxes, 5 00, 4 00 } ;
}
// / , -
Como en el ejempl o anterior, el dibujo se controla creando un escucha PaintListener con un mtodo paintControl( ) que
se invoca cuando la hebra SWT est lista para pintar el componente. El escucha PaintListener se registra en el constructor
de CBox.
Lo que difiere notablemente en esta versin de CBox es el mtodo run(), que no puede limitarse a invocar redraw() direc-
tamente, sino que tiene que enviar la llamada a redraw() al mtodo asyncExec() del objeto Display, que equivale aproxi-
madamente al mtodo SwingUtillties.invokeLater( ). Si sustituimos estos por una llamada directa a redraw( ),
comprobaremos que el programa simplemente se detiene.
Al ejecutar el programa, veremos pequeos artefactos visuales: lneas horizontales que atraviesan ocasionalmente un recua-
dro. Esto es debido a que SWT no utiliza doble buffer de manera predetenninada, mientras que Swing si lo utiliza. Trate de
ejecutar la versin Swing al lado de la versin SWT y podr observar la diferencia ms claramente. Podemos escribir cdi-
go para utili zar un doble buffer SWT; podr encontrar ejemplos en el sitio web www.eclipse.org.
Ejercicio 42: (4) Modifique swt!ColorBoxes.java para que comience distribuyendo una serie de puntos ("estrellas") por
todo el lienzo y luego cambie aleatoriamente el color de esas "estrellas".
SWT O Swing?
Resulta dificil hacerse una idea general a partir de una introduccin tan corta, pero el lector debera al menos comenzar a
percibir que SWT puede ser, en muchas situaciones, una fonna ms sencilla de escribir programas que Swing. Sin embar-
go, la programacin de GUI en SWT puede seguir siendo compleja, por lo que los motivos para utilizar SWT deberan ser,
en primer lugar, proporcionar al usuario una experiencia ms transparente a la hora de usar la aplicacin (porque el aspec-
to y estilo de la aplicacin se asemejarn a los de otras aplicaciones en dicha plataforma) y segundo, si la mayor velocidad
proporcionada por SWT resulta importante. En caso contrario, Swing puede ser una eleccin perfectamente apropiada.
Ejercicio 43: (6) Seleccione alguno de los ejemplos Swing que no haya sido traducido en esta seccin y lradzcalo a
Resumen
SWT. (Nota: esto puede constituir un buen ejercicio para que los estudiantes realicen en casa, ya que las
soluciones no se han incluido en la gua de soluciones).
Las bibli otecas GUI de Java han experimentado cambios bastante drsticos a lo largo del tiempo de existencia del lengua-
je. La biblioteca AWT de Java 1.0 fue muy criticada por su pobre diselo, y aunque permita crear programas portables, la
GUI resultante era "igualmente mediocre en todas las pl ataformas". Tambin era bastante limitadora, abstrusa e incmoda
de emplear comparada con las herramientas de desarrollo nativas disponibles en diversas plataformas.
Cuando Java 1.1 introdujo el nuevo modelo de sucesos y los componentes JavaBeans, el escenario completo ya estaba pre-
parado: allOr. era posible crear componentes GUl que se podan arrastrar y colocar fcilmente dentro de un entorno IDE
visual. Adems, el di seo del modelo de sucesos y de JavaBeans muestra claramente que los objetivos eran la facilidad de
programacin y la utilizacin de cdigo fcilmente mantenibl e (algo que no era evidente en la bibliotecaAWT de la versin
1.0). Pero la transicin no se concret hasta que aparecieron las clases JFC/ Swing. Con los componentes Swing, la progra-
macin de interfaces GUI multiplataforma puede resultar sencilla.
La verdadera revolucin radica en el uso de entornos IDE. Si desea adquirir un entorno IDE comercial para mejorar la pro-
gramacin con un lenguaje propietario, est obligado a cruzar los dedos y a esperar que el fabricante le proporcione lo que
usted espera. Pero Java es un entorno abierto, por lo que no slo permite que existan entornos IDE competidores, sino que
fomenta esa existencia. Y para que estas herramientas puedan tomarse en serio, estn obligadas a soportar JavaBeans. Esto
significa que el campo de juegos est nivelado para todos los contendientes; si aparece un entorno !DE mejor, no eslamos
obligados a continuar empleando el anterior. Podemos seleccionar el nuevo e incrementar con ello nuestra productividad.
Este tipo de campo de juego competitivo para entornos !DE de creacin de interfaces GUI no haba sido experimentado
antes, y el mercado resultante permitir generar resultados muy positivos en lo que respecta a la productividad de los pro-
gramadores.
22 Interfaces grficas de usuario 953
Este captulo ha pretendido nicamente proporcionar una introduccin a la potencia de la programacin de interfaces GUr,
y hacer que el lector comenzara a familiarizarse con material de programacin de interfaces para ver lo simple que resulta
emplear las correspondientes bibliotecas. Lo que hemos visto en el captulo bastar, probablemente, para cubrir una parte
de nuestras necesidades de diseo de interfaces de usuario. Sin embargo, tanto Swing como SWT y FlashIFlex tienen
muchas otras caractersticas y funcionalidades adicionales, ya que se trata de herramientas de diseo de interfaces de usua-
rio completas. Con ellas, probablemente encuentre una forma de realizar cualquier cosa que sea capaz de imaginar.
Recursos
Las presentaciones en lnea de Ben Galbraith disponibles en www.galbraiths.org/presentations hacen un repaso muyade-
cuado tanto de Swing como de SWT.
Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico Jne Thinking in Java Annotated Solution Guide, disponible para
la venta en www.MindView.net.
Suplementos
Este libro tiene varios suplementos, incluyendo los elementos, seminarios y servicios
disponibles en el sitio web MindView.
Este apndice describe estos suplementos, para que pueda decidir si le pueden ser tiles.
Observe que aunque los seminarios suelen ser pblicos, t m b i ~ n pueden impartirse ,como seminarios privados en sus pro,...
piasficinas.
Suplementos descargables
El cdigo correspondiente a este libro est disponible para su descarga en www.MindView.net. Esto inclnye los archivos de
construccinAnt y otros archivos .de soporte necesarios para construir y ejecutar correctamente todos los ejemplos del libro.
Adems, algunas partes del libro slo se ofrecen en formato electrnico. Los temas que estas partes abarcan:
Clonacin de objetos
Paso y devolucin de objetos
Anlisis y diseo
Partes de otros captulos procedentes de la 3
a
edicin de Thinking in Java, que no eran lo suficientemente rele-
vantes como para incluirlos en la versin impresa de la cuarta edicin .dellibro.
Thinking in C: fundamentos para Java
En www.MindView.net. podr descargarse gratuitamente el seminario Thinking in C. Esta presentacin,creadaporChuck
Allison y desarrollada por MindView, es un curso Flash multimedia que proporciona una introduccin a la sintaxis, los ope-
radores y las funciones de C en las que se basa la sintaxis de Java.
Observe que es necesario instalar en nuestro sistema el reproductor Flash PIayer de www.Macromedia.com para poder ver
la presentacin Thinking in C.
Seminario Thinking.in Java
Mi.empresa, MindView, Inc., proporciona seminarios de formacin pblicos o privados de carcter prctico y de. cinco .das
de duracin basados en el material de este libro. Anteriormente denominado seminario lfands-On Java, se trata .de nuestro
principal seminario introductorio, que proporciona la base para seminarios ms avanzados. Una .serie de materiales seleC-
cionados de cada captulo repr-esentan una leccin, que va seguida por un perodo de ejercicios monitorizado, de modo qlle
cada-estudiante recibe ,una atencin -personalizada. Puede .encontrarinformacin sobre horarios y lugares donde .se :imparte
este seminario, junto con testimonios y otros detalles en www.MindView.net.
956 Piensa en Java
Seminario en CD Hands-On Java
El CD Hands-On Java contiene una versin ampliada del material del seminario Thinking in Java y est basado en este libro.
Proporciona al menos una parte de la experiencia del seminario real, pero sin los gastos de viaje asociados. Existe lUla con-
ferencia audio y una serie de presentaciones correspondientes a cada uno de los captulos del libro. Yo he creado personal-
mente dicho seminario y me he encargado de narrar el material contenido en el CD. El material est en fonnato Flash, por
lo que debera poder ejecutarse en cualquier platafonna que soporte Flash Playero El CD de Hands-On Java CD puede adqui-
rirse a travs de www.MindView.net. donde encontrar demostraciones de prueba del producto.
Seminario Thinking in Objects
Este seminario introduce las ideas de la programacin orientada a objetos desde el punto de vista del diseador. Explora el
proceso de desarrollo y construccin de un sistema, centrndose principalmente en los denominados "Mtodos giles" o
"Metodologas ligeras", y en especial en XP (Extreme Programming). En el seminario introduje las metodologas en gene-
ral, lUlas pequeas herramientas como las tcnicas de planificacin mediante "tarjetas ndices" descritas en Planning
Extreme Programming por Beck y Fowler (Addison-Wesley, 2001), las tarjetas CRC para disear objetos, la programacin
por pares, la planificacin de iteraciones, las pruebas unitarias, la construccin automatizada, el control de cdigo fuente y
temas similares. El curso incluye un proyecto XP que se desarrolla a lo largo de lUla semana.
Si est iniciando un proyecto y desea comenzar a utilizar tcnicas de diseo orientadas a objetos, podemos emplear su pro-
yecto como ejemplo y disponer de un primer prototipo de diseo al final de la semana.
Visite "WWw.MindView.net para obtener la infonnacin sobre horarios y lugares donde se imparte este seminario, as como
testimonios y otros detalles
Thinking in Enterprise Java
Este libro se emplea a partir de algunos de los captulos ms avanzados de las ediciones anteriores en Thinking in Java. Este
libro no es un segundo volumen de Thinking in Java, sino que se centra ms bien en cubrir los temas avanzados de la pro-
gramacin empresarial. Est disponible (aunque todava en desarrollo) en forma de descarga gratuita en www.MindVew.net.
Pero como es un libro separado, debe expandirse para cubrir los temas necesarios. El objetivo, como el de Thinking in Java,
es proporcionar una introduccin comprensible a los fundamentos de las tecnologas de programacin empresarial, para que
el lector est preparado para acometer un estudio basado en dichos temas.
La lista de temas que incluye, entre otros, es la siguiente:
Introduccin a la programacin empresarial
Programacin de red con Sockets y canales
Invocacin remota de mtodos (RMI)
Conexin a bases de datos
Servicios de denominacin y directorio
Servlets
Java Server Pages
Marcadores, fragmentos JSP y lenguaje de expresin
Automatizacin de la creacin de interfaces de usuario
Enterprise JavaBeans
XML
Servicios Web
Pruebas automticas
A Suplementos 957
Puede encontrar infonnacin sobre el estado actual (en ingls) de Thinking in Enterprise Java en www.MindView.net.
Thinking in Patterns (con Java)
Uno de los pasos adelante ms importantes dentro del diseo orientado a objeto es el movimiento de los "patrones de dise-
o", que se tratan en Design Patterns, de Gannna, Helm, Johnson & Vlissides (Addison-Wesley, 1995), Dicho libro mues-
tra 23 clases generales de problemas junto con sus soluciones, escritos principalmente en C++, El libro Design Patterns es
una fuente autorizada de lo que ahora se ha convertido en un vocabulario esencial, si es que no obligatorio, para la progra-
macin orientada a objetos. Thinking in Patterns introduce los conceptos bsicos de los patrones de diseo junto con una
serie de ejemplos en Java. El libro no pretende ser una simple traduccin de Design Patterns, sino ms bien una nueva pers-
pectiva desde el punto de vista de Java. No est limitado a los 23 patrones tradicionales, sino que incluye tambin otras ideas
y tcnicas de resolucin de problemas.
Este libro comenz con el ltimo captulo de la primera edicin de Thinking in Java, y a medida que las ideas continuaron
desarrollndose, quedo claro que era necesario incluir ese material en su propio libro independiente. En el momento de escri-
bir estas lneas, el libro se encuentra todava en desarrollo, pero el material ya est muy avanzado y ha sido utilizado en
numerosas presentaciones del seminario Objects & Patterns (que ahora se ha dividido en los seminarios Designing Objects
& Syslems y Thinking in Patterns).
Puede encontrar ms informacin acerca de este libro en wyvw.MindView.net.
Seminario Thinking in Patterns
Este seminario ha evolucionado a partir del seminario Objecls & Pallems que Bil! Venners y yo mismo hemos impartido en
los ltimos aos. Dicho seminario lleg a estar demasiado cargado de contenido, por 10 que lo dividimos en dos seminarios
independientes: ste y el seminario Designing Objects & Systems, descrito anterionnente.
El seminario se ajusta de manera bastante fiel al material y a la presentacin del libro Thinking in Patterns, por lo que la
mejor fonna de conocer los detalles sobre el seminario es consultar la informacin relativa al libro en wyvw.MindView.net.
Buena parte de la presentacin que se centra en el proceso de evolucin del diseo, comenzando por una solucin inicial y
repasando la lgica y el proceso que subyace a la evolucin de esa solucin hacia diseos ms apropiados. El ltimo pro-
yecto mostrado (una simulacin de reciclado de residuos) ha ido evolucionando a lo largo del tiempo, y se puede examinar
esa evolucin como un prototipo de la manera en que un diseo real puede comenzar en fonna de una solucin adecuada a
un problema concreto, para tenninar evolucionando hasta convertirse en una solucin flexible para toda una clase de pro-
blemas.
Este seminario le ayudar a:
Aumentar enormemente la flexibilidad de sus diseos.
Incorporar a los diseos los conceptos de ampliabilidad y reusabilidad.
Crear mecanismos de comunicacin ms densos acerca de los diseos utilizando el lenguaje de patrones.
Despus de cada presentacin, hay un conjunto de ejercicios sobre patrones de diseo que los asistentes deben
resolver, siendo dirigidos durante la escritura del cdigo para poder aplicar patrones concretos a la solucin de
los problemas de programacin.
Visite wyvw.MindView.net para obtener ms informacin acerca de los horarios y los lugares donde se imparte este semina-
rio, junto con testimonios y detalles adicionales.
Consultora y revisin de diseo
Mi empresa tambin proporciona servicios de consultora, de tutora, de revisin de diseo y revisin de implementacin
como ayuda durante el ciclo de desarrollo de un proyecto, incluyendo los primeros proyectos Java de cualquier empresa.
Visite wwv.MindView.nel para obtener ms infonnacin sobre disponibilidad y otros detalles.
Recursos
Software
El JDK disponible ert http://java.slln.com. Incluso si decide emplear un entorno de desarrollo de otro fabricante, siempre es
convertiente tener el JDK a mano, por si acaso se tropieza con algn posible error del complador. El JDK es la piedra de
toque del diseo Java, y si existe un error en l, hay grandes posibilidades de que dicho error est bieu documentado.
La documentacin del JDK disponible en http://javacSun.com, en fonnato HTML. No he podido encontrar todava un libro
de referencia sobre las bibliotecas estndar de Java que no estuviera desactualizado o en el que no fa ltara infonuacin.
Aunque la documentacin del JDK de Sun tiene algunos pequeos errOres y es excesivamente concisa en algunos puntos, a
menos incluye todas las clases y mtodos. En ocasiones, algunas personas no se sienten cmodas utilizando inicialmente un
recurso en lnea en lugar de un libro en papel, pero merece la pena superar eSa incomodidad inicial y consultar los docu-
mentos HTML, al menos para obtener una panormica general. Si le sigue sin gustar el mtodo de las referencias ert lnea,
adquiera los libros impresos.
Editores y entornos lOE
Existe una sana competicin dentro de este rea. Muchos de los productos son gratuitos (y los que no lo son, normalmente
disponen de versiones de prueba gratuitas), por lo que lo mejor es probar los distintos productos y ver cul se adapta mejor
a sus necesidades. He aqu algunos de ellos:
JEdit, es un editor gratuito diseado por Slava Peslov, escrito en Java, con lo que obtenemos la ventaja adicional de poder
ver eu accin no. aplicacin de escrtorio desarrollada Java. Este editor est basado fuertemente en la utilizacin de plg
ins, muchos de los cuales han sido escritos por la comnoidad Java. Puede descargarlo en wwwjedit.org.
NetBeans, un entorno IDE gratuito de Sun, disponible en www. neibeans.OIg. Diseado para la construccin de interfaces
GUI mediante el procedimiento de arrastrar y colocar, para la creacin, edicin y depuracin de cdigo,etc.
Eclipse, un proyecto de cdigo abierto respaldado por lBM, entre otros. La plataforma Eclipse tambin est diseada para
constituir no. base amplable de desarrollo, de modo que se pneden construir aplicaciones autnomas sobre Eclipse. Este
proyecto desarroll el lenguaje SWT descrto en el Captulo 22, Interfaces grficas de usuario. Puede descargarlo en
www.Eclipse.otg.
IntelliJ IDEA, el entorno comercial favorito de no gran nmero de programadores Java,. muchos de los cuales afirman que
IDEA siempre est a noo o dos pasos de Eclpse, posiblemente porque IntelliJ no trata tanto de Crear un entorno !DE como
una plataforma de desarrollo, sino' que se limita a los aspectos del entorno IDE. Puede descargar una versin gratuita en
lIV\11Wjetbrains.com.
Libros
Core Java 2, 7'h Edit;on, Volmenes I & JI, de Horstmann & Comell (prentice Hall, 2005). Voluminoso, completo y es
donde debe buscar en primer lugar cuando est buscando respuestas. Recomiendo la lectura de este libro una vez que se
haya completado Thinking in JOVd y se necesite comenzar a profundizar en los distintos temas.
The Java C/ass Libraries: An Annotated Refermee, por Patrick Chan and Rosatula Lee (Addison-Wesley, 1.997J
Aunque est desactualizado, este Ibro es lo que la referencia JDK deberfa haber sido; incluye las suficientes descripciones
960 Piensa en Java
como para hacerlo utilizable. Uno de los revisores tcnicos de Thinking in Java me dijo: "Si tuviera un nico libro sobre
Java, seria ste (adems del tuyo, por supuesto)." Yo no estoy tan entusiasmo con este libro, como dicho revisor. Es volu-
minoso, resulta caro y la calidad de los ejemplos no me parece adecuada. Sin embargo, es tul buen lugar en el que consul-
tar cuando estemos bloqueados con un problema y aborda los temas con una mayor profundidad que la mayora de los dems
libros. Sin embargo, Core Java 2 tiene un tratamiento ms actualizado de muchos de los componentes de la biblioteca.
Java Network Programmillg, 2" Edicin, por Elliolte Rusty Harold (O'Reilly, 2000). Personahnente, no empec a compren-
der los aspectos de las redes en Java (ni, en realidad, los aspectos de comunicacin por red, en general) hasta que encontr
este libro. Tambin me parece que su sitio web, Caf au Lait, es muy estimulante, infonnativo y actualizado en lo que res-
pecta a los desarrollos Java, siendo muy independiente respecto a los distintos fabricantes de software. Las actualizaciones
regulares hacen que el sitio web est lleno de noticias acerca de la evolucin de Java. Consulte www.cafeaulait.org.
Design Pallerns, por Gamma, Helm, Johnson y Vlissides (Addison-Wesley, 1995). El libro originaJ que dio comienzo al
movimiento de patrones de diselo dentro del campo de la programacin y que hemos mencionado en numerosos lugares a
lo largo del libro.
Refactoring to Pallems, por Joshua Kerievsky (Addison-Wesley, 2005). Combina el tema de la reingeniera con el de los
patrones de diseo. Lo ms valioso de este libro es que muestra cmo hacer evolucionar un diseo introduciendo nuevos
patrones a medida que son necesarios.
The Art of UNIX Programming, por Eric Raymond (Addison-Wesley, 2004). Aunque Java es un lenguaje interplataforma,
la prevalencia de Java en el mundo de los servidores ha hecho que el conocimiento de Unix/Linux sea importante. El libro
de Eric constituye una excelente introduccin a la historia y filosofa de este sistema operativo, y resulta fascinante de leer
aunque slo se quieran comprender algunos aspectos de los orgenes de la informtica.
Anlisis y diseo
Extreme Programming Explained, 2"d Edition, por Kent Beck con Cynthia Andres. (Addison-Wesley, 2005). Siempre he
pensado que debera haber un proceso de desarrollo de programas muy distinto y mucho mejor que el que se emplea actual-
mente, y creo que XP se acerca bastante a ese ideal. El nico libro que me ha causado un impacto similar es Peopleware
(del que hablar ms adelante), que habla principalmente acerca del entorno y de cmo tratar con la cultura corporativa.
Ex/reme Programming Explained habla acerca de la programacin y proporciona una nueva visin sobre los principales
aspectos de esta ciencia. Los autores llegan incluso a decir que las imgenes estn bien siempre que no invirtamos demasia-
do tiempo en ellas y estemos dispuestos a prescindir de ellas en caso necesario (observar que el libro no tiene la marca de
aprobacin "UML" en la cubierta). En mi opinin podra decidirse si trabajar para una empresa basndose exclusivamente
en si utilizan XP. Es un libro pequeo, de captulos cortos, que se leen sin esfuerzo y que resuJta excitante. Uno puede ima-
ginarse a s mismo en ese tipo de atmsfera y le asaltan visiones de un nuevo mundo que se abre a sus pies.
UML Distilled, 2" Edicin, por Martin Fowler (Addison-Wesley, 2000). Cuando uno tropieza por primera vez con UML,
resulta aterrador, dada la gran cantidad de diagramas y de detalles que existen. De acuerdo con Fowler, la mayor parte de
estos detalles son innecesarios, por lo que l se centra en los aspectos esenciales. Para la mayora de los proyectos, basta con
conocer unas pocas herramientas de realizacin de diagramas, y el objetivo de Fowler es conseguir un buen diseo, en lugar
de preocuparse acerca de todos los . aditamentos que hacen falta para conseguirlo. De hecho, la mayor parte de los lectores
no necesitarn la prmera mitad del libro. Es un libro muy atractivo, de pequelo tamao y mi recomendacin es que lo
adquiera si desea comprender UML.
Domain-Driven Design, por Eric Evans (Addison-Wesley, 2004). Este libro se centra en el modelo de dominios como prin-
cipal herramienta del proceso de diselo. En mi opinin, se trata de un desplazamiento interesante del foco de atencin que
ayuda a los diseadores a adoptar el nivel de abstraccin adecuado.
The Unified Software Development Process, por Ivar Jacobsen, Grady Booch y James Rumbaugh (Addison-Wesley, 1999).
Cuando aborde la lectura de este libro, estaba preparado para que no me gustara. Parecia tener todos los ingredientes de un
aburrido libro universitaro. Sin embargo, me vi gratamente sorprendido (aunque hay algunas partes que incluyen explica-
ciones que dan la sensacin de que los autores no tienen los conceptos claros). El conjunto del libro no slo es muy claro,
sino tambin muy agradable de leer. Lo mejor de todo es que el proceso descrito tiene bastante sentido prctico. No se trata
de Extreme Programming (y no tiene la claridad que esta otra tcnica presenta en lo que respecta a las pruebas), pero tam-
bin forma parte del arsenal del mundo UML; incluso aquellos que no desean utilizar XP suelen estar de acuerdo en que
"UML es un buen lenguaj e de modelado" (independientemente del nivel experiencia real que tengan los que emiten estas
2 Todo es un objeto 961
opiniones). Este libro constituye una verdadera referencia de UML y es lo que yo recomendara, para conocer ms detalles
despus de leer UML Distilled de Fowler.
Antes de elegir ningn mtodo concreto, resulta til ganar cierta perspectiva, aprendindola de aquellos que no tratan de
vendernos ninguna en concreto. Es fcil adoptar un mtodo sin llegar a entender realmente qu es lo que queremos obtener
de l o qu es lo que nos puede proporcionar. Que otras personas usen este mtodo, parece una razn suficiente. Sin embar-
go, los seres humanos tienen una tendencia psicolgica muy curiosa. Si quieren creer que algo resolver sus problemas, tra-
tarn de usarlo (esto es experimentacin, lo cual es algo bueno). Pero si no resuelve sus problemas, puede que redoble sus
esfuerzos y comience a anunciar a grandes voces esa cosa tan increble que han descubierto (esto es negacin de la reali-
dad, que no es tan bueno). El mecanismo que subyace a este comportamiento es que si podemos conseguir que otras perso-
nas se suban al mismo barco, al menos no estaremos solos, incluso aunque el barco no vaya a ninguna parte (o se est
hundiendo).
Con esto no quiero sugerir que todas las metodologas no vayan a ninguna parte, sino slo que debemos utilizar las herra-
mientas mentales que nos permitan pennanecer en modo de experimentacin ("No est funcionando, probemos alguna otra
cosa"), sin entrar en el modo de negacin ("No, no se trata realmente de un problema. Como todo es maravilloso, no nece-
sitamos cambiarlo"). En mi opinin, los sigui entes libros que hay que leer antes de elegir un mtodo, le proporcionarn esas
herramientas fundamentales.
Software Crea/ivity, por Robert L. Glass (Prentice Hall, 1995). ste es el mejor libro que yo he visto en donde se analiza la
perspectiva de todo el tema de las metodologas. Es una coleccin de ensayos cortos y artculos que Glass ha escrito y en
ocasiones adquirido (P.J. Plauger es uno de los contribuidores), en donde se refleja sus muchos aos de reflexin y estudio
sobre el tema. Son bastante entretenidos y su longitud es estrictamente la adecuada para transmitir toda la informacin nece-
saria; el autor no divaga ni aburre. Tampoco se dedica a vender humo: hay cientos de referencias a otros artculos y estu-
dios. Todos los programadores y gestores deberan leer este libro antes de entrar en el tema de las metodologas.
Software Runaways: MOllltlllen/al Software Disas/ers, por Robert L. Glass (Prentice Hall, 1998). Lo mejor acerca de este
libro es que pone de manifiesto todas esas cosas de las que a nadie le gusta hablar: la cantidad de proyectos que no slo
fallan, sino que lo hacen de manera espectacular. La mayora de la gente tiende a pensar: "Eso no me puede pasar a m" (o
"Eso no me puede pasar de nuevo"), yeso hace que juguemos con desventaja. Recordando que las cosas siempre pueden ir
mal, estaremos en una posicin mucho mejor para hacer que vayan bien.
Peopleware, 2' Edicin, por Tom DeMarco y Timothy Lister (Dorset House, 1999). Tiene que leer este libro. No slo es
divertido, sino que conmueve todos los cimientos de nuestro esquema mental y destruye nuestras suposiciones previas.
Aunque DeMarco y Lister provienen del campo de desarrollo de software, este libro trata de proyectos y equipos de traba-
jo en general. El libro se centra en las personas y en sus necesidades, ms que en la tecnologa y en las necesidades de la
tecnologa. Hablan acerca de crear un entorno en el que las personas estn contentas y sean productivas, en lugar de deci-
dir las reglas que tienen que seguir para ser componentes adecuados de una mquina. Esta ltima aptitud, en mi opinin, es
la que ms contribuye a que los programadores se sonrian y se burlen cuando se adopta el mtodo XYZ, despus de lo cual
continan haciendo en silencio lo que siempre han estado haciendo.
Secre/s of Consulting: A Guide /0 Giving & Gelling Advice Sltccessfully, por Gerald M. Weinberg (Dorset House, 1985).
Este libro maravilloso es uno de mis favoritos. Resulta perfecto cuando alguien quiere dedicarse a la consultora, o si ha
contratado los servicios de algn consultor y desea mejorar las cosas. Est compuesto de cortos captulos, llenos de histo-
rias y ancdotas que nos ensean cmo llegar hasta el fondo del asunto con un esfuerzo mnimo. Lea tambin More Secrets
01 Consulting, publicado en 2002, o cualquier otro de los libros de Weinberg.
Complexity, por M. Mitchell Waldrop (Simon & Schuster, 1992). Este libro es la crnica de la reunin celebrada en Santa
Fe, Nuevo Mxico, por un grupo de cientficos de diferentes disciplinas para analizar problemas reales que sus disciplinas
individuales no pueden resolver .( el mercado burstil en Economa, la formacin inicial de la visa en Biologa, por qu las
personas hacen lo que hacen en Sociologa, etc.). Combinando la Fsica, la Economa, la Qumica, las Matemticas, la
Infonntica, la Sociologa, y otras ciencias, se est desarrollando un enfoque multidisciplinar para tratar de abordar estos
problemas. Pero lo ms importante es que est emergiendo una forma diferente de pensar acerca de estos problemas ultra-
complejos: una fonna que se aparta del detenninismo matemtico y de la ilusin de que se puede escribir una ecuacin que
prediga todos los comportamientos, adoptando en su lugar una aptitud que trata primero de observar y de buscar un patrn,
y luego de emular ese patrn utilizando cualquier medio posible (en el libro se narra, por ejemplo, la aparicin de los algo-
ritmos genticos). Este tipo de pensamiento, en mi opinin, resulta til para ver formas de gestionar proyectos de software
cada vez ms complejos.
"962 Piensa en Java
python
Leaming PytllOn, 2" Edicin, por Mark Lutz y David Ascher (O' Reilly, 2003). Una buena introduccin a mi lenguaje favo-
ritoque constituye un excelente complemento a Java. El libro incluye una introduccin a Jython, que permite combinar Java
y Python en un nico programa (el intrprete Jython compila los programas para generar cdigo intermedio Java puro, por
lo que no necesitamos aadir nada especial para conseguir esa combinacin). Esta unin de lenguajes parece tener .grandes
posibilidades.
Mi propia lista de libros
No todos estos libros estn actualmente disponibles, pero algunos de ellos pueden encontrarse en la libreras de segunda
mano.
Computer [nterfacing wit/ Pascal & C (publicado por Eisys, 1988. Disponibl e a la venta nicamente en
www.MindView. net). Una introduccin a la electrnica escrita en los aos en que CPIM segua siendo el rey y DOS no pasa-
ba de ser un advenedizo. Por aquel entonces, yo usaba lenguajes de alto nivel y a menudo el puerto paralelo de la compu-
tadora para controlar diversos proyectos electrnicos. Adaptado a partir de las colurnnasque yo escriba en la primera y
mejor revista de aquellas en las que he contribuido, Micro Cornucopia. Esta revista, llamada tambin Micro C cerr mucho
,antes de{jue Internet apareciera. La creacin de este libro fue una 'experiencia editorial extremadamente satisfactoria,
Using 'C++ (OsbomelMcGraw-Hi11, 1989). Uno de los primeros libros sobre C++. Est agotado y ha sido sustituido por su
segunda edicin, cuyo ttulo es C++ [nside & Oul.
C++ [nside & Out (OsbomelMcGraw-Hi11, 1993). Como ya digo, es la segunda edicin de Using C++. El lenguaje CH de
este libro es razonablemente preciso, pero fue escrito en tomo a 1992y posteriormente fue s ustituido por Thinking in C++,
Puede encontrar ms detalles acerca de este libro y descargar el cdigo fuente en www.MindView.net.
Thinking inC++, 1" Edicin (Prentice Hall, 1995). Consigui el premio Jolt de la revista Software Development Magazine
como mejor Iibm del ao,
Thinking in C++, 2" Edicin, Volumen 1 (Prentice Hall, 2000). Puede descargarlo de www.MindView.net.Actualizadopara
adaptarlo al estndar del lenguaje recin finalizado.
T/inking in C++, 2" Edicin, Volumen 2. Escrito en colaboracin con ChuckAllison (Prentice HalI, 2003). Puede descar-
garlo en www.MindView.net.
Black Belt C++: T/e Master's Colleetion, Bruce Eckel,editor (M&T Books, 1994). Agotado. Una coleccin de captulos
escntos por diversos expertos en C++ y basados en sus presentaciones dentro del clo dedicado a e++ en la conferencia
Software Development Conference,de la cual fui motlerador.La cubierta de este libro fue la que me anim :a decidir todos
los ,futuros diseos de cubierta,
T/illking in Java, 1" Edicin (prentice HaH, 1998). La primera edicin de este hbro gan ,elpremia .a la productividad de
la 'levista Software Developmenl Magazine, el premio del editor de la revista Java Developer s Journal y el premio de los
lectores de la re"ista Java World como mejor libro, Puede ,descargarlo en www.MindView.net.
Tlrinking in Java, 2" Edicin :(Prentice Hall, 2000). Esta edicin gan el premio del editor de la revista JavaWorldcomo
mejor libro. Puede descargarse en www.MindView.net.
TlJiliking in Java, 3" Edicin, (Prentice Hall, 2003). Esta edicin gan el premio 10lt de la revista Softw""e Development
Magazine como mejor libro del ao, junto con otros premios que se indican en la contraportada. Puede descargarlo de
)1lW;w.MindView.net.
ndice
. 51
49
&
&. 55
&&. 51
55
.NET20
.new, sintaxis' 214
,lhis, sintaxis 2L4
@
@. smbolo para anotaciones' -693
@author' 38
.@Deprecated, anotacin' 693
@deprecated, marcador Javado.c . 39
@docRoot . 38
@inheritDoc . . 38
.@interfaces y extends, palabra-clave- 700
@link' 37
@Overrid.e . 693
@param' 38
@Retention . 694
@return 38
@see ' 37
@since . 38
@SuppressWarnings . 693
@Target . 694
@Test "694
@Test para @Unit . 709
@TestObje.ctCleanup para @Unit . 715
@TestObjectCreatepara @Unit . 713
@throws' 38
@Unit'709
@version . 38
[J, operador de indexacin' 110
A
A55

l ' 55
11 . 51
'S5
+
+ . 48 String. conversin con operador +
44,59,317
<
< 49
, '55
55
49

>
> '49

55
55
A
abstraccin' 1
Abstraet Window Toolkit (AWT) . 857
abstraet, -palabra ,clave . 189
AbstractButton . 876
AbstractSequentialList . 558
AbstractSet 514
8_CCesO:
clases internas y derechos de . 213
control de . 121, 136
control de, violacin con la reflexin'
387
de clase' 134
de paquete' 129
dentro de un directorio a travs del
paquete predeterminado' 130
especificadores de . 5, 121, 128
'acoplamiento:
<le las llamadas a mtodos' . 68
dinmico' 168
dinniico, tardo o en tiempo de ejecu-
cin 165,168
tardo '10,165, 168
temprano' .10
ActionEvent . 894, 926
ActionListener . 865
ActionScript, para Macrome,dia Flex . 913
adaptador, patrn de diseo' 198, 204,
270,402, .472, 474, 515
adaptadores .de ,escucha 873
add( ), ArrayList . 242
addActionListener( ) . 924, 929
addChaogeListener . 897
addListener . 869
Adler32 635
agotar la memoria, solucin mediante
References . 579
agregacin 6
agrupamientos, @Unity JUnit . 717
alias, creacin de . 45
Allisoo, Chuck' 4, 18, .955,962
allocate() . 617
allocateDjrect( ) . 617
mbitos, clases internas en mtodos y .
217
ampliabilidad . 171
ANO: bit a bit 55, 60 lgico (&&). 51
anidamiento ,de interfaces 206
anotaciones 693
apt, herramientas de ,procesamiento de
703
elementos' .694, 697
marcadora 694
procesador basado en la reflexin 700
procesador de . 696
valores predeterminados de los elemen-
tos' 695, 697
.aplicacin de un mtodo a una secuencia
468
ApplicationBuilder 918
apt, herramienta de procesamiento de
anotaciones' 703
archivos:
caractersticas de ' 594
cuadros de dilogo de . 901
,de salida, errores y volcado' 606
File, clase' 587, 597,602
964 Piensa en Java
archivos (continuacin)
File.!ist( ) - 587
JAR - 123 bloqueo de - 632
mapeados en memoria 629
argumentos:
constructor' 86
covariantes . 453
final - 158,589
inferencia del argumento de tipo ' 403
lista de argumentos variable' 113
Amold, Ken - 858
ArrayBlockingQueue - 796
ArrayList - 242, 249, 530
add() - 242
get() - 242
size() - 242
Arrays: .sList() - 245, 272, 530
binarySearch( ) - 508
clase, 502
asCharBuffer() - 619
asignacin' 44
aspecto y estilo seleccionables ' 904
aspect-oriented prograrnming (AOP) - 458
assert y @Unit - 711
atmica, operacin 760
atomicidad en la programacin
concurrente- 754
Atomiclnteger . 764
AtomicLong - 764
AtornicReference . 764
autodecremento, operador' 48
autoincremento, operador 48
available( ) - 605
B
barra de progreso 903
base 16 - 53
base 8 - 53
base,clase 131,142,168
abstracta - 189
constructor de la . 176
interfaz - 171
base. tipos' 7
BASIC, Microsoft Visual BASIC - 918
BasicArrowButton . 877
Bean:
application builder . 918
archivo de manifiesto' 930
Beanlnfo - 931
componente' 91 9
convenio de denominacin' 91 9
empaquetado de . 930
EventSetDescriptors . 922
FeatureDescriptor . 931
getBeanInfo( ) - 920
getEventSetDescriptors( ) - 922
getMethodDescriptors( ) - 922
getName( ) - 922
getPropertyDescriptors( ) - 922
getPropertyType( ) - 922
getReadMethod( ) - 922
getWriteMethod( ) - 922
hoja de propiedades personalizada' 931
Introspector . 920
Method - 922
MethodDescriptors . 922
programacin visual' 918
PropertyChangeEvent - 931
PropertyDescriptors . 922
propi edades - 918
propiedades indexadas' 93 1
ProptertyVetoException . 931
reflexin - 918,920
Serializable - 926
sucesos' 919
Y Delphi de Borland - 918
Y Microsoft Visual BASIC - 918
BeanJnfo- 931
Beck, Kent - 960
biblioteca
creador de, y programador de clientes'
121
diseo de' 121
uso' 121
binarios, impresin de nmeros' 58
binarySearch( ) - 508, 576
bit a bit, operadores 55:
AND operador (&) - 55, 60
EXCLUSNE OR o XOR (Al - 55
NOT - - 55
OR operador (1) - 55, 60
BitSet - 584
Bloch, Joshua - 98, 659, 75 1, 762
BlockingQueue - 796, 809
bloqueo:
contienda 835
de archivos' 632
en concurrencia 756
en programas concurrentes' 729
optimista - 847
Y available( ) - 605
Booch, Grady - 960
Boolean . 70:
lgebra booelana' 55
CyC++-51
operadores que no funcionan con el
tipo boolean - 49
Y proyeccin . 60
borrado - 447
en genricos' 415
botn:
creacin de nuestro propio ' 874
de opcin - 884
Swing - 862, 876
BoxLayout - 868
break etiquetado' 79
break, palabra clave- 77
Budd, Timothy - 2
buffer, nio - 616
BufferedInputStream - 599
BufferedOutputStream - 600
BufferedReader - 304, 602, 603
BufferedWriter - 601, 605
bsqueda:
de archivos de clases durante la carga'
124
en una matriz' 508
Y ordenacin en listas' 575
ButtonGroup - 877, 884
ByteArraylnputStream - 597
ByteArrayOutputStream - 598
ByteBuffer - 616
e
C#: lenguaje de programacin' 20
C++ - 49, 586
plantillas - 394, 417
tratamiento de excepciones . 310
CachedThreadPool - 734
Cadena de responsabilidad, patrn de
diseo - 676
Callable, concurrencia' 736
cambio de contexto' 728
campos, inicializacin en interfaces ' 205
canal, nio' 61 6
canalizacin' 597
canalizaciones para la EIS ' 800
capacidad inicjal de un HashMap o
HashSet - 57 1
.class, archivos' 124
carga:
de clases - 162, 357
inicializacin y carga de clases' 162
cargador de clases' 353
cargador de clases primordial' 353
case, instruccin' 82
CASE_INSENSlTIVE_ORDER, compa-
rador de cadenas' 575, 588
casilla de verificacin' 883
cast() - 361
catch:
palabra clave ' 280
capturar una excepcin ' 286
cena de los filsofos, ejemplo de interblo-
queo - 801
CharArrayReader' 601
CharArrayWriter - 601
CharBuffer - 619
CharSequence - 336
Charset . 620
checkedCollection( ) - 456
CheckedlnputStream - 634
checkedList( ) - 456
checkedMap( ) - 456
CheckedOutputStream - 634
checkedSet( ) - 456
checkedSortedMap( ) - 456
checkedSortedSet( ) . 456
Checksum, clase' 635
Chiba, Shigeru, Dr .. 723, 724
cierre y clases internas' 229
clases' 3
abstractas' 189
acceso de . 134
base 131, 142, 168
carga 162,357
creadores de . 5
datos de la clase' 32
de coleccin 241
dentro de interfaces' 225
diagramas de herencia' 155
estilo de creacin' 133
explorador de . 134
final 161
heredar de clases abstractas' 189
heredar de clases internas' 236
inicializacin' 162,357
inicializacin de campos de . 102
inicializacin de la clase base' 143
inicializacin de la clase derivada' 143
inicializacin de miembros' 140
instanceoflislnstance( ) y equivalencia
de373
instancia de una' 2
literales de . 357, 367
mtodos de la clase' 32
montaje' 357
mltiplemente anidadas . 226
orden de inicializacin' 105
pblicas y unidades de compilacin'
122
subobjeto' 143
tratamiento de excepciones y jerarquas
de . 307
clases internas' 211-240
anidadas 224
anidamiento dentro de un mbito arbi-
trario . 218
annimas' 219, 589, 864
cierre' 229
derechos de acceso' 213
en mtodos y mbitos' 217
genricos' 412
heredar de . 236
identificadores y archivos .class . 239
local217
motivacin' 227
privadas 232, 377
referencia al objeto de la clase externa'
215
referencia oculta al objeto de la clase
contenedora' 214
retrollamada . 229
static . 224
Y generalizacin' 216
Y hebras 745
y marcos de control' 230
Y super 236
Y sustitucin' 236
Y Swing 869
Class 878
Class, objeto 353, 651, 757
forName( ) . 354, 872
getCanonicalName( ) . 356
getClass( ) . 287
getConstructors( ) . 377
getInterfaces( ) . 356
getMethods( ) . 377
getSimpleName( ) . 356
getSuperclass( ) . 356
isAssignableFrom( ) . 369
islnstance( ) . 368
islnterface( ) . 356
lmites y referencias' 360
newlnstance() . 356
proceso de creacin del objeto' 107
referencias de, y comodines' 359
referencias genricas' 359
RTTI con el objeto Class . 353
class, anlisis de archivos' 721
class, palabra clave 3 6,
ClassCastException' 186,362
ClassNotFoundException' 365
classpath . 124
clear(), nio . 618
cliente, programador de 5 y creador de
bibliotecas' 5, 121
close( ) . 604
cdigo:
conducido por tablas' 674
estndares de codificacin' xxxi
estilo de codificacin' 39
fuente' xxx
libre de bloqueos, en programacin
concurrente' 760
organizacin' 128
reutilizacin' 139
cola bidireccional o doble' 255, 539
colas Vase queue
colisin:
de nombres . 126
mecanismos hash . 551
Collection 241,244,266,525
lista de mtodos' 525
rellenar con un generador 406
utilidades' 572
Collections:
addAll( ) . 245
enumeration( ) . 581
fill() . 514
unmodifiableList( ) . 530
coma, operador' 74
comando de accin' 894
comandos, patrn de diseo basado en'
235,384,672,734
ndice 965
comentarios y documentacin embebida'
35
comodines:
de supertipo . 438
en genricos' 434
no limitados' 440
Y referencias Class . 359
Comparable 504, 533, 538
comparacin de matrices' 503
Comparator . 505, 533
compareTo(), enjava.lang.Comparable .
504,535
compatibilidad' 419
compilacin
condicional 128
de un programa Java' 35
unidad de 122
complemento a dos con signo' 58
complemento a uno, operador' 55
componente JavaBean' 919
composicin' 6, 139
Y campo de comportamiento dinmico'
184
Y herencia 147, 152, 155, 539, 582
compresin, biblioteca de . 634
comprobacin:
de limites en matrices' 111
pruebas unitarias basadas en anotacio-
nes con @Unit . 709
tcnicas de . 226
concurrencia:
almacenamiento local de habras . 771
ArrayBlockingQueue . 796
atomicidad' 754
BlockingQueue . 796, 809
Callable . 736
canalizaciones para la E/S entre tareas'
800
cdigo libre de bloqueos 760
condicin de carrera' 755
Condition, objeto' 793
constructores' 745
CountDownLatch . 805
CyclicBarrier . 807
DelayQueue . 809
desgajamiento de palabra' 760
Exchanger . 820
Executor . 734
hebras demonio . 740
hebras y tareas, terminologa' 748
interferencia de tareas' 753
LinkedBlockingQueue . 796
lock explcito' 758
long y double . 760
objetos activos' 850
optimizacin del rendimiento' 834
prioridad' 738
PriorityBlockingQueue . 811
. 791
966 Piensa en Java
concurrencia (continuacin)
Prueba de Goetz para evitar la sincroni-
zacin 760
ReadWriteLock . 848
Regla de Brian de la sincronizacin
757
ScheduJedExecutor . 814
semforo' 817
seales perdidas . 788
sleep( ) . 737
SynchronousQueue . 826
tenninacn de tareas' 772
UncaughtExceptionHaodler . 752
Y contenedores' 577
Y excepciones' 759
Y Swing' 910
ConcurrentHashMap . 542, 841, 845
ConcurrentLinkedQueue . 841
ConcurrentModificationException . 578
uso de CopyOn WriteArrayList para eli-
minar' 841, 852
condicin de carrera, concurrencia' 755
condicional, compilacin' 128
condicional, operador' 58
Condtion, concurrencia 793
conjunto compartido de objetos' 817
consola:
entorno de visualizacin Swmg en
net.mindview.util.SwingConsole .
861
paso de excepciones a la . 312
constante:
de tiempo de compilacin 156
grupos de valores' 205
implcitas y String . 317
constructores' 85:
argumentos' 86
comportamiento de los mtodos poli-
mdicos dentro de' 181
constructor, clase para reflexin' 375
de la clase base' 176
inicializacin de instancia' 220
inicializacin durante la herencia y la
composicin' 147
invocacin de constructores de la clase
base con argumentos' 144
invocacin desde otros constructores'
95
nombre' 86
orden de as llamadas a . 176
predeterminado ' 92
predeterminado srntetizado . 377
sin argumentos .' 86, 92
sobrecarga 87
static, clusula' lOS'
valor de retomo .' 86
Y cIases-internas nnmas . 219
Y concurrencia' 745
Y finally . 303
y polimorfismo' 175
Y tratamiento de excepciones . 302
consultora y formacin proporcionada
por MindView, Ine .. 955
contenedores' 12
comparacin con matriz' 484
impresin de . 247
libres de bloqueos' 841
pruebas de rendimiento' 558
seguros respecto al tipo y genricos
242
continue etiquetado 79
continue, palabra clave' 77
contravarianza y genricos 438
control de acceso' 5, 136
control de procesos' 614
conversin:
de ensanchamiento 61
de estrechamiento 61
de unidades de tiempo' 811
conversin automtica de tipos 261, 402
Y genricos 403, 446
Coplen, Jim' 451
CopyOnWriteArrayList 821, 841
CupyOnWriteArraySet 841
copyright, cdgo fuente xxx
cortocircuitos y operadores lgicos 52
CountDovmLatch, para concurrencia 805
covanante:
argumento 453
matrices' 434
tipo de retomo' 183,371,453
CRC32635
CSS (Cascading Style Sheets) y
Macromedia Flex . 936
cuadro combinado' 885
cuadros de dialogo' 898
con fichas ' 887
de archivos 901
cuantificadores' 335
CyclicBarrier, para concurrencia 807
D
DatagramChannel . 632
Datalnput . 603
DataInputStream . 599, 602, 604
DataOutput . 603
DataOutputStrearu . 600, 602
datos:
fmal . 156
inicializacin de datos-estticos 106
tipos de datos primitivos y uso con ope-
radares 62
decode( ), conjunto de caracteres, . 620
Decorador; patrn de diseo' 461
decremento, operador 48
default, palabra clave en una instruccin
switch' 82
defaultReadObject( ) . 648
defaultWriteObject( ) . 647
DeflaterOutputStream . 634
Delayed' 811
DelayQueue, para concurrencia 809
delegacin' 145,461
Delphi de Borland . 918
DeMarco, Toro' 961
demonio, hebras 740
depuracin de memoria 97, 98, 100
cmo funciona el depurador de
memoria 100
objetos alcanzables' 578
Y limpieza' 148
desacoplamiento por polimorfismo 10,
165
Desarrollo rpido de aplicaciones. Vase
RAD.
desbordamiento y tipos primitivos' 70
descompilador javap ' 318, 389, 422
desgajamiento de palabra 760
desigual, matriz 488
desliz.dor . 903
despacho mltiple:
coIi EnurnMap . 690
Y enumeraciones 684
despacho simple ' 684
desplazamiento a la derecha, operador
( ) . 55
desplazamiento a la izquierda, operador
(<<) . 55
desplazamiento, operadores de . 55
destructor' 91. 98, 296 Java no tiene' 148
diagramas de herenca" 11, 155
dibujo en un JPanel en Swing . 895
diccionario' 244
diferida, inicializacin' 140
Dijkstra, Edsger . 80 I
directorios:
bsqueda y creacin de . 594
listados de . 587
Y paquetes' 128
discplina de gestin de colas 264
diseo' 183
adicin de ms mtodos al < 131
de bibliotecas' 121
Y composicin 183
y errores < 137
Y herencia 183
dispose( ) . 899
disposicin, control de la . 865
divisin' 46
doble despacho' 684
con EnurnMap . 690
documentacin 17
cmnentarios y documentacin embe-
bida 35
double:
valor literal (d 1) D) . 53
Y hbras . 760
do-while . 73
downcasting. Vese especializacin
E
East, BorderLayout . 866
editor, creacin con JTextPane . 882
efecto colateral 44, 49, 92
eficiencia:
y final 161
y matrices' 483
ejecucin de programas del sistema ope-
rativo desde dentro de Java' 614
ejecucin de W1 programa Java' 35
eIse, palabra clave' 71
encadenamiento de excepciones -291, 31'3
encapsulacin . 133, 387
encode( ), conjunto de caracteres' 620
entorno de visualizacin para Swing . 861
Entrada/salida:
available( ) . 605
biblioteca' 587
biblioteca de compresin' 634
bloqueo y available( ) . 605
BufferedlnputStream . 599
BufferedOutputStream . 600
BufferedReader . 304, 602, 603
BufferedWriter . 602, 605
ByteArraylnputStream . 597
ByteArrayOutputStream . 598
canalizacin' 597
canali zaciones para la . 800
caractersticas de archivos . 594
CharArrayReader . 601
CharArrayWriter . 60 I
CheckedInputStrearn . 634
CheckedOutputStream . 634
c1ose() . 604
configuraciones tpicas de' 603
control en la serializacin . 642
creacin y bsqueda de directorios'
594
Datalnput . 603
DatalnputStream . 599, 602, 605
DataOutput . 603
DataOutputStream . 600, 602
DeflaterOutputStream . 634
E/S de red 616
entrada 596
entrada estndar, lectura de la . 613
Externalizable . 643
File ' 597,602
File, clase' 587
Fi le.list( ) . 587
FileDescriptor - 597
FilelnputReader . 603
FileInputStream . 597
FilenameFiHer' 587
FileOutputStream . 598
FileReader . 304, 601
File Writer . 601, 605
FilterOutputStrearn . 598
FilterReader . 602
FilterWriter . 602
flujo de datos canalizado' 609
GZIPlnputStrearn . 634
GZIPOutputStream . 634
InllaterInputStream . 634
InputStream . 596
InputStrearnReader . 600, 601
internacionalizacin . 601
interrumpible 778
LineNumberInputStream . 599
LineNwnberReader . 602
listado de directorios' 587
mark() . 603
mkdirs( ) . 596
new nio . 616
ObjectOutputStream . 639
OutputStream . 596, 598
OutputStrearnWriter . 600, 601
persistencia ligera 639
PipedInputStrearn . 597
PipedOutputStream . 597, 598
PipedReader 601
PipedWriter . 60 1
PrintStream . 600
PrintWriter . 602, 605, 606
PushbackIuputStream . 599
PushbackReader . 602
RandomAccessFi le . 602, 603, 608
read() . 596
readDouble( ) . 608
Reader 596, 600, 601
readExternal( ) . 643
readLine() . 305, 602, 606, 613
readObject( ) . 639
redireccionamiento de la E/S estndar
613
renameTo( ) . 596
reset( ) . 603
salida 596
seek( ) . 602, 608
SequencelnputStrearn . 597, 602
Serializable . 643
setErr(PrintStream) . 614
setIn(InputStream) . 614
setOut(PrintStream) . 614
StreamTokenizer . 602
StringBuffe, . 597
StringBufferlnputStream . 597
StringReader . 601, 604
StringWriter601
System.err . 613
System.in . 613
System.out . 613
transient . 646
!Jnicode . 601
uso bsko, ej.empl'os . 603
write( ) . 596
writeBytes( ) . 607
writeChars( ) . 607
writeDouble( ) . 607
writeExtemal() . 643
writeObject( ) . 639
Writer . 596, 600, 60 I
ZipEntry . 637
ZipInputStrearn . 634
ZipOutputStream . 634
entrySet( ) en Map . 549
enum:
adicin de mtodos . 661
Indice 967
e importaciones estticas' 660
e interfaces' 667
grupos de valores constantes en e y
C++205
mtodos especficos de constante' 673,
688
palabra clave 11 7,659
values() . 659, 663
Y despacho mltiple 684
y herencia' 665
Y mquinas de estado' 680
Y patrn de diseo Cadena de responsa-
bilidad . 676
Y sele'ccin aleatoria . 666
Y switch 662
enumeracin Vese enum
enumerados, tipos' 117
Enumeration . 581
EnurnMap . 672
EnumSet . 410, 585 en lugar de indicado-
res 671
envio de mensajes' 3
equals( ) . 50
condiciones que debe satisfacer ' 547
Y estructuras de datos "as" . 548
Y hashCode( ) . 533, 548, 554
Y HashMap . 547
equivalencia de objetos' 49
equivalente ( ). 49
Erlang, lenguaje 730
errores:
del libro . xxxii
error estndar' 282
infonnacin de . 309
recuperacin' 277
tratamiento de errores mediante excep-
ciones' 277
Y diseo 137
es-corno-un, relacin 9
escucha:
adaptadores de . 873
interfaces 872
Y sucesos 869
espacio de la solucin' 2
espacio de nombres' 122
espacio del problema ' 2
968 Piensa en Java
especializacin ' 155, 18,362
especificacin de la excepcin' 286, 309
especificacin explcita del tipo de argu-
mento' 247, 405
especificadores de acceso' 5, 121, 128
espera activa, concurrencia' 784
estndares de codificacin xxxi
esttico (static)
bloque' 108
comprobacin de tipos' 392
import y enUID . 660
inicializacin de datos . 106
inicializador . 371
synchroruzed . 757
Y comprobacin de tipos dinmicos 528
y fmal' 156
estilo de codificacin' 39
de creacin de clases' l33
estrategia, patrn de disefio basado en-
196,203,474, 494,504,588,593,
676,811
estructural, tipo' 464, 472
es-un, relacin' 9, 153
etiqueta (label) . 78
EventSetDescriptors . 922
excepciones:
capturar una excepcin' 286
comprobadas' 286, 309
condicinexcepcional . 278
constructores' 303
conversin de comprobadas a no com-
probadas' 313
creacin de nuestras propias 281
deteccin de lIDa excepcin 279
directrices de uso 315
encadenamiento de . 291, 313
Error, clase 294
especificacin de . 286, 310
Exception, clase 294
FileNotFoundException . 304
fillInStackTrace( ) . 289
finally . 295
generar' 278
genricos 457
informe de excepciones mediante log-
ger . 284
jerarquas de clases y . 307
localizacin de . 307
no comprobadas . 294
NullPointerException . 294
perdida 300
printStackTrace( ) . 289
problemas de diseo' 304
regeneracin de una excepcin' 289
regin protegida' 279
registro' 283
restricciones 301
RuntimeException . 294
rutina de tratamiento de . 278, 280
terminacin y reanudacin' 281
Throwable . 287
tratamiento de . 277
try, bloque' 280, 297
Y concurrencia ' 759
Y constructores' 302
Y herencia' 30 1, 307
Y la consola 312
Exchanger' 820
exclusin mutua (mutex), concurrencia
756
Executor, concurrencia 734
ExecutorService . 734
explorador de clases 134
expresiones regulares 331
extends'13I , 142, 185
e interfaces ' 202
palabra clave 142
Y @interfaces . 700
extensin con ceros' 55
extensin de signo ' 55
Externalizable . 643
una alternativa a . 647
Extreme Prograrnming (XP) . 960
F
factor de carga de HashMap o HashSet
571
Factoras registradas, variante del patrn
de diseo mtodo de factora' 371
fallo rpido, contenedores de . 578
false' 51
FeatureDescriptor . 93 1
Fibonacci . 401
Field, para reflexin' 375
FIFO (first-in, first out) . 263
File, clase 587
FileChannel . 616
FileDescriptor . 597
FilelnputReader . 603
FileInputStream . 597
FileLock . 632
FilenameFilter . 587
FileNotFounrlException . 304
FileOutputStream . 598
FileReader' 304, 601
FileWriter' 601, 605
fillInStackTrace( ) . 289
FilterInputStream . 597
FiltetutputStream . 598
FilterReader . 602
FilterWriter . 602
final, 192,396
argumentos' 158,
clases' 161
con referencias a objeto' 156
datos' 156
mtodos' 159, 168, 182
palabra clave ' 156
valores fmal en b1anco 158 589
Y eficiencia 161
Y private . 159
Y static . 156
fmalize( ) . 97, 305
fmally' 148, 150, 295
error ' 300
no ejecutar con hebras demonio ' 743
palabra clave' 295
Y constructores . 303
yretum 299
FixedThreadPooI . 734
Flex 932
flip(), nio . 617
floa!: valor literal (F) . 53
FlowLayout . 867
fluj o de dato de E/S' 596
flujo de datos canalizado' 609
for, palabra clave' 73
foreach ' 74, 78, 114, lIS, 127,232,243,
262,267,345,401, 402, 446,659,
677
e Iterable, 269
Y mtodo basado en adaptadores' 270
fonna (shape): ejemplo' 7, 169
Y RTTI 351
fonnat( ) . 324
formato:
anchura' 326
de cadenas de caracteres 324
especificadores de . 326
precisin' 326
Fonnatler . 325
forName( ) . 354, 872
FORTRAN, lenguaj e de programacin
54
Fowler, Martin' 121 ,960
funcin hash perfecta 551
Funcin, objetos de . 474
Future . 737
G
generador ' 399, 406, 494, 515, 666, 681
de propsito general . 407
para rellenar un objeto Collection . 406
generalizacin' 11, 154, 165 Y RTTI .
352 Y clases internas 216
generar una excepcin 278
Generator 412, 446, 471,494, 505 Vase
Generator
genrico curiosamente recurrente 451
@Unitcon' 716
genricos:
borrado' 415, 447
clases internas annimas' 412
comodines 434
comodines de supertipo . 438
comodines no limitados ' 440
contravarianza . 438
curiosamente recurrente' 451
definicin de clase ms simple 256
especificacin explcita del tipo de
argumento para mtodos genricos'
247,405
excepciones' 457
instanceof' 424, 447
introduccin' 242
isInstance( ) . 424
lmites' 418, 431
marcador de tipos' 424
matriz de objetos' 552
mtodos' 403, 515
proyeccin' 447
proyeccin mediante una clase genrica
448
reificacin' 419
sobrecarga' 449
tipos auto limitados . 450
varargs y mtodos genricos' 405
Y contenedores seguros respecto al tipo
242
gestor de invocaciones para proxy
mico' 379
get( ):
ArrayList . 242
HashMap . 261
no hay get( ) para Collection . 525
getBeanInfo( ) . 920
getBytes( ) . 605
getCanonicalName( ) . 356
getChannel() . 617
getClass() . 287, 354
getConstructor( ) . 878
getConstructors( ) . 377
getenv( ) . 269
getEventSetDescriptors( ) . 922
getInterfaces( ) . 356
getMethodDescriptors( ) . 922
getMethods() . 377
getName( ) . 922
getPropertyDescriptors( ) . 922
getPropertyType() . 922
getReadMethod( ) . 922
getSelectedValues( ) . 886
getSimpleName( ) . 356
getState( ) . 894
getSuperclass( ) . 356
getWriteMethod( ) . 922
Glass, Robert 961
Goetz prueba de, para evitar la sincroni-
zacin' 760
Goetz, Brian . 757, 760, 835, 855
gota, no existe en Java' 78
Graphics, clase' 896
GridBagLayout . 868
GridLayout 867,917
Grindstaff, Chris . 941
grupo de hebras' 751
grupos, expresiones regulares 338
GUl (graphical user inlerface) . 231, 857
constructores de interfaces' 858
GZIPlnputStream . 634
GZIPOutputStream . 634
H
Harold, Elliotte Rusty 931, 960
XOM, biblioteca XML . 654
hash, almacenamiento' 545, 550
encadenamiento externo' 551
funcin hash perfecta' 551
Y cdigos hash . 545
hash, funcin ' 550
hashCode( ) . 541, 545, 550, 553
equals( ) . 533
problemas' 553
Y estructuras de datos hash . 548
HashMap . 542, 571, 845, 876
HashSet'258,533,567
Hashtable . 570, 582
hasNext( ), Iterator . 253
hebras:
almacenamiento local de . 771
estados de las' 775
grupo de . 751
intenupt( ) . 776
isD.emon() . 742
notifyAll() . 784
planificador de . 732
prioridad' 738
resume() e interbloqueos' 775
safety, biblioteca estndar Java ' 807
stop( ) e interbloqueos' 776
suspende ), e interbloqueos' 775 .
wait() . 784
Y tareas, terminologa' 748
herencia' 6, 131,139,142,1 65
ampliacin de interfaces mediante la .
201
de clases abstractas' 189
de clases internas' 236
de mltiples implementaciones 228
diagrama de . 11
diagramas de . 155
diseo de sistemas con' 183
inicializacin con' 162
mltiple en Java' 199
pura y extensin' 184
sobrecarga y sustitucin de mtodos'
151
Y cdigo genrico' 393
Y composicin' 147, 152, 155,539,
582
y enum' 665
Y final, 161
Y synchronized . 929
herramienta abstracta de ventanas (AWT)
857
ndice 969
hexadecimal ' 53
hojas de estilo en cascada Vase CSS
Holub, Allen . 851
HTML en componentes Swing . 902
Iconos' 878
IdentityHashMap . 542, 570
if-else, instruccin 71
operador temario' 58
IllegalAccessException . 365
IllegalMonitorStateException . 785
Imagelcon . 878
impl ementacin' 4:
e interfaz' 5, 133, 152, 192,868
ocultacin' 121, 133,216
implements, palabra clave' 192
import, palabra clave' 122
incremento, operador' 48
Y concurrencia 755
indexacin, operador [ ] . 110
indexOf(), String . 377
indicador de fin . 399
indicadores en lugar de EnumSet . 671
inferencia del argwnento de tipo' 403
InflaterInputStream . 634
infonnacin de tipos en tiempo de ejecu-
cin. Vase RTTI
ingeniera de cdigo intennedio . 721
Javassist . 723
inicializacin:
con constructor' 85
con herencia 162
de campos de clases' 102
de constructores durante la herencia y
composicin' 147
de instancia' 140
de instancia no esttica 109
de la clase base' 143
de matrices' 110
de variables de mtodos' 102
diferida 140
orden de inicializacin' 105, 182
statie' 163
static explcita' 108
y carga de clases' 162,357
InputStream . 596
InputStreamReader' 600, 601
instanceof' 367
instanceof dinmico con isInstance( ) .
368
palabra clave' 362
y tipos genricos 447
instancia:
de una clase' 2
inicializacin de . 220
inicializacin de instancia no esttica
109
interbloqueo, en concurrencia 801
97.0 Piensa en Java
:intercambiador Yese Exchanger
interface, palabra clav.e 192
interfaces 189-210:
anidamiento de - 206
clases dentro de . 225
colisiones de nombres al combinar 202
comn - 189
de W1 objeto . 3
e implementacin ' 5, 133, 152 '868
generalizacin a una . 194
inicializacin de campos en las' 20$
Y clases abstractas' .200
Y cdigo genrico' .393
Y enum ' 667
Y factoras ' 208
y herencia' 201
interfaz comn 189
interfaz grfica de usuario (GUI) 231,
intemacionalizacin en la 'biblioteca de
E/S '601
interrupt( ):
concurrencia 776
hebras' 748
Introspector . 920
isAssigmibleFrom( ), mtodo de Class
369
isDaemon( ) . 742
islnstance( ) . 368
Y genricos' 424
islnterface( ) . 356
Iterable' 269,401,517
Y foreach . 269
Y matrices' 269 .
Iteradornulo, patrn d.e ,.diseo' 381
Iterador, patrn de diseo' 214
Iterator . 252, 253, 266
J
hasNext( ) . 252
nextO . 252
Jacobsen, Ivar . 960
JApplet . 866 mens' 890
JAR' 930
archivo' 123
ar.chivos jar y C1asspath . 125
utilidad, 637
Java:
AWT 857
compilacin y ejecucin de un progr.a-
ma'35
Java Foundation Classes(JFC/Swing) .
857
Java Web Star! . 906
JVM353
seguridad de las hebras en la biblioteca
'807
ylenguaje ensaITiblador-.319
JavaBe.an. YaseBean' 918
javac . 35
javadoc' 36
javap, deseompilador' 318, 389, 422
Javassist " 723
JButton . 878
Swing' 862
JCheckBox . 878, 883
JCheckBoxMenultem ' 891,893
JComboBox . 885
JComponen! . 880, 896
JDialog . 898, .890
JDK, descarga e instalacin ' 35
JFC, Java Foundation CJasses (Swing)
857-
JFileChooser . 901
JFrarne . 866, 890
JIT, compiladores just-in-time . 102
JLabel' 881
JList 886
JMenu . 890, 893
JMenuBar . 890, $93
JMenultem . . 878, 890, 893, 894, 895
lNLP, Java Network Launch Protocol .
906
join( ), hebras' 748
JOptionPane . 888
Joy, Bill . 49
JPanel' 877, 896,917
JPopupMenu' 894
JProgressBar . 904
JRadioButton . 878, 884
JSerollPane '865, 887
JSlider 904
JTabbedPane ' 887
JTextArea " 864
JTextField '863, 880
JTextPane . 882
JToggleButton . 877
JUnit, -problemas con ' 709
JVM(Java Virtual Machine) . 353
K
keySet( ) . 571
L
latente, tipo' 464, 472
lectura de la entrada estndar' 613
length:
miembro de matriz' 111
para matrices . 485
lenguajes funcionales' 730
lexicogrfica: ordenacin' 260, .507
LIFO (/asHn, first-out) . 256
ligero: objeto . 252
lmites: y referencias Class . 360
en genricos ' 418, 43J
superclase y 'Class . 361
tipos genricos autoliniitados - 450
limpieza:
con finaUy' 296
.r.ealizaci'o - -98
verificar la condicin .de terminacin
con finalize( ) . 98
Y depurador de memoria 148
LineNumberInputStream . 599
LineN\lIllberReader ' 602
LinkedBlockingQueue . 796
LinkedHashMap' 542,545,571
LinkedHashSet 258, 533, 567, 569
LinkedList . 249, 255, 263, 530
List 241, 244, 249, 530
comparacin de rendimiento' 561
.ordenaciones y bsqueda' 575
lista:
cua4ro de . 886
desplegable' 885
listas variables de argumentos (varargs) "
1I3,469
Y m.todosgenricos . 405
Lister, Timothy . 961
Listlterator . 530
literales:
dedase' 357, 367
double . 53
loat 53
long' 53
valores' 53
local:
clase interna' 217
variable ' 29
lock explcito, objeto 758
logaritmo natural, 54
l ong: y hebras' 760 valor literal (L) . 53
LR:U(least-recent/y-used) . 545
lvalor' 44
M
Macromedia Flex . 932
main()' 142
manifiesto, archivo de, para archivos JAR
. 637, 930
Map '241, 244, 260, 540
.comparacin de rendimiento' 569
EnumMap 672
Map.Entry . 549
MappedByteBuffer . 629
maqueta, objeto' 387
mquinas de estado 'con ,enumeraciones'
680
marcadora, anotacin' 694
marc.o:de: trab.ajo ,de una apli.cacin 231
martos de control y clas,es internas' 230
mark() . 603
matcher, expresin . 336
matrices:
'asociativa . 244 244, 54.0
comparacin con c90tenedor . 484
comparacin de . 503
comprobacin de limites . 111
copia en . 502
covariante . 434
de objetos 485
de objetos genricos 552
de primitivas 485
desigual . 488
devolucin de una matriz 487
inicializacin 110
inicializacin agregada dinmica 486
Iterable' 269
length . 1lI, 485
multidimensional . 488
objetos de primera' clase 485
mayor o igual que > ~ ) . 49
mayor que ( . 49
menor (J igual que ~ ) . 49
menor que ) . 49
mensaje, enVo de . 3
Mensajero' 396, 516, 559
mens:
JDialog, JApplet, JFr.me . 890
JPopupMenu . 894
meta-anotaciones' 695
metadatos . 693
Method . 375, 922
MethodDescriptors . 922
mtodo de factora, patrn de diseo 208,
222,371,399, 604
mtodo de plantillas, patrn de diseo
231,365,426,558, 631,768,840,
843
mtodos:
acoplamiento de' fas llamadas a . 168
adicin de ms mtodos al diseo 137
aplicacin de un mtodo' a una secuen-
cia 469
clases internas en mbitos y . 217
comportamiento de los mtodos poli-
mrficos entre de' cortstmctores .
181
creacin de alias en las, llamadas a . 46
distincin entre mtodos sobrecargados
88
final , 159, 168, 182
genricos 403
inicializacin de variables de . 102
llamadas a mtodos' en linea' 159
polimorfismo 165
private . 173,
protected' 153
recursivos' 322
sobrecarga' 87
static96
sustitucin de mtodos private . 173
Meyer, Jeremy' 693,121,906
Meyers, Scott . 5
micropruebas de rendimiento 566, 835
Microsoft Visual BASrC . 918
miembro:
de datos' 4
inici'nlizadores de . 177
objeto' 6
migracin, compatibilidad de la' 419
mixin' 458
mkdirs( ) . 596
mnemnicos (atajos de teclado) . 894
mdul" '46
monitor, para concurrencia 756
MOllO 20
montaje de clases' 357
multidifusin . 926
multidimensional, matriz 488
multiparadigma, programacin . 2
multiplicacin46
multitarea 729
:tv1XML, Macromedia Flex fonnato de
entrada' 932
mxmlc" Macromedia Flex compilador'
933
N
net.mindview.util.SwingConsole 861
Neville, Sean . 932
new,. E/S' .' 616
new, operador' 97
Y matrices' 111
newlnstance( ) . 356, 878
reflexin' 356
next( ), terator . 253
mo616
buffer' 616
channel . 616
rendimiento 630
no equivalente (!=) . 49
no modificable, coleccin o mapa' 576
nombres:
al combinar interfaces 202
colisiones de . 126
cualificados' 356
de paquetes' 31,124
North, BorderLayout . 866
NOT, lgico (!) . 51
notacin exponencial 54
notify AI!( ) . 784
notifyListeners( ) . 929
nul! . 26
NullPointerException . 294
o
Object:
clase raz estndar 142
herenca 142
ObjeclOutputStream . 639
objeto 2
activo 850
alcanzable 578
asignacin de objetos por copia de refe-
rencias . 45
ndice 971
bloqueo, para concurrencia 756
Class . 353, 651, 757
creacin' 86
creacin de alias 45
de transferencia de datos 396, 516,
559
equals( ) . 50
equivalencia de' objetos y de referencias
50
equivalente (--) 49
final' 156
getClass( ) . 354
hashCode( ) . 541
informacin de tipos 35-1
interfaz de un . 3
matrices son objetos-de primera clase'
485
miembro' 6
proceso de creacin del 107
red de objetos' 639
serializaci6n . 639
wait() y notifyAl1() 784
Objeto nulo, patrn de diseo' 381
Octal, 53
onda sinusoidal 896
OpenLaszlo. alternativa a Flex . 932
operacin atmica " 760
operaciones no soportadas en contenedo-
res Java 529
operaciones opcionales en contenedores
Java' 528
+ Y ~ para String . 59, 142
+, para String . 317
operadores 44
bit a bit 55
coma, operador 74
complemento a lUlO . 55
de desplazamiento 55
de indexacin [ 1 . Il O
de proyeccin 60
errores comunes' 60
lgicos' 51
lgicos y cortocircuitos 52
matemticos' 46. 633
precedencia 44
relacionales 49
sobrecarga de . 59
sobrecarga de operador para String .
318
String, conversin con operador + . 44,
59
temario 58
ooarios . 48, 55
OR:
bit a bit 55, 60
lgico (11) . 51
orden:
de inicializacin 105, 162, 182
de llamadas a constructores 176
972 Piensa en Java
ordenacin' 504
alfabtica ' 260
lexicogrfica ' 260
Y bsquedas en listas' 575
ordinal( ), para enum . 660
organizacin del cdigo ' 128
OSExecute . 615
OutputStream . 596, 598
OutputStreamWriter . 600, 601
p
paintComponent() . 896, 900
paquete (package) . 122
acceso de . 221
acceso de, y protected . j 53
nombres unvocos de . 124
nombres, uso de maysculas' 31
predeterminado' 122, 130
Y estructura de directorios' 128
parmetro de recopilacin, . 458, 478
pato, tipos' 464, 472
patrn de diseo:
adaptador' 198, 204, 402,472,474,
515
basado en comandos' 235, 384, 672,
734
basado en estados' 184
basado en estrategia' 196,203,474,
494,504,588,593,676,8 11
cadena de responsabilidad' 676
Decorador ' 461
Iterador' 214, 252
Iterador nulo' 381
mtodo basado en adaptadores' 270
mtodo de factoria' 208, 222, 371,
399,604
mtodo de plantillas' 231, 365, 426,
558,631,768,840,843
objeto de transferencia de datos
(Mensajero) . 396, 516, 559
Objeto nulo' 381
Peso mosca' 519, 854
Proxy378
Singleton . 136
solitario 136
Visitante' 706
pattern, expresiones regulares' 333
persistencia ' 649
ligera' 639
Peso mosca, patrn de diseo . 519, 854
PhantornReferenee . 578
Piedra, papel, tijera . 685
pila' 255, 256, 398, 582
Piped1nputStream . 597
PipedOutputStream' 597, 598
PipedReader . 601, 800
PipedWriter . 60 1, 800
planificador de hebras' 732
plantillas C++ . 394, 417
Plauger, P.! .. 961
polimorfismo' 9, 165- 187,351, 391
comportamiento de los mtodos poli-
mrficos dentro de constructores .
181
Y constructores' 175
Y despacho mltiple - 684
POO (programacin orientada a objetos):
caractersticas bsicas' 2
conceptos bsicos' 1
protocolo' 192
Simula-67, lenguaje de programacin'3
suplantacin' 2
posicionamiento absoluto 868
post-decremento' 48
postfija . 48
post-incremento 48
pre-decremento . 48
predetenninado, constructor- 92, 144,377
predetenninado, paquete - 122, 130
prediccin, operadores de, 60
preferenees, MI . 656
prefija' 48
pre-incremento' 48
prerrequisitos para este libro' 1
primitivos:
comparacin' 50
final' 156
final y static . 156
inicializacin de campos de clases ' 102
tipos 25
tipos de datos primitivos y uso con ope-
radores . 62
printf( ) . 324
printStaekTrace( ) . 287, 289
PrintStream . 600
PrintWriter . 602, 605, 606
constructor en Java SES, 610
prioridad, en concurrencia 738
PriorityBlockingQueue, concurrencia
811 -
PriorityQueue . 264, 537
private' 5,121,128, 130, 153, 159,756
clases internas 232
interfaces anidadas 207
sustitucin de mtodos 173
proceso concurrente' 729
ProeessBuilder' 615
ProcessFiles . 720
productor-consumidor) concurrencia 791
programacin basada en agentes 853
programacin multiparadigma . 2
programacin orientada a aspectos (AOP)
458
programacin orientada a objetos (POO),
VesePOO
programacin visual 918
entornos de . 858
programador de clientes 5
promocin a int . 62., 70
PropertyChangeEvent . 931
PropertyDescriptors . 922
propiedades:
hoja de propiedades personalizada 931
indexadas 931
restringidas 931
ProptertyVetoExcepti on . 931
proteeted' 5, 121,128,13 1, 153
Y acceso de paquete 13 1, 153
protoeol - 192
Protocolo Java de inicio a travs de red
(INLP) 906
Proxy, patrn de disefio . 378
proxy: y java.lang. ref. Referenee . 579
para mtodos DO modificables de la
clase CollectioDs . 530
proyeccin . 11
asSubclass() . 361
mediante una clase genrica 448
Y tipos genricos 447
Y tipos primitivos 70
publie' 5,121,128, 128
clase, y unidades de compilacin 122
e interface 192
puntero, exclusin de punteros en Java
229
PushbackInputStream . 599
PushbaekReader . 602
Python 1,5,9, 18, 22, 464,510,729,962
queue - 241,255, 263,537,796
rendimiento561
R
RAD (Rapid Applieation Development) -
375
random( ) . 260
RandomAccess. interfaz de marcado para
contenedores 275
RandomAccessFile' 602, 603, 608, 616
read() . 596 nio' 616
readDouble( ) . 608
Reader' 596,600, 601
readExtemal( ) . 643
readLine() . 305, 602, 606, 613
readObjeet( ) . 639
con Serializable . 647
ReadWriteLock . 848
reanudacin en el tratamiento de excep-
ciones . 281
recuadros de mensaje en Swing . 888
recuento de referencias, depurador de
memoria tOO
recursin no intencionada con toString( ) .
321
red de objetos' 639
red, E/S de - 616
redireccionamiento de la. EIS estndar 613
redisefio . 121
ReentrantLoek' 759, 781
Reference de java.lang.ref 578
referencia:
asignacin de objetos por copia de refe-
rencias . 45
equivalencia de referencias y equiva-
lencia de objetos ' 50
final, 156 hallar el tipo exacto de refe-
rencia base' 352
null . 26
referencias Class genricas 359
reflexin' 375, 387, 870, 920
diferencia entre RITl y . 375
ejemplo' 877
procesador de anotaciones' 696, 700
tipos latentes y genricos' 467
Y Beans' 918
regeneracin de una excepcin' 289
regex . 333
regin protegida en el tratamiento de
excepciones' 279
registro y excepciones' 283
Regla de Brian de la sincronizacin' 757
rehashing' 571
reificacin y genricos' 419
removeActionListener( ) . 924, 929
removeXXXListener( ) . 869
renameTo() . 596
rendimiento:
nio ' 630
optimizacin del . 834
pruebas de . 558
Y fmal . 161
reset( ) . 603
respuesta rpida, interfaces de usuario de .
750
resta 46
resume( ) e . 775
retomo:
sobrecarga de los valores de . 92
tipos de retomo covariantes . 183,453
valor de retomo de constructor' 86
retrollamada' 588, 863
Y clases internas' 229
retum:
devolucin de mltiples objetos' 396
devolucin de una matriz' 487
Y fmally . 299
reutilizacin' 6
cdigo reutilizable ' 918
de cdigo' 139
rewind( ) . 620
RTTI (runtime type information) 186,351
Class, objeto' 353, 878
ClassCastException . 362
Constructor, clase' 375
Field ' 375
getConstructor( ) . 878
instanceof, palabra clave' 362
isInstance( ) . 368
Method' 375
newlnstance( ) . 878
reflexin' 375
shape, ejemplo' 351
Rumbaugh, James' 960
RuntimeExeeption ' 294, 3 13
rutina de tratamiento de excepciones, 280
rvalor' 44
s
salto incondicional' 76
ScheduledExecutor, para concurrencia
814
seccin crtica y bloque synchronized 765
secuencia, aplicacin de un mtodo a una
468
seek( ) . 602, 608
seleccin aleatoria y enum . 666
semforo contador' 817
seminarios xxiii
fonnaci6n proporcionada por
MindView, Inc .. 955
seales perdidas, concurrencia 788
separacin de la interfaz y la implementa-
cin . 5, 133, 869
SequenceInputStream' 597, 602
Serializable' 639, 643, 646, 653, 926.
Vase tambin serializacin
readObject() . 647
writeObject( ) . 647
serializacin:
control en la . 642
defaultReadObject( ) . 648
defaultWriteObject() . 647
Versionado . 649
y almacenamiento de objeto 649
Y transient . 646
Set'241, 244,258, 533
comparacin de rendimiento' 567
relaciones matemticas' 409
setActionCommand( ) . 894
setBorder( ) . 881
setErr(PrintStream) . 614
setIcon( ) . 879
setIn(InputStream) . 614
setLayout( ) . 866
setMnemonic( ) . 894
setOut(PrintStream) . 614
setToolTipText( ) . 880
shuffie( ) . 576
signatura del mtodo' 30
Simula-67, lenguaje de programacin' 3
simulacin' 821
SingleThreadExecutor . 735
size( ), ArrayList . 242
sizeof( ), no existe en Java' 62
sleep( ), en concurrencia 737
Smalltalk . 2
ndice 973
sobrecarga:
de constructores' 87
de los valores de retorno' 92
de mtodos' 87
de operadores' 59
distincin entre mtodos sobrecargados
88
genricos' 449
ocultacin de nombres durante la
herencia' 151
operadores + y += para String' 142,
318
SocketChannel . 632
SoftReference . 578
Software Development Conference . xxii i
solicitud' 3
Solitario (singleton), patrn de diseo 136
SortedMap . 544
SortedSet . 536
South, BorderLayout . 866
split(), String . 196,332
sprintf( ) . 329
SQL generado mediante anotaciones' 698
stateChanged( ) . 897
stabe: palabra clave 32, 192
clases internas' 224
inicializacin 163,354
mtodo' 96,
stop( ) e interbloqueos ' 775
StreamTokenizer . 602
String:
CASE _INSENSITIVE _ ORDER . 575
concatenacin con el operador += . 59
conversin con operador + . 44, 59
expresiones regulares ' 331
fonnat( ) . 329
indexOf( ) . 377
inmutabilidad, 317
mtodos' 317, 322
operadores + y += para ' 142
ordenacin lexicogrfica y alfabtica'
507
ordenacin, CASE _INSENSITIVE_
ORDER 588
split( ), mtodo ' 196
toString( ) . 140
StringBuffer . 597
StringBufferlnputStream . 597
StringBuilder, String y toString( ) . 318
StringReader . 601, 604
String Writer . 60 I
Stroustrup, Bjame . 119
Stub . 387
subobjeto . 143
sucesos:
JavaBeans . 918
modelo de Swing . 869
multidifusin y JavaBeans . 927
programacin dirigida por' 862
974 Piensa en Java
respuesta a un suceso Swing . 862
sistema dirigido pOI sucesos' 231
Y escuchas 869
sugerencias' 880
swna' 46
super: .
palabra clave' 143
Y clases internas' 236
superclase 143
limites 361
supertipo. comodines de . 438
suplantacin en la POO . 2
suspende ) e interbloqueos 775
sustitucin 9:
de mtodos private . 173
Y clases internas' 236
Y sobrecarga' 151
sustitucin:
herencia yex.tension . 184
principio de . 9
pura 9, 185
SWF, formato de cdigo intermedio Flash
932
Swing857
componentes' 876
HTML en los componentes' 902
modelo de sucesos' 869
Y concurrencia' 910
switch:
palabra clave 81
Y enuro 662
synchronized:
contenedores' 577
decidr qu mtodos sincronizar' 929
esttico' 757
Regla de Brian de la sincronizacin 757
seccin critica y bloque 765
wait( ) y notifY AlI( ) . 784
Y herencia 929
SynchronousQueue, concurrencia 826
System.arraycopy( ) . 502
System.err 282, 613
System. in613
System.out . 613
systemNodeForPackage( ), API preferen-
ces 657
T
tabla de base de datos, SQL generado
mediante anotaciones' 698
tamao de un HashMap o RasbSet . 571
tareas y hebras, terminologa' 748
teclado: navegacin y Swing ,858 atajo
de 894
Teoria del compromiso delegado' 751
terminacin:
alta (big endian) . 624
baja (Jttle endian) . 624
condicin de, y finalize( ) . 98
en el tratamiento de excepciones' 280
ternario, operador' 58
this, palabra clave' 93
ThreadFactory pen;onalizada . 741
tbrow, palabra clave' 280
Throwable, clase base para Exception 287
tiene-un, relacin' 6, 153
TimeUnit . 738, 811
tipos:
base 7
comprobacin de . 309, 392
derivado ' 7
enumerados 117
estructurales 464, 472
genricos y contenedores seguros res-
pecto al tipo 242
hallar el tipo exacto de referencia base'
352
inferencia del argumento de . 403
latentes 464,472
marcador de, en genricos' 424
matrices y comprobacin de . 483
parametrizados . 393
pato 464, 472
primitivos' 25
seguridad de tipos en Java ' 60
seguridad dinmica de . 456
tipo de datos equivalente a clase' 3
tipos de datos primitivos y uso con ope-
radares' 62
toArray() . 571
TooManyListenersException . 926
toString() . 140
directrices para usar StringBuilder' 319
transferFrom() . 618
transferTo()618
transieot, palabra clave' .646
TreeMap . 542, 544, 571
TreeSet . 258, 533, 536, 567
true . .5 1
try, bloque 150, 280, 297
en excepciones' 280
tryLock( ), bloqueo de archivos 632
tupla 396, 408, 413
TYPE, campo para literales de clases pri-
mitivas . 357
u
UML(Unified Modeling Language) . 4, 6,
960
unario, operador:
ms (+) 48
menos (-)48
UncaugbtExceptionHandler, clase Thread
752
Unicode . 601
'Unidad de c.ompilacin . 122
unidad de traduccin' 122
unidifusin . 926
Unified Modeling Language (UML) . 4,
960
unmodifiableList(), Collections . 530
UnsupportedOperationException . 530
upcasting. Vase generalizacn
userNodeForPackage(), API preferences .
657
Utilidades de java.uti1.Collec,tions . 572
v
values() para enumeraciones 659, 663
Varga, Ervin . 7, 780
variable:
defmicin . 73
inicializacin de variables de mtodos'
102
listas de argumentos variables' 113
local 29
Vector 566, 581
vector de cambio' 232
Venners, Bill . 98
versionado, serializacin . 649
Visitante, patrn de diseo' 706
Visual BASIC, Microsoft 918
volatile . 754, 760, 763
w
wait() . 784
Waldrop, M. Mitchell 961
WeakHashMap . 542, 580
WeakReference . 578
Web Start, Java 906
West, BorderLayout . 866
while . 72
windowClosing( ) . 899
write( ) . 596
nio618
writeBytes( ) > 607
writeChars( ) . 607
writeDouble( ) . 607
writeExtemal( ) . 643
writeObject( ) . 639
con Serializable . 646
Writer 596,600,601
x
XDoclet . 693
XML654
XOM, biblioteca XML . 654
XOR (Exclusive-OR) . 55
Z
ZipEutry . 637
ZipInputStream . 634
ZipOutputStream . 634

Você também pode gostar