Java, C, C++ y C#, y es un maestro programador en Windows. Se han vendido más de 3.5 millones de ejemplares de sus libros sobre programación y se han traducido a todos los idiomas importantes. Es autor de gran cantidad de bestsellers de Java, incluidos Java: Manual de referencia, Java: Manual de referencia y Fundamentos de Java. Entre sus otros bestsellers se incluyen Manual de referencia y El arte de programar en Java. Schildt tiene grado universitario y maestría de la Universidad de Illinois. Su sitio Web es
Soluciones de programación
Herbert Schildt
Traducción
Eloy Pineda Rojas
Traductor profesional
MÉXICO • BOGOTÁ • BUENOS AIRES • CARACAS • GUATEMALA • LISBOA • MADRID NUEVA YORK • SAN JUAN • SANTIAGO • AUCKLAND • LONDRES • MILÁN
Supervisora de producción: Jacqueline Brieño Álvarez Formación: Gráfica FX
JAVA Soluciones de programación
Prohibida la reproducción total o parcial de esta obra, por cualquier medio, sin la autorización escrita del editor.
DERECHOS RESERVADOS © 2009 respecto a la primera edición en español por McGRAW-HILL INTERAMERICANA EDITORES, S.A. DE C.V.
A Subsidiary of The McGraw-Hill Companies, Inc.
Corporativo Punta Santa Fe
Prolongación Paseo de la Reforma 1015 Torre A Piso 17, Colonia Desarrollo Santa Fe,
Delegación Álvaro Obregón C.P. 01376, México, D. F.
Miembro de la Cámara Nacional de la Industria Editorial Mexicana, Reg. Núm. 736 ISBN10 : 970-10-6756-8
ISBN13 : 978-970-10-6756-7
Translated from the 1st English edition of
Herb Schildt’s Java Programming Cookbook
By: Herbert Schildt
Copyright © 2008 by The McGraw-Hill Companies. All rights reserved.
ISBN: 978-0-07-226315-2
1234567890 0876543219
Contenido
v
Prefacio xix
1 Revisión general 1
¿Qué encontrará en el interior? 1
¿Cómo están organizadas las recetas? 2
Una cuantas palabras de precaución 3
Es necesaria experiencia en Java 3
¿Qué versión de Java? 4
2 Trabajo con cadenas y expresiones regulares 5
Una revisión general de las clases de cadena de Java 6
La API de expresiones regulares de Java 8
Introducción a las expresiones regulares 8
Caracteres normales 9
Clases de caracteres 9
El carácter comodín 10
Cuantificadores 10
Cuantificadores avaros, renuentes y posesivos 11
Comparadores de límites 11
El operador O 11
Grupos 12
Secuencias de marcas 13
Recuerde incluir el carácter de escape \ en cadenas de Java 13
Ordene una matriz de cadenas de manera inversa 14
Paso a paso 14
Análisis 14
Ejemplo 16
Opciones 17
Ignore las diferencias entre mayúsculas y minúsculas cuando ordene una matriz de cadenas 18
Paso a paso 18
Análisis 19
Ejemplo 19
Opciones 21
Ignore las diferencias entre mayúsculas y minúsculas cuando busque o reemplace subcadenas 22
Paso a paso 22
Análisis 22
Ejemplo 23
Opciones 24
Divida una cadena en partes empleando split( ) 25
Análisis 26
Ejemplo 26
Opciones 28
Recupere pares clave/valor de una cadena 28
Paso a paso 29
Análisis 29
Ejemplo 29
Opciones 32
Compare y extraiga subcadenas empleando la API de expresiones regulares 32
Paso a paso 33
Análisis 33
Ejemplo 33
Opciones 34
Divida en fichas una cadena empleando la API de expresiones regulares 35
Paso a paso 36 Análisis 37 Ejemplo 38 Ejemplo adicional 40 Opciones 47 3 Manejo de archivos 49
Una revisión general del manejo de archivos 50
Flujos 50
La clase RandomAccessFile 53
La clase File 54
Las interfaz de E/S 55
Los flujos de archivos comprimidos 57
Lea bytes de un archivo 59
Paso a paso 59
Análisis 59
Ejemplo 60
Opciones 61
Escriba bytes en un archivo 62
Paso a paso 63
Análisis 63
Ejemplo 63
Opciones 64
Use el búfer para la E/S de un archivo basada en bytes 65
Paso a paso 66
Análisis 66
Ejemplo 66
Opciones 68
Lea caracteres de un archivo 69
Análisis 69
Ejemplo 70
Opciones 71
Escriba caracteres en un archivo 72
Paso a paso 72
Análisis 73
Ejemplo 73
Opciones 74
Use el búfer para la E/S de un archivo basada en caracteres 75
Paso a paso 76
Análisis 76
Ejemplo 77
Opciones 79
Lea y escriba archivos de acceso aleatorio 80
Paso a paso 80
Análisis 80
Ejemplo 81
Opciones 83
Obtenga atributos de archivos 83
Paso a paso 84
Análisis 84
Ejemplo 84
Opciones 86
Establezca atributos de archivos 86
Paso a paso 87
Análisis 87
Ejemplo 87
Opciones 89
Elabore una lista de un directorio 90
Paso a paso 90
Análisis 90
Ejemplo 91
Ejemplo adicional 93
Opciones 94
Comprima y descomprima datos 95
Paso a paso 95
Análisis 96
Ejemplo 96
Opciones 99
Cree un archivo ZIP 100
Paso a paso 100
Análisis 101
Ejemplo 102
Descomprima un archivo ZIP 105 Paso a paso 105 Análisis 106 Ejemplo 107 Opciones 109 Serialice objetos 110 Paso a paso 111 Análisis 111 Ejemplo 112 Opciones 115 4 Formato de datos 117
Revisión general de Formatter 118
Fundamentos de formación 119
Especificación de un ancho mínimo de campo 121
Especificación de precisión 121
Uso de las marcas de formato 122
La opción en mayúsculas 122
Uso de un índice de argumentos 123
Cuatro técnicas simples de formación numérica que emplean Formatter 124
Paso a paso 124
Análisis 124
Ejemplo 125
Opciones 126
Alinee verticalmente datos numéricos empleando Formatter 126
Paso a paso 126
Análisis 127
Ejemplo 127
Ejemplo adicional: centro de datos 128
Opciones 131
Justifique a la izquierda la salida con Formatter 131
Paso a paso 131
Análisis 131
Ejemplo 132
Opciones 133
Forme fecha y hora empleando Formatter 133
Paso a paso 134
Análisis 134
Ejemplo 136
Opciones 137
Especifique un idioma local usando Formatter 138
Paso a paso 138
Análisis 138
Ejemplo 139
Use flujos con Formatter 140
Paso a paso 140
Análisis 140
Ejemplo 141
Opciones 142
Use printf( ) para desplegar datos formados 143
Paso a paso 143
Análisis 143
Ejemplo 144
Ejemplo adicional 145
Opciones 145
Forme fecha y hora con DateFormat 146
Paso a paso 147
Análisis 148
Ejemplo 148
Opciones 149
Forme fecha y hora con patrones empleando SimpleDateFormat 150
Paso a paso 151
Análisis 151
Ejemplo 152
Opciones 153
Forme valores numéricos con NumberFormat 153
Paso a paso 154
Análisis 154
Ejemplo 155
Opciones 156
Forme valores monetarios usando NumberFormat 156
Paso a paso 157
Análisis 157
Ejemplo 157
Opciones 157
Forme valores numéricos con patrones empleando DecimalFormat 158
Paso a paso 158
Análisis 158
Ejemplo 159
Opciones 160
5 Trabajo con colecciones 161
Revisión general de las colecciones 162
Tres cambios recientes 163
Las interfaz de Collection 164
Las clases de la colección 173
La clase ArrayList 173
La clase LinkedList 174
La clase HashSet 175
La clase TreeSet 176
La clase PriorityQueue 176
La clase ArrayDeque 177
La clase EnumSet 178
Revisión general de los mapas 178
Las interfaz de Map 178
Las clases de Map 183
Algoritmos 185
Técnicas básicas de colecciones 186
Paso a paso 187
Análisis 187
Ejemplo 188
Opciones 190
Trabaje con listas 191
Paso a paso 191
Análisis 192
Ejemplo 192
Opciones 195
Trabaje con conjuntos 195
Paso a paso 196
Análisis 196
Ejemplo 197
Ejemplo adicional 198
Opciones 201
Use Comparable para almacenar objetos en una colección ordenada 201
Paso a paso 202
Análisis 202
Ejemplo 203
Opciones 204
Use un Comparator con una colección 205
Paso a paso 205
Análisis 205
Ejemplo 206
Opciones 209
Itere en una colección 209
Paso a paso 210
Análisis 210
Ejemplo 211
Opciones 213
Cree una cola o una pila empleando Deque 214
Paso a paso 214
Análisis 215
Ejemplo 216
Invierta, gire y ordene al azar una List 218
Paso a paso 219
Análisis 219
Ejemplo 219
Opciones 220
Ordene una List y busque en ella 221
Paso a paso 221
Análisis 221
Ejemplo 222
Opciones 223
Cree una colección comprobada 224
Paso a paso 224
Análisis 224
Ejemplo 225
Opciones 227
Cree una colección sincronizada 227
Paso a paso 228
Análisis 228
Ejemplo 228
Opciones 231
Cree una colección inmutable 231
Paso a paso 231
Análisis 232
Ejemplo 232
Opciones 233
Técnicas básicas de Map 233
Paso a paso 234
Análisis 235
Ejemplo 235
Opciones 238
Convierta una lista de Properties en un HashMap 238
Paso a paso 239
Análisis 239
Ejemplo 239
Opciones 240
6 Applets y servlets 241
Revisión general de las applets 241
La clase Applet 242
Arquitectura de Applet 244
El ciclo de vida de la applet 245
Las interfaz AppletContext, AudioClip y AppletStub 246
Revisión general de la servlet 246
El paquete javax.servlet 246
La clase HttpServlet 251
La clase Cookie 251
El ciclo de vida de la servlet 253
Uso de Tomcat para desarrollo de servlets 254
Cree un esqueleto de Applet basado en AWT 255
Paso a paso 256
Análisis 256
Ejemplo 256
Opciones 257
Cree un esqueleto de Applet basado en Swing 257
Paso a paso 258
Análisis 258
Ejemplo 259
Opciones 260
Cree una GUI y maneje sucesos en una Applet de Swing 260
Paso a paso 261
Análisis 261
Nota histórica: getContentPane( ) 263
Ejemplo 263
Ejemplo adicional 266
Opciones 268
Pinte directamente en la superficie de la Applet 269
Paso a paso 269
Análisis 270
Ejemplo 271
Opciones 273
Pase parámetros a Applets 275
Paso a paso 275
Análisis 275
Ejemplo 276
Opciones 277
Use AppletContext para desplegar una página Web 278
Paso a paso 278
Análisis 278
Ejemplo 278
Opciones 281
Cree una servlet simple usando GenericServlet 282
Paso a paso 282
Análisis 282
Ejemplo 283
Opciones 284
Maneje solicitudes HTTP en una servlet 185
Paso a paso 285
Análisis 285
Ejemplo adicional 287
Opciones 290
Use una cookie con una servlet 290
Paso a paso 290
Análisis 290
Ejemplo 291
Opciones 293
7 Multiprocesamiento 295
Fundamentos del multiprocesamiento
La interfaz Runnable 297
La clase Thread 298
Cree un subproceso al implementar Runnable 299
Paso a paso 300
Análisis 300
Ejemplo 300
Opciones 303
Cree un subproceso al extender Thread 304
Paso a paso 305
Análisis 305
Ejemplo 305
Opciones 306
Use el nombre y el ID de un subproceso 307
Paso a paso 307
Análisis 308
Ejemplo 308
Opciones 310
Espere a que termine un subproceso 311
Paso a paso 311 Análisis 311 Ejemplo 312 Opciones 313 Sincronice subprocesos 314 Paso a paso 315 Análisis 315 Ejemplo 316 Opciones 318
Establezca comunicación entre subprocesos 318
Paso a paso 319
Análisis 319
Ejemplo 320
Opciones 322
Suspenda, reanude y detenga un subproceso 323
Análisis 324
Ejemplo 325
Opciones 327
Use un subproceso de daemon 328
Paso a paso 329
Análisis 329
Ejemplo 329
Ejemplo adicional: una clase simple de recordatorio 331
Opciones 336 Interrumpa un subproceso 336 Paso a paso 337 Análisis 337 Ejemplo 337 Opciones 339
Establezca y obtenga una prioridad de subproceso 341
Paso a paso 341
Análisis 342
Ejemplo 342
Opciones 344
Monitoree el estado de un subproceso 344
Paso a paso 345
Análisis 345
Ejemplo 346
Ejemplo adicional: un monitor de subprocesos en tiempo real 349
Opciones 353
Use un grupo de subprocesos 353
Paso a paso 354
Análisis 354
Ejemplo 355
Opciones 357
8 Swing 359
Revisión general de Swing 360
Componentes y contenedores 361
Componentes 362
Contenedores 362
Los paneles de contenedor de nivel superior 363
Revisión general del administrador de diseño 363
Manejo de sucesos 364
Sucesos 365
Orígenes de sucesos 365
Escuchas de sucesos 365
Cree una aplicación simple de Swing 366
Paso a paso 366
Nota histórica: getContentPane( ) 369
Ejemplo 369
Opciones 371
Establezca el administrador de diseño del panel de contenido 372
Paso a paso 372
Análisis 372
Ejemplo 373
Opciones 375
Trabaje con JLabel 376
Paso a paso 376
Análisis 377
Ejemplo 379
Opciones 382
Cree un botón simple 383
Paso a paso 384
Análisis 384
Ejemplo 385
Opciones 387
Use iconos, HTML y mnemotécnica con JButton 390
Paso a paso 391
Análisis 391
Ejemplo 393
Opciones 395
Cree un botón interruptor 396
Paso a paso 397
Análisis 397
Ejemplo 398
Opciones 400
Cree casillas de verificación 400
Paso a paso 401
Análisis 401
Ejemplo 401
Opciones 405
Cree botones de opción 405
Paso a paso 406
Análisis 406
Ejemplo 407
Opciones 410
Ingrese texto con JTextField 411
Paso a paso 411
Análisis 412
Ejemplo 413
Ejemplo adicional: cortar, copiar y pegar 416
Opciones 419
Paso a paso 420
Análisis 420
Ejemplo 422
Opciones 424
Use una barra de desplazamiento 426
Paso a paso 427
Análisis 427
Ejemplo 429
Opciones 431
Use JScrollPane para manejar el desplazamiento 433
Paso a paso 433
Análisis 433
Ejemplo 433
Opciones 436
Despliegue datos en una JTable 438
Paso a paso 439
Análisis 440
Ejemplo 441
Opciones 444
Maneje sucesos de JTable 446
Paso a paso 447
Análisis 447
Ejemplo 450
Opciones 455
Despliegue datos en un JTree 456
Paso a paso 458
Análisis 458
Ejemplo 461
Opciones 464
Cree un menú principal 466
Paso a paso 467
Análisis 467
Ejemplo 469
Opciones 471
9 Miscelánea 473
Acceda a un recurso mediante una conexión HTTP 474
Paso a paso 474 Análisis 474 Ejemplo 475 Opciones 476 Use un semáforo 480 Paso a paso 481 Análisis 482 Ejemplo 482 Opciones 485
Devuelva un valor de un subproceso 486
Paso a paso 487
Análisis 487
Ejemplo 488
Opciones 491
Use reflexión para obtener información acerca de una clase en tiempo de ejecución 491
Paso a paso 492
Análisis 492
Ejemplo 493
Ejemplo adicional: una utilería de reflexión 494
Opciones 496
Use reflexión para crear dinámicamente un objeto y llamar métodos 496
Paso a paso 497
Análisis 497
Ejemplo 498
Opciones 501
Cree una clase personalizada de excepción 501
Paso a paso 502
Análisis 502
Ejemplo 504
Opciones 505
Calendarice una tarea para ejecución futura 506
Paso a paso 507
Análisis 507
Ejemplo 508
Opciones 510
xix
D
urante muchos años, amigos y lectores me han pedido que escriba un libro de solucionespara Java, compartiendo algunas de las técnicas y métodos que utilizo cuando programo. Desde el principio me gustó la idea, pero no lograba darme tiempo para ella con un calendario de escritura muy ocupado. Como muchos lectores saben, escribo acerca de muchas facetas de programación, con énfasis especial en Java, C/C++ y C#. Debido a los rápidos ciclos de revisión de estos lenguajes, dedico casi todo mi tiempo disponible a actualizar mis libros para que cubran las versiones más recientes de esos lenguajes. Por fortuna, a principios de 2007 se abrió una ventana de oportunidad y fi nalmente pude dedicar tiempo a escribir este libro de soluciones de Java. Debo admitir que rápidamente se volvió uno de los proyectos que más he disfrutado.
Este libro destila la esencia de muchas técnicas de propósito general en un conjunto de soluciones paso a paso. En cada solución se describe un conjunto de componentes clave, como clases, interfaz y métodos. Luego se muestran los pasos necesarios para ensamblar esos componentes en una secuencia de código que logre los resultados deseados. Esta organización facilita la búsqueda de técnicas en que está interesado y luego ponerlas en acción.
En realidad, “en acción” es una parte importante de este libro. Creo que los buenos libros de programación contienen dos elementos: teoría sólida y aplicación práctica. En las soluciones, las instrucciones paso a paso y los análisis proporcionan la teoría. Para llevar esa teoría a la práctica, cada solución incluye un ejemplo completo de código. En los ejemplos se demuestra de manera concreta, sin ambigüedades, la manera en que pueden aplicarse. En otras palabras, en los ejemplos se eliminan las “adivinanzas” y se ahorra tiempo.
Aunque ningún libro puede incluir todas las soluciones que pudieran desearse (hay un número casi ilimitado de soluciones posibles), traté de abarcar un amplio rango de temas. Mis criterios para incluir una solución se analizan de manera detallada en el capítulo 1, pero, en resumen, incluí las que serían útiles para muchos programadores y que responderían las preguntas más frecuentes. Aún con estos criterios, fue difícil decidir qué incluir y qué dejar fuera. Ésta fue la parte más desafiante de la escritura del libro. Al final, se impusieron la experiencia, el juicio y la intuición. Por fortuna, ¡he incluido algo para satisfacer al gusto de cada programador!
Código de ejemplo en Web
El código fuente para todos los ejemplos de este libro está disponible de manera gratuita en Web en http://www.mcgraw-hill-educacion.com/
Más de Herbert Schildt
Java, Soluciones de programación es sólo uno de los muchos libros de programación de Herb. He aquí algunos otros que le resultarán de interés:
Para aprender más acerca de Java recomendamos: Java: Manual de referencia
Fundamentos de Java Java: Manual de referencia
Para aprender más acerca de C++, estos libros le resultarán especialmente útiles. C++: Soluciones de programación
C++: A Begginer’s Guide C++ The complete reference
STL Programming From the Ground Up The Art of C++
Para aprender acerca de C#, sugerimos los siguientes libros de Schildt: C#: The Complete Reference
C#: A Begginer’s Guide
Si quiere aprender acerca del lenguaje C, entonces le interesará el siguiente título: C: The complete reference
Cuando necesite respuestas sólidas, rápidas, busque algo de Herbert Schildt, la autoridad reconocida en programación.
CAPÍTULO
Revisión general
1
1
E
ste libro es una colección de técnicas que muestra cómo realizar varias tareas de programación en Java. Cada solución ilustra la manera de realizar una operación específi ca. Por ejemplo, hay soluciones que leen bytes de un archivo, iteran una colección, forman datos numéricos, construyen componentes de Swing, crean un servlet, etc. Cada técnica de este libro describe un conjunto de elementos de programa claves y la secuencia de pasos necesarios para usarlos y realizar una tarea de programación.A fi n de cuentas, el objetivo de este libro es ahorrarle tiempo y esfuerzo durante el desarrollo de programas. Muchas tareas de programación incluyen un conjunto de clases de API, interfaces y métodos que deben aplicarse en una secuencia específi ca. El problema es que a veces no sabe cuáles clases de API usar o en qué orden llamar a los métodos. En lugar de tener que recorrer una gran cantidad de documentación y de tutoriales en línea para determinar la manera de realizar alguna tarea, puede buscar su receta. Cada receta muestra una manera de llegar a una solución, describiendo los elementos necesarios y el orden en que deben usarse. Con esta información puede diseñar una solución que se adecue a sus necesidades específi cas.
¿Qué encontrará en el interior?
Para elegir las soluciones para este libro, me concentré en las siguientes categorías: • Procesamiento de cadenas (incluidas expresiones regulares)
• Manejo de archivos • Formateo de datos • Applets y servlets • Swing
• Las colecciones del marco conceptual • Multiprocesamiento
Elegí estas categorías porque se relacionan con un amplio rango de programadores (evité temas especializados que se aplican sólo a un subconjunto estrecho de casos). Cada una de estas categorías se vuelve la base de un capítulo. Además de las soluciones relacionadas con los temas anteriores, tengo otros que quiero incluir pero para los cuales no fue posible un capítulo completo. Agrupé esas soluciones en el capítulo fi nal.
Por supuesto, la elección de temas sólo fue el principio del proceso de selección. Dentro de cada categoría, tuve que decidir lo que se incluía y lo que se dejaba fuera. En general, incluí una solución si cumplía los dos criterios siguientes:
1. La técnica es útil para un amplio rango de programadores.
2. Proporciona una respuesta a una pregunta frecuente de programación.
El primer criterio se explica por sí solo y se basa en mi experiencia. Incluí soluciones que describen la manera de realizar un conjunto de tareas que se encontrarían comúnmente cuando se crean aplicaciones de Java. Algunas de ellas ilustran un concepto general que puede adaptarse para resolver varios tipos diferentes de problemas. Por ejemplo, en el capítulo 2 se muestra una solución que usa la API de expresión regular para buscar y extraer subcadenas de una cadena. Este procedimiento general es útil en varios contextos, como encontrar una dirección de correo electrónico, o un número telefónico dentro de una frase, o extraer una palabra clave de una consulta de base de datos. Otras soluciones describen técnicas más específi cas pero de uso más amplio. Por ejemplo, en el capítulo 4 se muestra la manera de formar la fecha y hora usando SimpleDateFormat.
El segundo criterio se basa en mi experiencia como autor de libros de programación. A través de los muchos años que llevo escribiendo, los lectores me han hecho miles y miles de preguntas del tipo “¿Cómo lo hago?”. Estas preguntas vienen de todas las áreas de la programación de Java y van de las muy fáciles a las muy difíciles. Sin embargo, he encontrado que un núcleo central de preguntas surge una y otra vez. He aquí un ejemplo: “¿Cómo formo la salida?”. He aquí otra: “¿Cómo comprimo un archivo?”. Hay muchas otras. Este mismo tipo de preguntas también se presenta con frecuencia en varios foros de programadores en Web. He utilizado estas preguntas comunes como guía para mi selección de soluciones.
Las soluciones de este libro abarcan varios niveles de habilidad. Algunas ilustran técnicas básicas, como la lectura de bytes de un archivo o la creación de una JTable de Swing. Otras son más avanzadas, como la creación de una servlet o el uso de refl ejo para crear una instancia de un objeto en tiempo de ejecución. Por tanto, el nivel de difi cultad de una solución individual puede ir de relativamente fácil a muy avanzada. Por supuesto, casi todo en programación es fácil una vez que sabe cómo hacerlo, pero es difícil cuando no lo sabe. Por tanto, no se sorprenda si algunas soluciones parecen obvias. Eso sólo signifi ca que ya sabe cómo realizar esa tarea.
¿Cómo están organizadas las soluciones?
Cada solución de este libro sigue el mismo formato, que tiene las siguientes partes: • Una descripción del problema que resuelve.
• Una tabla de componentes clave usados. • Los pasos necesarios para completar la solución. • Un análisis a profundidad de los pasos.
• Un ejemplo de código que pone la solución en acción. • Opciones que sugieren otras maneras de llegar a la solución.
Una solución empieza por describir la tarea que se realizará. Los componentes clave empleados se muestran en una tabla. Ésta incluye las clases de API, las interfaces y los métodos necesarios para crear una solución. Por supuesto, tal vez para ponerla en práctica se requiera el uso de elementos adicionales, pero los componentes clave son los fundamentales para la tarea a mano.
Cada solución presenta después instrucciones paso a paso que resumen el procedimiento. Estas son seguidas por un análisis a profundidad de los pasos. En muchos casos el resumen bastará, pero los detalles están allí, si los necesita.
A continuación, se presenta un ejemplo de código que muestra la solución en acción. Todos los ejemplos de código se presentan completos. Esto evita la ambigüedad y le permite ver con precisión y claramente lo que está sucediendo sin tener que llenar los detalles adicionales. En ocasiones, se incluye un ejemplo extra que ilustra un poco más cómo puede aplicarse la solución.
Cada una concluye con un análisis de varias opciones. Esta sección resulta especialmente importante porque sugiere diferentes maneras de implementar una solución u otras maneras de pensar acerca del problema.
Unas cuantas palabras de precaución
Hay unos cuantos elementos importantes que debe tener en cuenta cuando use este libro.
En primer lugar, se muestra una manera de llegar a una solución. Es probable (y a menudo sucede) que haya otras maneras. Es probable que su aplicación específi ca requiera un método diferente del mostrado. Las soluciones de este libro pueden servir como puntos de partida y ayudarle a elegir un acercamiento general a una solución, además de que pueden despertar su imaginación. Sin embargo, en todos los casos, debe determinar lo que es apropiado y lo que no lo es para su aplicación.
En segundo lugar, es importante comprender que los ejemplos de código no tienen un
rendimiento óptimo. En realidad, están optimizados para ser claros y de fácil comprensión. El objetivo es ilustrar de manera evidente los pasos de la solución. En muchos casos tendrá pocos problemas para escribir un código más condensado y efi ciente. Además, los ejemplos no son más que eso: ejemplos. Hay usos simples que no refl ejan necesariamente la manera en que escribirá código para su propia aplicación. En todas las circunstancias, debe crear una solución propia que satisfaga las necesidades de su aplicación.
En tercer lugar, cada ejemplo contiene un manejo de errores que resulta apropiado para ese ejemplo específi co, pero que tal vez no lo sea en otras situaciones. En todos los casos, debe manejar de manera apropiada los diversos errores y excepciones que pueden producirse cuando adapta una solución para usarla en su propio código. Permítame dejar en claro de nuevo este punto importante: cuando se implementa una solución, debe proporcionar un manejo apropiado de errores para su aplicación. No puede suponer simplemente que la manera en que se manejan (o no se manejan) los errores o las excepciones en un ejemplo es sufi ciente o adecuada para su uso. Por lo general, en las aplicaciones reales se requerirá el manejo de excepciones.
Es necesaria experiencia en Java
Este libro está dirigido a todos los programadores en Java, sean principiantes o profesionales con experiencia. Sin embargo, se supone que conoce los fundamentos de la programación en Java, incluidos palabras clave de Java, sintaxis y clases de API básicas. También debe tener la capacidad de crear, compilar y ejecutar programas en Java. Nada de esto se enseña en esta obra. (Como ya se explicó, este libro trata sobre la aplicación de Java a diversos problemas de programación reales.
No busca enseñar los fundamentos del lenguaje Java). Si necesita mejorar sus habilidades en Java, recomiendo mi libro Java: Manual de referencia, séptima edición. Publicado por McGraw-Hill.
¿Qué versión de Java?
Como muchos lectores lo saben, Java se encuentra en un estado de evolución constante desde su creación. Con cada nueva versión, se agregan características. En muchos casos, con cada nueva versión también se vuelven obsoletas otras características. Como resultado, no todo el código moderno de Java puede compilarse en un compilador antiguo de Java. Esto resulta importante porque el código de este libro se basa en Java SE 6, que (al momento de escribir el libro) es la versión actual de Java. El kit del desarrollador para Java SE 6 es JDK 6. También es el JDK usado para probar todos los ejemplos de código.
Como tal vez ya lo sepa, a partir de JDK 5 se agregaron varias características importantes a Java. Entre éstas se incluyen genéricos, enumeraciones y autoencuadre. Algunas de las técnicas de este libro emplean estas características. Si está utilizando una versión de Java anterior a JDK 5, entonces no podrá compilar los ejemplos que utilizan estas nuevas características. Por tanto, se recomienda mucho que utilice una versión moderna de Java.
Trabajo con cadenas
y expresiones regulares
5
2
CAPÍTULO
U
na de las tareas de programación más comunes es el manejo de cadenas. Casi todos losprogramas tratan con cadenas, de una forma u otra, porque suelen ser el conducto por el cual los seres humanos interactúan con la información digital. Debido a la parte importante que juega el manejo de cadenas, Java le proporciona amplio soporte.
Como lo saben todos los programadores que usan Java, la clase más importante para trabajar con cadenas es String. Proporciona un amplio conjunto de métodos para el manejo de cadenas. Muchos de estos métodos proporcionan las operaciones de cadena básicas con que están familiarizados la mayoría de los programadores que usan Java. Entre éstos se incluyen métodos que comparan dos cadenas, buscan la aparición de una cadena en otra, etc. Sin embargo, String también contiene varios métodos menos conocidos que aumentan de manera importante sus opciones, porque operan con expresiones regulares. Una expresión regular defi ne un patrón general, no una secuencia específi ca de caracteres. Este patrón también puede usarse para buscar subcadenas que coincidan con un patrón. Se trata de un concepto poderoso que está revolucionando la manera en que los programadores que usan Java piensan en el manejo de cadenas.
Java empezó a proporcionar soporte a expresiones regulares en la versión 1.4. Las expresiones regulares están soportadas por la API de expresiones regulares, que está empaquetada en java. util.regex. Como se acaba de explicar, las expresiones regulares también tienen soporte en varios métodos de String. Con la adición de las expresiones regulares, se han facilitado muchas tareas de manejo de cadenas que, de otra manera, serían difíciles.
Este capítulo contiene soluciones que ilustran varias técnicas de manejo de cadenas que van más allá de las operaciones básicas de búsqueda, comparación y reemplazo encontradas en String. Varias también usan expresiones regulares. En algunos casos, se emplean las capacidades de expresión regular de String. En otros se usa la propia API de expresiones regulares.
He aquí las soluciones incluidas en este capítulo: • Ordene una matriz de cadenas de manera inversa
• Ignore las diferencias entre mayúsculas y minúsculas cuando ordene una matriz de cadenas • Ignore las diferencias entre mayúsculas y minúsculas cuando busque o reemplace
subcadenas
• Divida una cadena en partes empleando split( ) • Recupere pares clave/valor de una cadena
• Compare y extraiga subcadenas empleando la API de expresiones regulares • Divida en fi chas una cadena empleando la API de expresiones regulares
Una revisión general de las clases de cadena de Java
Una cadena es una secuencia de caracteres. A diferencia de otros lenguajes de programación, Java no implementa las cadenas como matrices de caracteres. En cambio, las implementa como objetos. Esto le permite a Java defi nir una serie rica de métodos que actúan sobre las cadenas. Aunque éstas son un territorio familiar para casi todos los programadores que usan Java, aún es útil revisar sus atributos y capacidades clave.
Casi todas las cadenas que usará en un programa son objetos de tipo String. String es parte de java.lang. Por tanto, queda a disposición automáticamente de todos los programas en Java. Uno de los aspectos más interesantes de String es que crea cadenas inmutables. Esto signifi ca que una vez que se crea una instancia de String, no puede modifi carse su contenido. Aunque ésta parece una restricción importante, no lo es. Si necesita cambiar una cadena, simplemente cree una nueva que contenga la modifi cación. La cadena original permanecerá sin cambios. Si ya no se necesita ésta, descártela. La cadena que no se usa será reciclada la próxima vez que se ejecute el recolector de basura. Mediante el uso de cadenas inmutables, String puede implementarse de manera más efi ciente de lo que sería si se usara una modifi cable.
Es posible crear cadenas de varias maneras. Puede construir explícitamente una cadena al usar uno de los constructores de String. Por ejemplo, hay constructores que crean una instancia de String a partir de una matriz de caracteres, una matriz de bytes u otra cadena. Sin embargo, la manera más fácil de crear una cadena consiste en usar una literal de cadena, que es una cadena entre comillas. Todas las literales de cadena son automáticamente objetos de tipo String. Por tanto, una literal de cadena puede asignarse a una referencia a String, como se muestra aquí:
String cad = "Prueba";
Esta línea crea un objeto de String que contiene la palabra "Prueba" y luego le asigna a cad una referencia a ese objeto.
String sólo soporta un operador: +. Éste une dos cadenas. Por ejemplo, String cadA = "Hola,";
String cadB = " allá"; String cadC = cadA + cadB;
Esta secuencia da como resultado cadC, que contiene la secuencia "Hola, allá".
String defi ne varios métodos que operan en cadenas. Debido a que la mayoría de los lectores tienen por lo menos una familiaridad pasable con String, no es necesaria una descripción
detallada de todos sus métodos. Más aún, las soluciones de este capítulo describen por completo los métodos de String que emplean. Sin embargo, es útil revisar las capacidades centrales de manejo de cadenas de String al agruparlas en categorías.
String defi ne los siguientes métodos que buscan el contenido de una cadena en otra: contains Devuelve verdadero si una cadena contiene otra.
endsWith Devuelve verdadero si una cadena termina con una cadena específica.
indexOf Devuelve el índice dentro de una cadena en que se encuentra la primera aparición de otra cadena. Devuelve –1 si no se encuentra la cadena.
lastIndexOf Devuelve el índice dentro de la cadena que invoca en que se encuentra la última aparición de la cadena especificada. Devuelve –1 si no se encuentra la cadena. startsWith Devuelve verdadero si una cadena empieza con una cadena específica.
Los siguientes métodos comparan una cadena con otra:
compareTo Compara una cadena con otra.
compareToIgnoreCase Compara una cadena con otra. Se ignoran las diferencias entre mayúsculas y minúsculas.
contentEquals Compara una cadena con una secuencia específica de caracteres. equals Devuelve verdadero si dos cadenas contienen la misma
secuencia de caracteres.
equalsIgnoreCase Devuelve verdadero si dos cadenas contienen la misma secuencia de caracteres. Se ignoran las diferencias entre mayúsculas y minúsculas.
matches Devuelve verdadero si una cadena coincide con una expresión regular específica.
regionMatches Devuelve verdadero si la región especificada de una cadena coincide con la región especificada de otra.
Cada uno de los métodos dentro del siguiente grupo reemplaza una parte de una cadena con otra:
replace Reemplaza todas las apariciones de un carácter o una subcadena con otra.
replaceFirst Reemplaza la primera secuencia de caracteres que coincide con una expresión regular específica.
replaceAll Reemplaza todas las secuencias de caracteres que coinciden con una expresión regular específica.
Los siguientes dos métodos cambian las mayúsculas y minúsculas de las letras dentro de una cadena:
toLowercase Convierte la cadena a minúsculas. toUpperCase Convierta la cadena a mayúsculas.
Además de los métodos de manejo de cadena centrales que acabamos de describir, String defi ne otros más. Dos de uso muy común son length( ), que devuelve el número de caracteres en una cadena, y charAt( ), que devuelve el carácter en un índice específi co.
En su mayor parte, las soluciones de este capítulo usan String, y suelen ser su mejor opción cuando trabaja con cadenas. Sin embargo, en los pocos casos en que necesita que se modifi que una cadena, Java ofrece otras dos opciones. La primera es StringBuffer, que ha sido parte de Java desde el principio. Es similar a String, excepto que permite cambiar el contenido de una cadena.
Por tanto, proporciona métodos, como setCharAt( ) e insert( ), que modifi can la cadena. La segunda opción es la más nueva StringBuilder, que se agregó a Java en la versión 1.5. Resulta
similar a StringBuffer, excepto que no es segura para subprocesos. Por tanto, es más efi ciente cuando no se usan multiprocesamientos. (En aplicaciones con multiprocesamientos, debe usar StringBuffer, porque es segura para subprocesos). Tanto StringBuffer como StringBuilder están empaquetados en java.lang.
La API de expresiones regulares de Java
Las expresiones regulares tienen soporte en Java con las clases Matcher y Pattern, que están empaquetadas en java.util.regex. Estas clases funcionan juntas. Utilizará Pattern para defi nir una expresión regular. Comparará el patrón contra otra sección empleando Matcher. Los procedimientos precisos se describen en las soluciones en que se usan.
Las expresiones regulares también se usan en otras partes de la API de Java. Tal vez lo más importante sea que varios métodos de String, como split( ) y matches( ), aceptan una expresión regular como argumento. Por tanto, con frecuencia usará una expresión regular sin usar explícitamente Pattern o Matcher.
Varias de las soluciones de este capítulo usan expresiones regulares. La mayor parte de ellas lo hacen mediante métodos de String, pero tres de ellas usan explícitamente Pattern y Matcher. Para tener un control detallado del proceso de comparación, a menudo es necesario usar Pattern y Matcher. Sin embargo, en muchos casos la funcionalidad de expresiones regulares que proporciona String es sufi ciente y más conveniente.
Varios métodos que usan expresiones regulares lanzarán una excepción
PatternSyntaxException cuando se hace un intento por usar una expresión regular sintácticamente incorrecta. Esta excepción se defi ne mediante la API de expresiones regulares y está empaquetada en java.util.regex. Necesitará manejar esta excepción de una manera apropiada en su aplicación.
Introducción a las expresiones regulares
Antes de que pueda usar expresiones regulares, debe comprender cómo están construidas. Si es nuevo en las expresiones regulares, entonces esta revisión general le ayudará a iniciarse en ellas. Antes de seguir, es importante establecer que el tema de las expresiones regulares es más bien amplio. En realidad, se han escrito libros completos sobre ellas. Está más allá del alcance de este libro describirlas de manera detallada. En cambio, aquí se presenta una breve introducción que incluye sufi ciente información para que comprenda los ejemplos de las soluciones. También le permitirá empezar a experimentar con expresiones regulares propias. Sin embargo, si las usa de manera reiterada, entonces tendrá que estudiarlas con mucho mayor detalle.
Tal como se usa aquí el término, una expresión regular es una cadena de caracteres que describe un patrón. Un patrón comparará cualquier secuencia de caracteres que satisfaga el patrón. Por tanto, éste constituye una forma general que coincidirá con diversas secuencias específi cas. En conjunto con un motor de expresiones regulares (como las proporcionadas por la API de expresiones regulares de Java), puede usarse un patrón para buscar coincidencias en otra secuencia de caracteres. Es esta capacidad la que da a las expresiones regulares su poder cuando se manipulan cadenas.
Una expresión regular consta de uno o más de los siguientes elementos: caracteres normales, clases de caracteres (conjuntos de caracteres), el carácter comodín, cuantifi cadores, comparadores de límites, operadores y grupos. Aquí se examinará cada uno de manera breve.
NOTA Hay cierta variación en la manera en que los diferentes motores de expresiones regulares manejan
éstas. En este análisis se analiza la implementación de Java.
Caracteres normales
Un carácter normal (es decir una literal de carácter) se compara tal cual. Por tanto, si un patrón consta de xy, entonces la única secuencia de entrada con la que coincidirá es "xy". Caracteres como nueva línea y tabulador se especifi can empleando secuencias de escape, que empiezan con una \. Por ejemplo, una nueva línea se especifi ca como \n.
Clases de caracteres
Una clase de caracteres es un conjunto de caracteres. Una clase de caracteres se especifi ca al poner los caracteres en la clase entre corchetes. Una clase coincidirá con cualquier carácter que sea parte de la clase. Por ejemplo, la clase [wxyz] buscará coincidencias de w, x, y o z. Para especifi car un conjunto invertido, anteceda los caracteres con un ^. Por ejemplo, [^wxyz] buscará coincidencias con cualquier carácter, excepto w, x, y o z. Puede especifi car un rango de caracteres empleando un guión. Por ejemplo, para especifi car una clase de caracteres que coincida con los dígitos 1 a 9, use [1–9]. Una clase puede contener dos o más rangos con sólo especifi carlos. Por ejemplo, la clase [0–9A–Z] busca todos los dígitos y las letras mayúsculas, de la A a la Z.
La API de expresiones regulares de Java proporciona varias clases predefi nidas. He aquí algunas de las de uso más común:
Clase predefinida Coincide con
\d Los dígitos del 0 al 9.
\D Todos los caracteres que no son dígitos.
\s Espacio en blanco.
\S Todo lo que no es un espacio en blanco.
\w Caracteres que pueden ser parte de una palabra. En Java, son las letras mayúsculas y minúsculas, los dígitos del 0 al 9 y los guiones de subrayado. Suele denominárseles caracteres de palabra.
\W Todos los caracteres que no son de palabra.
Además de estas clases, Java proporciona una amplia cantidad de clases de caracteres adicionales que tienen la siguiente forma general:
\p{nombre}
Aquí, nombre especifi ca el nombre de la clase. He aquí algunos ejemplos:
\p{Lower} Contiene las letras minúsculas.
\p{Upper} Contiene las letras mayúsculas.
\p{Punct} Contiene todos los signos de puntuación.
Hay otros más. Debe consultar la documentación de la API para conocer las clases de caracteres que soporta su JDK.
Una clase puede contener otra. Por ejemplo, [[abc][012]] defi ne una clase que buscará
conjuntos. Por supuesto, este ejemplo podría escribirse de manera más conveniente como [abc012]. Sin embargo, las clases anidadas son muy útiles en otros contextos, como cuando se trabaja con conjuntos predefi nidos o cuando quiere crear la intersección de dos conjuntos.
Para crear una clase que contenga la intersección de dos o más conjuntos de caracteres, use el operador &&. Por ejemplo, esto crea un conjunto que busca coincidencias de todos los caracteres de palabra, excepto para las letras mayúsculas [\w && [^A–Z]].
Otros dos puntos: La parte exterior de una clase de caracteres, – se trata como un carácter normal. Asimismo, la parte exterior de una clase, la ^ se usa para especifi car el inicio de una línea, como se describe en breve.
El carácter comodín
El carácter comodín es . (punto) y coincide con cualquier carácter. Por tanto, un patrón que consta de un . buscará coincidencias de estas (y otras) secuencias de entrada: "A", "a", "x" y "!" En esencia, el punto es una clase predefi nida que coincide con todos los caracteres.
Para crear un patrón que busque coincidencias con un punto, anteceda éste con una \. Por ejemplo, dada esta cadena de entrada.
Final del juego. esta expresión juego\.
busca coincidencias con la secuencia "juego".
Cuantifi cadores
Un cuantifi cador determina cuántas veces se buscarán coincidencias con una expresión. A continuación se muestran los cuantifi cadores:
+ Busca una o más coincidencias. * Busca cero o más coincidencias. ? Busca cero o una coincidencia.
Por ejemplo, x+ buscará una o más x, como "x", "xx", "xxx", etc. El patrón .* buscará coincidencias de cualquier carácter cero o más veces. El patrón ,? buscará cero o una coma.
También puede especifi car un cuantifi cador que buscará coincidencias de un patrón un número específi co de veces. He aquí una forma general:
{núm}
Por tanto, x{2} encontrará "xx", pero no "x" o "xxx". Puede especifi car que se busquen coincidencias de un patrón por lo menos un número mínimo de veces al usar este cuantifi cador:
{mín,}
Por ejemplo, x{2,} buscará xx, xxx, xxxx, etc.
Puede especifi car que se busques coincidencias de un patrón por lo menos un número mínimo de veces, pero no más de un número máximo usando este cuantifi cador:
Cuantifi cadores avaros, renuentes y posesivos
En realidad hay tres tipos de cuantifi cadores: avaros, renuentes y posesivos. Los ejemplos de cuantifi cadores que se acaban de mostrar son de la variedad avara. Encuentran la secuencia coincidente más larga. Un cuantifi cador renuente (también denominado cuantifi cador holgazán) encuentra la secuencia coincidente más corta. Para crear uno renuente, incluya al fi nal un ? Un cuantifi cador posesivo busca la secuencia coincidente más larga y no encontrará una secuencia más corta, aunque habilite toda la expresión a fi n de tener éxito. Para crear un cuantifi cador posesivo, coloque un + al fi nal.
Recorramos ejemplos de cada tipo de cuantifi cador que trata de encontrar una coincidencia en la cadena "sarape simple". El patrón s.+e buscará coincidencias con la secuencia más larga, que es toda la cadena "sarape simple", porque el cuantifi cador avaro .+ buscará coincidencias con todos los caracteres después de la primera s hasta la e fi nal.
El patrón s.+?e encontrará "sarape", que es la coincidencia más corta. Esto se debe a que el cuantifi cador renuente .+? se detendrá después de encontrar la primera secuencia coincidente.
El patrón s.++e fallará, porque el cuantifi cador posesivo .++ encontrará todos los
caracteres coincidentes después de la s inicial. Debido a que es posesivo, no liberará la e fi nal para permitir coincidencias con el patrón general. Por tanto, no se encontrará la e fi nal y la coincidencia fallará.
Comparadores de límites
En ocasiones querrá especifi car un patrón que empieza o termina en algún límite, como al fi nal de una palabra o el principio de una línea. Para ello, usará los comparadores de límites. Tal vez los comparadores de límites de uso más amplio sean ^ y $. Encuentran coincidencias en el inicio y el fi nal de la línea en que se está buscando, que como opción predeterminada son el principio y el fi nal de la cadena de entrada. Por ejemplo, dada la cadena "prueba1 prueba2", el patrón prueba.?$ encontrará "prueba2", pero el patrón ^prueba.? encontrará "prueba1". Si quiere que encuentre una coincidencia con uno de estos caracteres por sí solo, necesitará usar la secuencia de escape \^ o \$.
Aquí se muestran los demás comparadores de límites.
Comparador Coincide con
\A Inicio de cadena
\b Límite de palabra
\B Límite que no es de palabra \G Fin de la coincidencia anterior
\Z Final de la cadena (no incluye el terminador de línea) \z Final de la cadena (incluye el terminador de línea)
El operador O
Cuando creamos un patrón, puede especifi car una o más opciones al usar el operador O, que es |. Por ejemplo, la expresión puede|podría buscará la palabra "puede" o la palabra "podría". A menudo el operador O se usa dentro de un grupo entre paréntesis. Para comprender la razón, imagine que quiere encontrar todos los usos de "puede" o "podría" además de cualquier palabra que se encuentre junto a ellas. He aquí una manera de componer una expresión que hace esto: \w+\s+(puede | podría)\s+\w+\b
Dada la cadena
Ella podría ir. No puede ahora.
esta expresión regular encuentra estas dos coincidencias: Ella podría ir
No puede ahora
Si se eliminan los paréntesis alrededor de |, como en \w+\s+puede | podría\s+\w+\b
la expresión encontraría estas dos coincidencias: podría ir
No puede
La razón es que el | ahora separa a toda la subexpresión \w\s+puede de la subexpresión podría \s+\w+\b. Por tanto, toda la expresión coincidirá con frases que empiezan con alguna palabra seguida de "puede" o frases que empiezan con "podría" seguida por alguna palabra.
Grupos
Un grupo se crea al incluir un patrón dentro de paréntesis. Por ejemplo, la expresión entre paréntesis (puede | podría) en la sección anterior forma un grupo. Además de vincular los elementos de una subexpresión, los grupos tienen un segundo propósito. Una vez que ha defi nido un grupo, otra parte de una expresión regular puede hacer referencia a la secuencia capturada por ese grupo. Cada conjunto de paréntesis defi ne un grupo. El paréntesis de apertura del extremo izquierdo defi ne al grupo uno, el siguiente paréntesis de apertura defi ne al grupo dos, etc. Dentro de una expresión regular, se hace referencia a los grupos por número. El primer grupo es \1, el segundo \2, etcétera.
Trabajemos con un ejemplo. Suponga que quiere encontrar frases dentro de la misma oración en que se usan las formas singular y plural de una palabra. Por razones de simplicidad, también suponga que sólo quiere encontrar plurales, que siempre terminan con s. Por ejemplo, dadas estas frases
Tengo un perro, pero él tiene cuatro perros.
Ella tiene un gato, ¿pero quiere una buena cantidad de gatos? Ella también tiene un perro. Pero no quisiera tener cuatro perros.
quiere encontrar la frase "perro, pero él tiene cuatro perros" porque contiene una forma singular y una plural de perro dentro de la misma oración y la frase "gato, ¿pero quiere una buena cantidad de gatos?", porque tiene gato y gatos dentro de la misma oración. No quiere encontrar instancias que abarquen dos o más oraciones, de modo que no querrá encontrar las formas "perro" y "perros" contenidos en las dos últimas frases. He aquí una manera de escribir una expresión regular que haga esto.
\b(\w+)\b[^.?!]*?\1s
Aquí, la expresión entre paréntesis (\w+) crea un grupo que contiene una palabra. Este grupo se usa después para buscar entradas coincidentes posteriores cuando se hace referencia a él con \1. Cualquier número de caracteres puede encontrarse entre la palabra y su plural, siempre y cuando no se localicen terminadores de oración (.?!). Por tanto, el resto de la expresión sólo tiene éxito
cuando se encuentra una palabra posterior dentro de la misma oración que sea el plural de la palabra que está contenida en \1. Por ejemplo, cuando se aplica a la primera oración de ejemplo, (\w+) encontrará palabras coincidentes, poniendo la palabra en el grupo \1. Para que toda la expresión tenga éxito, una palabra posterior en la oración debe ser igual a la palabra contenida en \1 y debe ir seguida de inmediato por una s. Esto sólo ocurre cuando \1 contiene la palabra "perro", porque "perros" se encuentra después en la misma oración.
Un tema adicional. Puede crear un grupo de no captura al añadir ?: después de los paréntesis de apertura, como en (?:\s*). Son posibles otros tipos de grupos que usan búsqueda hacia delante o hacia atrás positiva o negativa, pero están más allá del alcance de este libro.
Secuencias de marcas
El motor de expresiones regulares de Java soporta varias opciones que controlan la manera en que se buscan coincidencias de un patrón. Estas opciones se establecen o limpian al usar la siguiente construcción: (?f), donde f especifi ca que se establezca una marca. Hay seis marcas, que se muestran aquí:
d Habilita el modo de línea de Unix.
i Ignora las diferencias entre mayúsculas y minúsculas.
m Habilita el modo multilínea, en que ^ y $ buscan coincidencias al principio y el final de las líneas, en lugar de toda la cadena de entrada.
s Habilita el modo "punto en todo", que hace que el punto (.) busque coincidencias de todos los caracteres, incluido el terminador de línea.
u Junto con i, causa que se hagan coincidencias no sensibles a mayúsculas y minúsculas de acuerdo con el estándar de Unicode, en lugar de suponer sólo caracteres ASCII.
x Ignora espacios en blanco y comentarios con # en una expresión regular.
Puede deshabilitar un modo al anteceder su marca con un signo de menos. Por ejemplo, (?–i) deshabilita la coincidencia no sensible a mayúsculas y minúsculas.
Recuerde incluir el carácter de escape \ en cadenas de Java
Un breve recordatorio antes de pasar a las soluciones. Cuando se crean cadenas de Java que contienen expresiones regulares, recuerde que debe usar la secuencia de escape \\ para especifi car una \. Por tanto, la siguiente expresión regular
\b\w+\b
Debe escribirse así cuando se especifi que como una literal de cadena en un programa de Java: "\\b\\w+\\b"
Olvidar la inclusión del carácter de escape \ es una fuente común de problemas porque no siempre da como resultado errores en tiempo de compilación o de ejecución. En cambio, su expresión regular simplemente no encontrará coincidencias donde pensaba que las hallaría. Por ejemplo, si usa \b en lugar de \\b en la cadena anterior verá que una expresión trata de buscar coincidencias del carácter de retroceso, en lugar de utilizarse como límite de palabra.
Ordenar es una tarea común en programación, y ordenar matrices de cadenas no es la excepción. Por ejemplo, tal vez quiera ordenar una lista de los artículos vendidos por una tienda en línea o una lista de nombres de clientes y direcciones de correo electrónico. Por fortuna, Java facilita el ordenamiento de matrices de cadenas porque proporciona el método de utilería sort( ), que está defi nido por la clase Arrays en java.util. En su forma predeterminada, sort( ) ordena cadenas en orden alfabético, sensible a mayúsculas y minúsculas, y esto es adecuado por muchas situaciones. Sin embargo, en ocasiones querrá ordenar una matriz de cadenas en orden alfabético inverso. Esto requiere un poco más de trabajo.
Hay varias maneras de tratar el problema de ordenar a la inversa. Por ejemplo, una solución inocente consiste en ordenar la matriz y luego copiarla de atrás hacia delante en otra matriz. Además de carecer de elegancia, esta técnica también es inefi ciente. Por fortuna, Java proporciona una manera simple, pero efectiva de ordenar a la inversa una matriz de cadenas. Este método usa un Comparator personalizado para especifi car la manera en que debe aplicarse el orden y una versión de sort( ) que toma Comparator como argumento.
Paso a paso
Para ordenar una matriz de cadenas a la inversa se requieren tres pasos:
1. Cree un Comparator que invierte la salida de una comparación entre dos cadenas. 2. Cree un objeto de ese Comparator.
3. Pase la matriz que se ordenará y el Comparator a una versión de java.util.Arrays.sort( ) que tome un comparador como argumento. Cuando sort( ) regrese, la matriz se ordenará a la inversa.
Análisis
Comparator es una interfaz genérica que se declara como se muestra aquí: Comparator<T>
Ordene una matriz de cadenas de manera inversa
Componentes clave
Clases e interfaces Métodos
java.lang.String int compareTo(String cad) java.util.Arrays static <T> void sort(T[ ] matriz,
Comparator<? Super T> comp) java.util.Comparator<T> int compare(T objA, T objB)
El parámetro de tipo T especifi ca el tipo de datos que se habrá de comparar. En este caso, String se pasará a T.
Comparator defi ne los dos métodos siguientes: int compare(T objA, T objB)
boolean equals(Object obj)
De éstos, sólo compare( ) debe implementarse. El método equals( ) simplemente especifi ca una sobreescritura de equals( ) en Object. La implementación de equals( ) le permite determinar si dos Comparator son iguales. Sin embargo, esta capacidad no siempre es necesaria. Cuando no se necesita (como sucede en este capítulo), no es necesario sobreescribir la implementación de Object.
El método en que estamos interesados es compare( ). Determina la manera en que se compara un objeto con otro. Por lo general, debe devolver menos de cero si objA es menor que objB, más que cero si objA es mayor que objB y cero si los dos objetos son iguales. Al implementar compare( ) de esta manera se logra que opere de acuerdo con el orden natural de los datos. En el caso de cadenas, esto signifi ca orden alfabético. Sin embargo, tiene la libertad de implementar compare( ) para adecuarse a las necesidades de su tarea. Para ordenar a la inversa una matriz de cadenas, necesitará crear una versión de compare( ) que invierta la salida de la comparación.
He aquí la manera de implementar un operador inverso para String. // Crea un Comparator que devuelve la salida
// de una comparación de cadena inversa.
class CompCadInv implements Comparator<String> { // Implementa el método compare( ) de modo que // invierte el orden de la comparación de la cadena. public int compare(String cadA, String cadB) {
// Compara cadB con cadA, en lugar de cadA con cadB. return cadB.compareTo(cadA);
} }
Revisemos de cerca CompCadInv. En primer lugar, observe que implementa Comparator. Esto signifi ca que un objeto de tipo CompCadInv se puede usar en cualquier lugar que se necesite un Comparator. Asimismo, observe que implementa una versión específi ca de String de Comparator. Por tanto, CompCadInv no es, en sí, genérico. Sólo funciona con cadenas.
Ahora, observe que el método compare( ) llama al método compareTo( ) de String para comparar dos cadenas. compareTo( ) es especifi cada por la interfaz Comparable, que está
implementada por String (y muchas otras clases). Una clase que implementa Comparable garantiza que los objetos de esa clase pueden ordenarse. A continuación se muestra la forma general de compareTo( ), como la implementa String:
int compareTo(String cad)
Devuelve menos de cero si la cadena de invocación es menor que cad, más de cero si es mayor que cad y cero si son iguales. Una cadena es menor que otra si se encuentra antes en el orden alfabético y es mayor si se encuentra después.
El método compare( ) de CompCadInv devuelve el resultado de la llamada a compareTo( ). Sin embargo, observe que compare( ) llama a compareTo( ) en orden inverso. Es decir, se llama a compareTo( ) en cadB mientras cadA se pasa como argumento. Para una comparación normal, cadA invocaría a compareTo( ), pasando cadB. Sin embargo, como cadB invoca a compareTo( ), se invierte el resultado de la comparación. Por tanto, se invierte el orden de las dos cadenas.
Una vez que ha creado un comparador inverso, se crea un objeto de ese comparador y se pasa a esta versión de sort( ) defi nida por java.util.Arrays:
static<T> void sort(T[ ] matriz, Comparator<? Super T> comp)
Observe la cláusula super. Asegura que la matriz pasada a sort( ) sea compatible con el tipo de Comparator. Después de la llamada a sort( ), la matriz estará en orden alfabético invertido.
Ejemplo
En el siguiente ejemplo se invierte el orden de una matriz de cadenas. Para fi nes de demostración, también se les ordena de manera natural empleando la versión predeterminada de sort( ).
// Ordena una matriz de cadenas en orden inverso. import java.util.*;
// Crea un Comparator que devuelve la salida // de una comparación de cadena inversa.
class CompCadInv implements Comparator<String> { // Implementa el método compare( ) de modo que // invierte el orden de la comparación de la cadena. public int compare(String cadA, String cadB) {
// Compara cadB con cadA, en lugar de cadA con cadB. return cadB.compareTo(cadA);
} }
// Demuestra el comparador de cadena inverso. class OrdenCadInv {
public static void main(String args[ ]) { // Crea una matriz simple de cadenas.
String cads[ ] = { "perro", "caballo", "cebra", "vaca", "gato" }; // Muestra el orden inicial.
System.out.print("Orden inicial: "); for(String s : cads)
System.out.print(s + " "); System.out.println("\n");
// Ordena la matriz a la inversa.
// Empieza por crear un comparador de cadena inversa. CompCadInv cci = new CompCadInv( );
// Ahora, ordena las cadenas empleando el comparador inverso. Arrays.sort(cads, cci);
// Muestra el orden inverso.
System.out.print("Orden inverso: "); for(String s : cads)
System.out.print(s + " "); System.out.println("\n");
// Para comparación, ordena la cadena de manera natural. Arrays.sort(cads);
// Muestra el orden natural.
System.out.print("Orden natural: "); for(String s : cads) System.out.print(s + " "); System.out.println("\n"); } }
A continuación se muestra la salida de este programa: Orden inicial: perro caballo cebra vaca gato Orden inverso: vaca perro gato cebra caballo Orden natural: caballo cebra gato perro vaca
Opciones
Aunque esta solución ordena cadenas en orden alfabético inverso, la misma técnica básica puede generalizarse para otras situaciones. Por ejemplo, puede revertir el orden de otros tipos de datos al crear el Comparator apropiado. Simplemente adapta el método mostrado en el ejemplo.
El método compareTo( ) defi nido por String es sensible a mayúsculas y minúsculas. Esto signifi ca que ambos tipos de letra se ordenarán por separado. Tiene la opción de ordenar datos sin importar las diferencias entre mayúsculas y minúsculas al emplear compareToIgnoreCase( ). (Consulte Ignore las diferencias entre mayúsculas y minúsculas cuando ordene una matriz de cadenas).
Puede ordenar cadenas con base en alguna subcadena específi ca. Por ejemplo, si cada cadena contiene un nombre y una dirección de correo electrónico, entonces puede crear un comparador que ordene a partir de la parte de la dirección de cada cadena. Una manera de realizar esto consiste en usar el método regionMatches( ). También puede ordenar por algún criterio diferente de una estricta relación alfabética. Por ejemplo, cadenas que representan tareas pendientes pueden ordenarse por prioridad.
En Java, el orden natural de las cadenas es sensible a mayúsculas y minúsculas. Esto signifi ca que las letras mayúsculas están separadas y son diferentes de las minúsculas. Como resultado, cuando ordena una matriz de cadenas, podrían ocurrir algunas sorpresas que no son bienvenidas. Por ejemplo, si ordena una matriz String que contiene las siguientes palabras:
alfa beta Gama Zeta
El orden resultante será como se muestra aquí: Gama Zeta alfa beta
Como verá, aunque Gama y Zeta normalmente se encontrarían después de alfa y beta, están al principio de la matriz ordenada. La razón es que, en Unicode, las mayúsculas están representadas por valores menores que los usados para las minúsculas. Por tanto, aunque Zeta se encontraría normalmente al fi nal de la lista cuando se ordena alfabéticamente, se encuentra antes de alfa cuando son importantes las diferencias entre mayúsculas y minúsculas. ¡Esto puede llevar a órdenes que producen resultados técnicamente exactos, pero indeseables!
NOTA Como algo interesante hay que mencionar que una mayúscula vale exactamente 32 menos que su
equivalente en minúsculas. Por ejemplo, el valor de Unicode para la A es 65. Y para la a es 97. Por fortuna, es muy fácil ordenar una matriz de cadenas con base en el verdadero orden alfabético al crear un Comparator que ignore si una letra está en mayúsculas o minúsculas durante el proceso de ordenamiento. La técnica es similar a la descrita en Ordene una matriz de cadenas de manera inversa. Los detalles se describen a continuación.
Paso a paso
Para ignorar las diferencias entre mayúsculas y minúsculas cuando ordene una matriz de cadenas se incluyen estos tres pasos:
1. Cree un Comparator que ignore las diferencias entre mayúsculas y minúsculas de dos cadenas.
2. Cree un objeto de ese Comparator.
Ignore las diferencias entre mayúsculas y minúsculas cuando ordene una matriz de
cadenas
Componentes clave
Clases e interfaces Métodos
java.lang.String int compareToIgnoreCase(String cad) java.util.Arrays static<T> void sort(T[ ] matriz,
Comparator<? super T> comp) java.util.Comparator<T> int compare(T objA, T objB)