Vous êtes sur la page 1sur 2624

PIENSA EN JAVA

Fiaros de catalogacin bibliogrfica PIENSA EN JAVA

Cuarta Edicin

BRUCE ECKEL President, MindView, Inc.

Traduccin Vuelapluma

PEARSON

Fiaros de catalogacin bibliogrfica Prentice

Hall

Madrid Mexico Santa Fe de Bogota Buenos Aires Caracas Lima Montevideo San Juan San Jos Santiago So Paulo White Plains

Todos los derechos reservados.

Queda prohibida, salvo excepcin prevista en la Ley, cualquier forma de reproduccin, distribucin, comunicacin pblica y transformacin de esta obra sin contar con autorizacin de los titulares de propiedad intelectual. La infraccin de los derechos mencionados puede ser constitutiva de delito contra la propiedad intelectual (arts. 270 y sgls. Cdigo Penal).

Fiaros de catalogacin bibliogrfica DERECHOS RESERVADOS 2007 por PEARSON EDUCACIN S.A.

Ri be ra de l L oi ra , 2 8 2 8 0 4 2 M ad ri d PIENSA EN JAVA

Bruce Eckel ISBN: 978-84-8966-034-2

Fiaros de catalogacin bibliogrfica Deposito Legal; M-4.753-2007

PRENTICE HALL es un sello editorial autorizado de PEARSON EDUCACIN S.A.

Authorized translation from the English language edition, entitled THINKING IN JAVA, 4,h Edition by ECKJ-L BRUCE, published by Pearson Education Inc, publishing as Prentice Hall, Copyright 2006 EQUIPO EDITORIAL

Editor: Miguel MartinRomo Tcnico editorial: Marta Caicoya EQUIPO DE PRODUCCIN:

Fiaros de catalogacin bibliogrfica Direct or: Jos A. Clare s Tcni co: Maria Alvea r Diseo de Cubierta: Equipo de diseo de Pearson Educacin S.A. Impreso por: Grficas Rogar, S. A.

IMPRESO EN ESPAA - PRINTED IN SPAIN Este libro ha sido impreso con papel y tintas ecolgicosDedicatoria A Daw nPrefacio

Originalmente, me enfrent a Java como si friera simplemente otro lenguaje ms de programacin, So 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 fundamental 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 complejidad 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 diseo

Fiaros de catalogacin bibliogrfica resolver la complejidad 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 conjunto 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 paite 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) estaba ligado a BASIC, que no haba sido diseado para ser un lenguaje extensiblc, por lo que todas las extensiones aadidas a VB han dado como resultado una sintaxis verdaderamente inmanejable. Perl 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 "cdigo de slo escritura" (es decir, despus de pasado un tiempo se vuelve completamente ilegible). Por otro lado, C++, VB, Perl 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 objetivos hubieran dicho: "Tratemos de reducir el tiempo y la dificultad necesarios para generar cdigo 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-H- 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 programacin, 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 interplalaforma, 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 oportunidades que Java nos proporciona son inmensas, ya que puede 1 Sin embargo, creo que el lenguaje Python es el que ms se acerca a ese objetivo. Consulte wmv.Python.org.

Fiaros de catalogacin bibliogrfica incrementar significativamente nuestra productividad como programadores.

Java incrementa el ancho de banda de comunicacin entre las personas en todos los sentidos: a la hora de crear los programas, a la hora de trabajar en grupo, a la hora de construir interfaces para comunicarse con los usuarios, a la hora de

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 comunicarnos 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 tratar de ensear este lenguaje.

Java SE5 y SE6

Esta edicin del libro aprovecha en buena medida las mejoras realizadas al lenguaje Java en lo que Sun originalmente denomin JDK 1.5 y cambi posteriormente a JDK.5 o J2SE5. Posteriormente, la empresa elimin el obsoleto 2 y cambi el nombre a Java SE5. Muchos de los cambios en el lenguaje 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 decisin de hacer el texto nicamente compatible con Java SE5/6, por lo que buena parte del cdigo del libro

Fiaros de catalogacin bibliogrfica no puede compilarse 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 anteriores de este libro (en ingls) en la direccin mvw.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 si misma, un proyecto de proporciones colosales y al que ha habido que dedicar muchsimo 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 mejoras de la nueva versin se centran en el aumento 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 JavaSE, ese cambio se ver reflejado en el cdigo fuente del libro, que puede descargarse desde www Mind View. 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

Fiaros de catalogacin bibliogrfica 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 edicin 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 deas de una fonna ms adecuada.

Asimismo, siempre se tiene presente, en el fondo de la mente, ese desafo de escribir un libro que los poseedores de las ediciones anteriores estn dispuestos a comprar. Ese desafo 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 tradicionalmentc como parte del libro no ha sido incluido en esta edicin. La parte esencial de dicho CD, el seminario multimedia Thinking in C (creado para MindVicw por Chuck Allison), est ahora disponible como presentacin Flash descargable. El objetivo de dicho seminario consiste en preparar a aquellos que no estn lo suficientemente 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 C trata de ayudar a dichas personas a alcanzar el nivel necesario.

Fiaros de catalogacin bibliogrfica El captulo dedicado a la concurrencia, que antes llevaba por ttulo Programacin multihebra, ha sido completamente reescrito con el fin de adaptarlo a los cambios principales en las bibliotecas de concurrencia de Java SE5, pero sigue proporcionando informacin bsica sobre las ideas fundamentales en las que la concurrencia se apoya. Sin esas ideas fundamentales, resulta difcil 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. Adicionalmentc, siempre he tendido a creer que un tema tena que tener la suficiente envergadura para justificar el dedicarle un captulo. Pero luego he visto, especialmente a la hora de ensear los patrones de diseo, que las personas que asistan a los seminarios obtenan mejores resultados 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 capmlo. 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 pruebas 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 validar la salida de cada programa (dicho marco est escrito en Python. y puede descargarse en www.MindView.net. El tema de las pruebas, en general, se trata en el suplemento disponible en http://www.MindView.net/Books/BetterJava, que presenta lo que creo que son capacidades fundamentales que todos los programadores deberan tener como parte de sus conocimientos bsicos.

Fiaros de catalogacin bibliogrfica 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 limites 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 satisfaccin. Sin embargo, de vez en cuando tambin me llegan algunas quejas y, por alguna razn, una 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

que han llegado 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 original correspondiente contina estando en el sitio web (www.MindView.net), gracias a la versin descargable de las tres primeras ediciones del libro. Asimismo, el lector tiene a su disposicin material adicional en suplementos descargablcs de esta edicin.

En cualquier 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.

Fiaros de catalogacin bibliogrfica Sobre el diseo de la cubierta

La cubierta del libro est inspirada por el movimiento American Arts & Crafts Movement que comenz poco antes del cambio de siglo y alcanz su cnit entre 1900 y 1920. Comenz en Inglaterra como reaccin a la produccin en masa de la revolucin industrial y al estilo altamente ornamental de la poca victoriana. Arts & Crafts enfatizaba el diseo con formas naturales, como en el movimiento art nouvean, 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 evolucin 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 periodo o inspirados en el mismo.

H1 otro tema de la cubierta sugiere una vitrina coleccionista que un naturalista podra emplear para mostrar los especmenes de insectos que ha preservado. Estos insectos son objetos situados dentro de los objetos compartimento. Los objetos compartimento estn a su vez, colocados dentro del objeto cubierta, lo que ilustra el concepto de agregacin dentro de la programacin orientada a objetos. Por supuesto, cualquier programador de habla inglesa 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 finalmente dentro de una vitrina, con lo que tratamos de sugerir la habilidad 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.

Fiaros de catalogacin bibliogrfica 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 Allison, Jeremy Meyer y Jamie King. Agradezco la paciencia que mostris mientras contino tratando de desarrollar el mejor modelo para que una serie de personas 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 tendra 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 sin esos costes adicionales. Dentro de mis intentos 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 Stcucr 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 minuciosidad y tu persistencia. Sharlyim Cobaugh ha llegado a convertirse en una autntica experta en edicin de sonido y ha sido una de las personas esenciales a la hora de crear los cursos de formacin multimedia, adems de ayudar en la resolucin de muchos otros problemas. 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 ftien -te de inspiracin en lo que respecta al proyecto de trabajo a travs de Internet y tambin ha sido imprescindible, por supuesto, 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 mentor 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

Fiaros de catalogacin bibliogrfica y ejemplos. Ervin ha sido el revisor tcnico principal del libro y tambin 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 sorprendentes 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 determinadas ideas. Gracias a los lectores que me han ayudado a aclarar los conceptos enviando sus comentarios; entre esos lectores 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 configuracin y mantenimiento del servidor web basados en Linux, as como a la hora de mantener optimizado y protegido el servidor 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 lodos 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 momento.

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 (wmv.cygwin.com) me ha permitido resolver innumerables problemas que Windows no puede resolver y cada da que pasa ms me engancho a esta herramienta (me hubiera

Fiaros de catalogacin bibliogrfica encantado disponer de ella hace 15 aos, cuando tena mi mente orientada a Gnu Emacs). Eclipse de IBM (www.eciipsc.org) representa una maravillosa contribucin a la comunidad de desarrolladores y cabe esperar que se puedan obtener grandes cosas con esta herramienta 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 Sparxsvstems 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 concretas. En mi opinin, la herramienta JEdit de Slava Pcstov y sus correspondientes plug-ins tambin resultan tiles en diversos momentos (www.jedit.org); esta herramienta es un editor muy adecuado para todos aquellos que se estn iniciando en el desarrollo de seminarios.

por supuesto, por si acaso no lo he dejado claro an, utilizo constantemente Python (www.Python.org) para resolver problemas, 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 comcr. 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 gracias en particular a (por la primera edicin): Kevin Raulerson (encontr numerossimos errores imperdonables), Bob Rcscndcs (simplemente increble), John Pinto, Joe Dante, Joe Sharp (fabulosos vuestros comentarios), David Combs (numerosas correcciones de clarificacin y de gramtica). Dr. Robert Stephenson, John Cook, Franklin Chen, Zev Griner, David K.arr, Leander A. Stroschein. Steve Clark, Charles A. Lee. Austin Maher, Dennis P. Roth, Roque Oliveira. Douglas Dunn, Dejan Ristic, Neil Galameau, David B. Malkovsky, Steve Wilkinson, y muchos otros. El Profesor Marc Meurrens dedic
Y

una gran cantidad de esfuerzo a publieitar y difundir la versin electrnica de la primera edicin de esle 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: Jon Shvarts, Thomas Kirsch, Rahim Adatia, Rajesh Jain. Ravi Manthena. tanu 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 escribi 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 algn momento de vida y que han llegado a ser autnticos amigos, habiendo tenido una enorme influencia sobre m. Son personas poco usuales en el sentido de que practican yoga y otras formas de engrandecimiento espiritual, que me resultan particularmente inspiradoras 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 lenguaje. 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 comida 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 inspiraciones para Java SE5).

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 fotografa de la cubierta han sido realizados por mi amigo Daniel Will-Harris, renombrado autor y diseador ('m\w. WiU-Harris.com), que ya sola crear sus propios diseos en el colegio, mientras esperaba 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 Georgia para el texto y los ttulos estn enVerdana. Laletrade la

18 Piensa en Java

cubierta original es ITC 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 masajista), Andrew Binstock, Stcve Sinofsky, JD Hildebrandt, Tom Keffer, Brian McElhinney, Brinkley Barr, Bill Gates en Midmght Engineering Magazine, Larry Constantine y Lucy Lockwood, Gene Wang, Dave Mayer, David Intcrsimonc, Chris y Laura Strand, los Almquists, Brad Jerbic. Marilyn Cvitanic. Mark Mabry. la familia Robbins. la familia Moelter (y los McMillans), Michacl Wilk, Dave Stoner, los Cranstons, Larry Fogg, Mike Sequeira, Gary Entsminger, Cevin 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.Resumen del contenido

Contenido

Contenido 19

El controlador de invernadero

implementado

Introduccin

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 ... estamos, en buena medida, a merced de! 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

20 Piensa en Java de resolver problemas especficos de comunicacin y reflexin. Lo cierto es que el mundo real est en gran parte construido, de manera inconsciente, sobre los hbitos lingsticos del grupo. El estado de la Lingistica como ciencia, 1929, Edward Sapir

Como cualquier lenguaje humano. Java proporciona una forma de expresar conceptos. Si tiene xito, esta forma de expresin ser significativamente ms fcil y flexible que las alternativas a medida que los problemas crecen en tamao y en complejidad.

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 entender 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 desarrollar 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 familiarizado con la programacin: sabe que un programa es una coleccin de instrucciones, 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 programadores de C y ms todava para los de C++, pero tampoco debe autoexcluirse si no tiene experiencia con estos lenguajes (aunque tendr que trabajar duro). Puede descargarse en www.MindView.net el seminario multimedia Thinking in C, el cual le ayudar a aprender ms rpidamente los fundamentos necesarios para estudiar Java. No

Contenido 21 obstante, en el libro se abordan 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. Aprendiendo Java

Casi al mismo tiempo que se public mi primer libro, Using C++ (Osbome/McGrawHill, 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-H* en la Software Development Conference (y ms tarde la sesin sobre Java), que tanto yo como otros oradores tocbamos demasiados temas muy rpidamente. Por ello, tanto debido a la variedad en el nivel de la audiencia como a la forma de presentar el material, se termina perdiendo audiencia. Quiz es pedir demasiado pero dado que soy uno de esos que se resisten a las conferencias tradicionales (y en la mayora de los casos, creo que esa resistencia proviene del aburrimiento), quera intentar algo que permitiera tener a todo el mundo enganchado.

Durante algn tiempo, cre varias presentaciones diferentes en poco tiempo, por lo que termine aprendiendo segn el mtodo de la experimentacin e iteracin (una tcnica que tambin inciona en el diseo de programas). Desarroll un curso utilizando todo lo que habia 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 fundamentos para nuestros restantes seminarios ms avanzados. Puede encontrar informacin detallada en www.MindView.net. El seminario de introduccin tambin est disponible en el CDROM Hamls-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 funciona bien como mtodo de enseanza. Pero este libro no

22 Piensa en Java 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 capitulo del libro, pienso en trminos de qu hizo que fuera una leccin durante un seminario. La informacin que me proporcionan las personas que asisten a un seminario me ayuda a comprender cules 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 y eso 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 contexto 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 siguiente. 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 comprender 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.

Contenido 23 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 programadores nunca conocern, detalles que slo sirven para confundir a las personas y que incrementan su percepcin de la complejidad del lenguaje. Tomemos un ejemplo de Cf si se memoriza la tabla de precedencia de lo

24 Piensa en Java

soperadores (yo nunca lo he hecho), se puede escribir cdigo inteligente. Pero si se piensa en ello, tambin confundir 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.

3.

Mantener cada seccin enfocada de manera que el tiempo de lectura y el tiempo entre ejercicios, 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.

4.

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 encontraba en su infancia, suficiente tiempo para cubrir el lenguaje. A medida que Java fue creciendo y aadiendo ms y ms funcionalidades 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 memorizar todo en una nica semana era angustioso tanto para m como para las personas que asistan al seminario. Java ya no era un lenguaje 'simple" que se poda aprender en una semana.

Dicha experiencia me llev a reorganizar este libro, el cual ahora est diseado como material de apoyo para un seminario de dos semanas o un curso escolar de dos trimestres. La parte de introduccin termina con el Capitulo 12, Tratamiento de errores mediante excepciones, aunque tambin puede complementarla con una introduccin a JDBC, Servlcts y JSP. Esto proporciona las bases y es el ncleo del CD-ROM Hands-On Java. El resto del libro se corresponde con un curso de nivel intermedio y es el material cubierto en el CDROM Intermediare Thinking in Java. Ambos discos CD ROM pueden adquirirse a tTavs de www.MindView.net.

Contacte con Prentice-Hall en www.prenhaHprofessional.com para obtener ms informacin acerca del material para el profesor relacionado con este libro. Documentacin del JDK en HTML

El lenguaje Java y las bibliotecas de Sun Microsystems (descarga gratuita en http://java.sun.com) se suministran con documentacin 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

Introduccin 25

utilice la referencia JDK documentaron. En este libro se proporcionan descripciones adicionales de las clases slo cuando es necesario complementar dicha documentacin, con el fin de que se pueda comprender un determinado ejemplo. Ejercicios

He descubierto que durante las clases los ejercicios sencillos son excepcionalmcntc tiles para alumno termine

que

el

comprender el tema, por lo que he incluido al final de cada captulo una serie de ejercicios.

La mayor parte de los ejercicios son bastante sencillos y estn diseados para que se puedan realizar durante un tiempo razonable 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 inalcanzable.

Las soluciones a los ejercicios seleccionados se pueden encontrar en el documento electrnicoTheThinking in Java Annotated So/ution Guide, que se puede adquirir en www.MindView.net. Fundamentos para Java

Otra ventaja que presenta esta edicin es el seminario multimedia gratuito que puede descargarse en la direccin www.MindView.net. Se trata del seminario Thinking in C. el cual proporciona una introduccin a los operadores, funciones y la sintaxis de C en la que se basa la sintaxis de Java. En las ediciones anteriores del libro se encontraba en el CD Foundations for Java que se proporcionaba junto con el libro, pero ahora este seminario puede descargarse gratuitamente.

Originalmente, encargu a Chuck Allison que creara Thinking in C como un producto autnomo, pero decid incluirlo en la segunda edicin 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 deC. El razonamiento suele ser: Soy un programador inteligente y no quiero aprender C. sino C++ o Java, por tanto, me salto el C y paso directamente a ver el C++/Java\ Despus de asistir al seminario, lentamente todo el mundo se da cuenta de que el prerrequisito de conocer la sintaxis de C tiene sus buenas razones de ser.

Las tecnologas han cambiado y han permitido rehacer Thinking in C como una presentacin Flash

26 Piensa en Java

descargable en lugar de tener que proporcionarlo en CD. Al proporcionar este seminario en linea, puedo garantizar que todo el mundo pueda comenzar 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 seminario 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 graftiitamente 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 distribucin del cdigo. Puede distribuir 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 publiquen sin permiso. No obstante, mientras se cite la ftiente, 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 This computer source code is Copyright 2006 MindView, Inc. All Rights Reserved. Permission to use, copy, modify, and distribute this computer source code (Source Code) and its documentation without fee and without a written agreement for the purposes set forth below is hereby granted, provided that the above copyright notice, this paragraph and the following five numbered paragraphs appear in all copies. Permission is granted to compile the Source Code and to include the compiled code, in executable format only, in personal and commercial software programs.
1.

Permission is granted to use the Source Code without modification in classroom situations, including in presentation materials, provided that the book "Thinking in Java" is cited as the origin.
2.

Introduccin 27

Permission to incorporate the Source Code into printed media may be obtained by contacting:
3.

MindView, Inc. 5343 Valle Vista La Mesa, California 91941 Wayne@MindView.net The Source Code and documentation are copyrighted by MindView, Inc. The Source code is provided without express
4.

or implied warranty of any kind, including any implied warranty of merchantability, fitness for a particular purpose or non-infringement. MindView, Inc. does not warrant that the operation of any program that includes the Source Code will be uninterrupted or error-free. MindView, Inc. makes no representation about the suitability of the Source Code or of any software that includes the Source Code for any purpose. The entire risk as to the quality and performance of any program that includes the Source Code is with the user of the Source Code. The user understands that the Source Code was developed for research and instructional purposes and is advised not to rely exclusively for any reason on the Source Code or any program that includes the Source Code. Should the Source Code or any resulting software prove defective, the user assumes the cost of all necessary servicing, repair, or correction. 5. IN NO EVENT SHALL MINDVIEW, INC., OR ITS PUBLISHER BE LIABLE 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 INJURIES, ARISING OUT OF THE USE OF THIS SOURCE CODE AND ITS DOCUMENTATION, OR ARISING OUT OF THE INABILITY TO USE ANY RESULTING PROGRAM, EVEN IF MINDVIEW, INC., OR ITS PUBLISHER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. MINDVIEW, INC. SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOURCE CODE AND DOCUMENTATION PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, WITHOUT ANY ACCOMPANYING SERVICES FROM MINDVIEW, INC., AND MINDVIEW, INC. HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. Please note that MindView, Inc. maintains a Web site which is the sole distribution point for electronic copies of the Source Code, http://www.MindView.net (and official mirror sites), where it is freely available under the terms stated above. If you think you've found an error in the Source Code, please submit a correction using the feedback system that you will find

28 Piensa en Java

at http://www.MindView.net.

///:

Puede utilizar el cdigo en sus proyectos y en la clase (incluyendo su material de presentaciones) siempre y cuando se mantenga la nota de copyright en cada uno de los archivos fuente. Estndares de codificacin

En el texto del libro, los identificadores (nombres de mtodos, variables 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 ponerlas en negrita podra volverse tedioso, como en el caso de la palabra class.

En este libro, he utilizado un estilo de codificacin particular para los ejemplos. Este estilo sigue el que emplea Sun en prcticamente todo el cdigo que encontrar en su sitio (vase http://java.sun.com/docs/codeconv/wdex.htnii), 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 codificacin de Sun coincide con el mo, lo que me complace, ya que yo 110 tengo nada que ver con la creacin del estilo de

Sun. El tema del estilo de formato es bueno para conseguir horas de intenso debate, por lo que no voy a intentar dictar un estilo correcto a travs de mis ejemplos; tengo mis propias motivaciones para usar el estilo que uso. Dado que Java es un lenguaje de programacin de formato libre, se puede emplear el estilo con el que uno se encuentre a gusto. Una solucin para el tema del estilo de codificacin consiste en utilizar una herramienta como Jalopy (www.triemax.com), la cual me ha ayudado en el desarrollo de este libro a cambiar el formato al que se adaptaba a mi.

Los archivos de cdigo impresos en el libro se han probado con un sistema automatizado, por lo que deberan ejecutarse sin errores de compilacin.

Este libro est basado y se ha comprobado con Java SE5/6. Si necesita obtener informacin 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.

Introduccin 29

Errores

No importa cuantas herramientas utilice un escritor para detectar los errores, algunos quedan ah y a menudo son lo que primero ve el lector. Si descubre cualquier cosa que piensa que es un error, por favor utilice el vnculo que encontrar para este libro en www.MindView.net y enveme el error junto con la correccin que usted crea. Cualquier ayuda siempre es bienvenida.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 nuestro idioma... nos resulta imposible hablar si no utilizamos la organizacin y clasificacin de los

datos decretadas por ese acuerdo". Benjamn Lee Whorf (1897-1941)

La gnesis de la revolucin de las computadoras se hallaba en una mquina. La gnesis de nuestros lenguajes de programacin tiende entonces a parecerse a dicha mquina.

Pero las computadoras, ms que mquinas, pueden considerarse como herramientas que permiten ampliar la mente (bicicletas para la mente, como se enorgullece en decir Steve Jobs), adems de un medio de expresin diferente. Como resultado, las herramientas empiezan a parecerse menos a

mquinas y ms a panes 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 programacin orientada a objetos (POO) es parte de este movimiento dirigido al uso de las computadoras como un medio de expresin.

Este captulo presenta los conceptos bsicos de la programacin orientada a objetos, incluyendo una introduccin a los mtodos de desarrollo. Este captulo, y este libro, supone que el lector tiene cierta experiencia en programacin, aunque no necesariamente en C. Si cree cjue necesita una mayor preparacin antes de abordar este libro, debera trabajar con el seminario multimedia sobre C, Thinking in C, que puede descargarse en MindView.net.

Este captulo contiene 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 POO. Sin embargo, otras personas pueden no necesitar tener una visin general hasta haber visto algunos de los mecanismos primero, estas personas suelen perderse si no se les ofrece algo de cdigo que puedan manipular. Si usted forma parte de este ltimo grupo, estar ansioso por ver las especifidades del lenguaje, por lo que puede saltarse este capitulo, 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 objetos y cmo puede disearse con ellos. El progreso de la abstraccin

Todos los lenguajes 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 lenguaje ensamblador es una pequea abstraccin de la mquina subyacente. Muchos de los lenguajes denominados imperativos que le siguieron (como FORTRAN, BASIC y C) fueron abstracciones del lenguaje ensamblador. Estos lenguajes constituyen grandes mejoras sobre el lenguaje ensamblador, pero su principal abstraccin requiere que se piense en trminos de la estructura de la computadora en

lugar de en la estructura del problema que se est intentado resolver. El programador debe establecer 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

del problema que es lo que realmente se quiere resolver (en el espacio del problema, que es el lugar donde existe el problema. 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 difciles de escribir y caros de mantener, adems 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 L1SP 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. Esta es una abstraccin del lenguaje ms flexible y potente que cualquiera de las que se hayan hecho anteriormentec. Por tanto, la programacin orientada a objetos permite describir el problema en trminos 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.

Algunos diseadores de lenguajes han decidido que la programacin orientada 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 multiparadigma. Consulte Sfultiparadim Programming in Leda de Timothy Budd (Addison-Wcsicy, 1995).
c

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. Estos caractersticas representan un enfoque puro de la programacin orientada a objetos.

1.

Iodo es un objeto. Piense en un objeto como en una variable: almacena datos, permite que se le planteen solicitudes, pidindole que realice operaciones sobre s mismo. En teora, puede tomarse cualquier componente conceptual del problema que se est intentado resolver (perros, edificios, servicios, etc.) y representarse como un objeto del programa. Un programa es un montn de objetos que se dicen entre s lo que tienen que hacer envindose mensajes.

2.

Para hacer una solicitud a un objeto, hay que enviar un mensaje a dicho objeto. Ms concretamente, puede pensar 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 complejidad de un programa ocultndola 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, siendo clase sinnimo de tipo. La caracterstica distintiva ms importante de una clase es el conjunto de mensajes 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 escribir cdigo para comunicarse con objetos de tipo forma y controlar automticamente cualquier cosa que se ajuste a la descripcin de una forma. Esta capacidad de stiplantacin es uno de los conceptos ms importantes de la programacin orientada a objetos.

Booch ofrece una descripcin an ms sucinta de objeto: 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 forma 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 lenguaje orientado a objetos, el Simula-67, que ya usaba su palabra clave fundamental class, que permite introducir un nuevo tipo en un programa.

Algunas personas hacen una distincin, estableciendo que el lipo determina la interfaz mientras que la clase es una implcmcntacin concreta de dicha interfaz.
2

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 concepto fundamental en la programacin orientada a objetos. Los tipos de datos abstractos funcionan casi exactamente como tipos predefinidos: pueden crearse variables de un tipo (llamadas objetos u instancias en la jerga de la POO) 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 cajero 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 entidad unvoca en el programa informtico. Esta entidad es el objeto y cada objeto pertenece a una determinada clase que define 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 prctica, todos los lenguajes de programacin orientada a objetos utilizan la palabra clave class. Cuando vea la palabra type (tipo) piense en class (clase), y viceversa.'

Dado que una clase describe un conjunto de objetos que tienen caractersticas (elementos de datos) y comportamientos (funcionalidad) idnticos, una clase realmente es un tipo de datos porque, por ejemplo, un nmero en coma flotante tambin tiene un conjunto de caractersticas y comportamientos. La diferencia est en que el programador define un clase para adaptar un problema en lugar de forzar el uso de un tipo de datos existente que fue diseado para representar una unidad de almacenamiento 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 comprobaciones 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 programa es una simulacin del sistema que se est diseando, el uso de las tcnicas de la POO puede reducir fcilmente 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 forma de hacer una solicitud 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 interfaz y es el tipo lo que determina la interfaz. Veamos un ejemplo con la representacin de una bombilla:

Tipo

Interfaz

Luz lz = new Luz(); 1z.encender{);

La interfaz determina las solicitudes que se pueden hacer a un determinado objeto, por lo que debe existir un cdigo en alguna 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 proced mental, esto no es complicado. Un tipo tiene un mtodo asociado con cada posible solicitud; cuando se hace una determinada solicitud a un objeto, se llama a dicho mtodo. Este proceso se resume diciendo 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 pueden 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 Language, 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, no incluyndose la zona intermedia, como en este caso. Si slo se est interesado en el nombre de la clase, tampoco 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 utilizando los servicios que ofrecen otros objetos. El objetivo es producir (o incluso mejor, localizar en las bibliotecas de cdigo 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 resolveran 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 servicios deberan proporcionar esos objetos y qu objetos necesitaran para cumplir con sus obligaciones? Si se hace este planteamiento, 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 forma 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 componente de software (tal como un objeto, aunque tambin podra aplicarse a un mtodo o a una biblioteca de objetos) deben ajustar bien entre si. Un problema que suelen tener los programadores cuando disean objetos es el de asignar demasiada 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 c 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

puede consultar para obtener informacin 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 utilizar 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 permitir descubrir objetos que pueden adquirirse (el objeto interfaz de impresora), tambin genera nuevos objetos 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 proceso 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 programadores de clientes3 (los consumidores de clases que emplean los tipos de datos en sus aplicaciones). El objetivo del programador 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 mantenga todo lo dems oculto. Por qu? Porque si est oculto, el programador cliente no puede acceder a ello, lo que significa que el creador de clases puede cambiar la parte oculta a voluntad sin preocuparse del impacto que la modificacin pueda implicar. Normalmente, 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 3 Termino acuado por rnt amigo Scott Meyers.

es un programador, pero que debe construir 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 manipule 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 interfaz 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 ejecute ms rpidamente. Si la interfaz y la implementacin estn claramente separadas y protegidas, podr hacer esto fcilmente.

Java emplea tres palabras clave explcitamente para definir los lmites en una clase: public, prvate y protected Estos modificadores de acceso determinan 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 acceder a dicho elemento excepto usted, el creador del tipo, dentro de los mtodos de dicho tipo, prvate es un muro de ladrillos 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 privados (prvate). Reutilizacin de la implementacin

Una vez que se ha creado y probado una clase, idealmente debera representar una unidad de cdigo til. Pero esta reutilizacin no siempre es tan fcil de conseguir como era de esperar; se necesita experiencia y perspicacia para generar un diseo de un objeto reutilizable. Pero, una vez que se dispone de tal diseo, parece implorar ser reutilizado. La reutilizacin de cdigo es una de las grandes ventajas que proporcionan los lenguajes de programacin orientada a objetos.

La forma ms sencilla de reutilizar una clase consiste simplemente en emplear directamente un objeto de dicha clase, aunque tambin se puede colocar un objeto de dicha clase dentro de una clase nueva. Esto es lo que se denomina crear un objeto miembro. La nueva clase puede estar formada por cualquier 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 existentes se denomina composicin (si la composicin se realiza de forma dinmica, se llama agregacin). A menudo se hace referencia a la composicin como una relacin "tiene un", como en un coche tiene un motor. Este diagrama UML indica la composicin mediante un rombo relleno, que establece que hay un coche. Normalmente, yo utilizo una forma ms sencilla: slo una linea, sin el rombo, para indicar una asociacin.4

La composicin conlleva una gran flexibilidad. Los objetos miembro de la nueva clase normalmente 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 composicin.
4

son privados, lo que Ies hace inaccesibles a los programadores de clientes que estn usando la clase. Esto le permite cambiar dichos miembros sin disturbar al cdigo cliente existente. Los objetos miembro tambin se pueden modificar en tiempo de ejecucin, con el fin de cambiar dinmicamente el comportamiento del programa. La herencia, que se describe a continuacin, no proporciona esta flexibilidad, ya que el compilador tiene 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 llegar 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 nuevas 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 misma, 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 utilizando la palabra clave class.

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 necesario al clon. Esto es lo que se logra con la herencia, con la excepcin de que la clase original (llamada clase base, superclase o clase padre) se modifica, el clon "modificado" (denominado clase derivada, clase heredada, subclase o clase hija) tambin refleja los cambios. base

t derivada

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 caractersticas 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 formas de implementar ese ncleo.

Por ejemplo, una mquina para el reciclado de basura clasifica los desperdicios. El tipo base es basura y cada desperdicio 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 aluminio puede modelarse, el accro puede tener propiedades magnticas). Adems, algunos comportamientos pueden ser diferentes (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 compradora 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 (circulo, cuadrado, tringulo, etc.), cada una con sus propias caractersticas adicionales y comportamientos. Por ejemplo, ciertas formas podrn voltearse. Algunos 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.

Representar la solucin en los problema es muy til, porque modelos intermedios para problema a una descripcin de jerarqua de tipos es el puede pasar directamente de el mundo real a la descripcin A pesar de esto, una de las los programadores con el que es demasiado sencillo ir Una mente formada para ver inicialmente, verse simplicidad.

mismos trminos que el no se necesitan muchos pasar de una descripcin del la solucin. Con objetos, la modelo principal, porque se la descripcin del sistema en del sistema mediante cdigo. dificultades que suelen tener diseo orientado a objetos es del principio hasta el final. soluciones complejas puede, desconcertada por esta

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 objetos 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 dase base. En el ejemplo anterior, un crculo es una forma. Esta equivalencia de tipos a travs de la herencia es uno de los caminos fundamentales para comprender el significado de la programacin 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 simplemente hereda una clase y no hace nada ms. los mtodos de la interfaz de la clase base pasan tal cual a la clase derivada, 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 derivada de la clase base original. La primera es bastante directa: simplemente, 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 primitivo 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.

Aunque en ocasiones la (especialmente en Java, herencia es extends) que se nuevos a la interfaz, no necesariamente. La forma de diferenciar la el comportamiento de un clase base. Esto es lo que mtodo.

herencia puede implicar donde la palabra clave para van a aadir mtodos tiene que ser as segunda y ms importante nueva clase es cambiando mtodo existente de la se denomina sustitucin del

Para sustituir un mtodo, definicin para el mismo decir, se usa el mismo mtodo haga algo diferente en el tipo Relaciones es-un y es-

basta con crear una nueva en la clase derivada. Es de interfaz, pero se quiere que nuevo. como-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 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 denomina principio de sustitucin. En cierto sentido, sta es la forma ideal de tratar la herencia. A menudo, en este caso, la relacin 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 sentido.

A veces es necesario aadir 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-como-un. El tipo nuevo tiene la interfaz del tipo antiguo pero tambin contiene 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.

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 caliente, 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 forma de poder hacer las cosas, debe aplicarse 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 que 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 permite escribir cdigo que no dependa de tipos especficos. En el ejemplo de las formas, los mtodos manipulan las formas genricas, independientemente de que se trate de crculos, cuadrados, tringulos o cualquier otra forma que todava no haya sido definida. Todas las formas 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 forma 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 formas genricas. Esta capacidad de ampliar fcilmente un diseo derivando nuevos subtipos es una de las principales formas 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 (crculos como formas, bicicletas como automviles, cormoranes como aves, etc.). Si un mtodo dice a una forma que se dibuje, 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 compilacin de forma precisa qu pane del cdigo tiene que ejecutar. ste es el punto clave, cuando se envia 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 circulo, 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 ejecutar, entonces se aadir un subtipo nuevo y el cdigo que se ejecute 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 controladorAves slo funciona con los objetos genricos Ave y no sabe exactamente de qu tipo son. Desde la perspectiva del objeto controladorAves esto es adecuado ya que no tiene 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)? La respuesta es una de las principales novedades de la programacin orientada a objetos: el compilador 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 denomina 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 acopiamiento tardo. Cuando se enva un mensaje a un objeto, el cdigo al que se est llamando no se determina 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 exactamente qu cdigo tiene que ejecutar.

Para realizar el acoplamiento tardo, Java emplea un bit de cdigo especial en lugar de una llamada absoluta. Este cdigo calcula la direccin del cuerpo del mtodo, utilizando la informacin almacenada en el objeto (este proceso se estudia en detalle en el Captulo 8, Polimorfismo). Por tanto, cada objeto puede comportarse de forma diferente de acuerdo con los contenidos de dicho bit de cdigo especial. Cuando se enva un mensaje a un objeto, realmente el objeto resuelve lo que tiene que hacer con dicho mensaje.

En algunos lenguajes debe establecerse explcitamente que un mtodo tenga la flexibilidad de las propiedades del acoplamiento tardo (C++ utiliza la palabra clave virtual para ello). En estos lenguajes, de manera predeterminada, los mtodos no se acoplan de forma dinmica. En Java, el acoplamiento dinmico es el comportamiento predeterminado y el programador no tiene que aadir ninguna palabra clave adicional para definir el polimorfismo.

Considere el ejemplo de las formas. La familia de clases (todas basadas en la misma interfaz uniforme) se ha mostrado en un diagrama anteriormente en el captulo. Para demostrar el polimorfismo, 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 informacin especfica del tipo y por tanto es ms sencillo 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: vod hacerAlgo(Forma forma) { borrar.forma(); // ... dibuj ar.forma();

Este mtodo sirve para cualquier Forma, por lo que es independiente del tipo especfico de objeto que se est dibujando y borrando. Si alguna otra parte del programa utiliza el mtodo hacerAlgo(): Circulo circulo = new Circulo O, Triangulo triangulo = new Triangulo 0; Linea linea = new Linea{); hacerAlgo (circulo); hacerAlgo (triangulo); hacerAlgo (linea);

Las llamadas a hacerAlgo () funcionarn correctamente, independientemente del tipo exacto del objeto.

De hecho, ste es un buen truco. Considere la lnea: hacerAlgo (circulo);

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, cualquier 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 significa en ingls proyeccin hacia arriba" y refleja la forma en que se dibujan habitualmcnte 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, efectuar una proyeccin sobre un tipo base, ascendiendo por el diagrama de herencia. Un programa orientado a alguna generalizacin, desvincularse de tener que que se trabaja. Veamos el forma.borrar(); objetos siempre contiene porque es la forma de conocer el tipo exacto con cdigo de hacerAlgoO-

// forma.dibuj 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 forma, te puedo borrarO y dibujarQ teniendo en cuenta correctamente los detalles".

Lo que ms impresiona del cdigo del mtodo hacerAlgoO es que. de alguna manera se hace lo correcto. Llamar a dihujarO para Circulo da lugar a que se ejecute un cdigo diferente que cuando se le llama para un Cuadrado o una Linea, pero cuando el mensaje dibujar() se enva a una Forma annima, tiene lugar el comportamiento correcto basndose 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 ello, habitualmente se espera que llame a la versin de borrar() y dbujar() para la clase base Forma y no a la versin especfica de Crculo. Cuadrado o Linca. Y sigue ocurriendo lo correcto gracias al polimorfismo. 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 C++ es si todas las clases en ltima instancia deberan ser heredadas de una nica clase base. En Java (como en casi todos los dems lenguajes de POO excepto C-H-) la respuesta es afirmativa. 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 lo que en ltima instancia son del mismo tipo fundamental. La alternativa (proporcionada por 0+) 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 C 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 proporcionar la misma utilidad que se construye en otros lenguajes de programacin orientada a objetos. Y en cualquier nueva biblioteca 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 C++? Si la necesita (si dispone ya de una gran cantidad de cdigo en C), 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 determinadas operaciones bsicas sobre todos los objetos del sistema. Pueden crearse todos los objetos y el paso de argumentos se simplifica enormemente.

Una jerarqua de raz nica facilita mucho la implcmentacin de un depurador de memoria, que es una de las mejoras fundamentales 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, como 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 llevar. Tampoco se sabe cmo se van a almacenar dichos objetos. Cmo se puede saber cunto espacio hay que crear si no se conoce dicha informacin 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 lo 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 amplia por si mismo cuando es necesario acomodar cualquier cosa que se quiera introducir en l. Por tanto, no necesitamos saber cuntos objetos pueden almacenarse 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 conjunto de contenedores como parte del paquete. En C++, ese conjunto forma parte de la biblioteca estndar C++ y a menudo se le denomina STL (Standard Templte 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 resolver nuestro problema. Si un mismo tipo de contenedor satisface todas las necesidades, no existe ninguna razn para disponer 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. Una 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 determinadas operaciones. Por ejemplo, existen dos tipos bsicos de contenedores de tipo List: ArrayList (lista matricial) y LinkedList (lista enlazada). Ambos son secuencias simples que pueden tener interfaces y comportamientos externos idnticos. Pero ciertas operaciones pueden llevar asociado un coste radicalmente distinto. La operacin de acceder aleatoriamente a los elementos contenidos en un contenedor de tipo ArrayList es una operacin de tiempo constante. Se tarda el mismo tiempo 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 operaciones 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 minimo en el cdigo. Tipos parametrizados (genricos)

Antes de Java SE5, los contenedores albergaban objetos del tipo universal de Java: Object. La jerarqua de raz nica indica que todo es de tipo Object. por lo que un contenedor que almacene objetos de tipo Object podr almacenar cualquier cosa. 5 Esto haca que los contenedores fueran fciles de reutilizar.

Los contenedores no permiten almacenar primitivas, pero la caracterstica de atuobxing de Java SE5 hace que esta restriccin tenga pocs importancia. Hablaremos de esto en detalle ms adelante en el libro.
5

Para utilizar uno de estos contenedores, simplemente se aaden a l referencias a objetos y luego se las extrae. Sin embargo, puesto que el contenedor slo permite almacenar objetos de tipo Object, al aadir una referencia a objeto al contenedor, esa referencia se transforma en una referencia a Object perdiendo asi 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 referencia en algo que tenga el tipo especifico de objeto que hubiramos almacenado en el contenedor?

Lo que se hace es volver a utilizar el mecanismo de transformacin de tipos (casi), pero esta vez no efectuamos una generalizacin. 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 (;upeasting), 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 lodo objeto de tipo Object es necesariamente de tipo Circulo o Forma por lo que no resulta tan seguro realizar una especializacin a menos que sepamos concretamente lo que estamos haciendo.

Sin embargo, esta operacin no es del todo peligrosa, porque si efectuamos una conversin de tipos y transformamos el objeto 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 errores. asociados? La solucin a este problema es el mecanismo de itpos parametrizados. Un tipo parametrizado es una clase que el compilador puede personalizar automticamente para que funcione con cada tipo concreto. Por ejemplo, con un contenedor parametrizado, el compilador puede personalizar dicho contenedor para que slo acepte y devuelva objetos Forma.

Uno de los cambios principales en Java SE5 es la adicin de tipos parametrizados, que se denominan genricos en Java. El uso de genricos es fcilmente 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 cdigo utilizado en este libro. Creacin y vida de los objetos

Una de las cuestiones crticas 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 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 difcil 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 videos o para una tienda de venta de mascotas). A primera vista, el problema parece muy simple: creamos un contenedor para almacenar las aeronaves y luego creamos 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, podramos 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 problema puede surgir en muchas otras situaciones, y puede llegar a resultar enormemente 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 quedan 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 resultar muy til en muchas situaciones. Sin embargo, perdemos flexibilidad 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 general. 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 falla 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 almacenamiento 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 una 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 mecanismo 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 6. Cada vez que se quiere crear un objeto, se utiliza el operador ne>v para construir una instancia dinmica del objeto.

Sin embargo, existe otro problema, referido al tiempo de vida de un objeto. En aquellos lenguajes que permiten crear objetos en la pila, el compilador determina cul es la duracin del objeto y puede desunirlo automticamente. Sin embargo, si creamos el objeto en el cmulo de memoria, el
6

Los tipos primitivos, de los que hablaremos en breve rcpresenlun un caso especial.

compilador no sabe cul es su tiempo de vida. En un lenguaje como C++, es preciso determinar mediante programa cundo debe destruirse el objeto, lo que puede provocar prdidas de memoria si no se realiza esta tarea correctamente (y este problema resulta bastante comn en los programas C++). Java proporciona una caracterstica denominada depurador de memoria, que descubre automticamente cundo un determinado objeto ya no est en uso. en cuyo caso lo destruye. Un depurador de memoria resulta mucho ms cmodo que cualquier otra solucin alternativa, 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 nivel mucho mayor de proteccin contra el insidioso problema de las fugas de memoria, que ha hecho que muchos proyectos en C++ fracasaran.

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 objeto). El depurador de memoria sabe* cundo ya no se est usando un objeto, en cuyo caso libera automticamente la memoria 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 objetos 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 aparicin de los lenguajes de programacin, el tratamiento de los errores ha constituido un problema peculiarmente difcil. Debido a que resulta muy complicado disear un buen esquema de tratamiento de errores, muchos lenguajes simplemente ignoran este problema, dejando que lo resuelvan los diseadores de bibliotecas, que al final terminan desarrollando soluciones parciales que funcionan en muchas situaciones pero cuyas medidas pueden ser obviadas fcilmente: generalmente, basta con ignorarlas. Uno de los problemas principales 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 lenguaje. Si el programador no tiene cuidado (lo cual suele suceder cuando hay prisa por terminar un proyecto), puede olvidarse fcilmente de estos esquemas.

Los mecanismos de tratamiento de excepciones integran la gestin de errores directamente dentro del lenguaje de programacin. 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 ftiera una rula de ejecucin paralela y diferente, que se loma cuando algo va mal. Y. como se utiliza una rata de ejecucin independiente, sta no tiene porqu interferir con el cdigo que se ejecuta normalmente. Esto hace que el cdigo sea ms simple de escribir, porque no es necesario comprobar constantemente la existencia de errores. 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 producido una condicin de error: tanto los valores como los indicadores de error podran ser ignorados por el programador. Las excepciones no pueden ignorarse, por lo que se garantiza que en algn momento sern tratadas. Finalmente, las excepciones 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 sobresaliente entre los lenguajes de programacin, porque en Java el tratamiento de excepciones estaba previsto desde el principio y estamos obligados a utilizarlo. Este esquema de tratamiento de excepciones es el nico mecanismo 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 compilacin. Esta garanta de coherencia 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 lenguajes de programacin orientada a objetos las excepciones se representan normalmente mediante un objeto. Los mecanismos 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 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 intemipcin 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 mejor tiempo de respuesta. En un programa, estos fragmentos que se ejecutan por separado, se denominan hebras y el conjunto general se llama concurrencia. Un ejemplo bastante 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 realizando.

Normalmente, las tareas son slo una forma de asignar el tiempo disponible en un nico procesador. Pero si el sistema operativo 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 ejecutar ms rpido, 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 problema, los recursos que puedan compartirse, como por ejemplo una impresora, deben bloquearse mientras estn siendo utilizados 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 SE5 ha mejorado significativamente el soporte de biblioteca para los mecanismos de concurrencia. Java e Internet

Si Java no es, en definitiva, 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 respuesta 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 problemas 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 realmente, pero para ello es necesario comprender primero lo que son los sistemas cliente/servidor, que constituyen otro campo de la informtica lleno 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 consumidores de la informacin. El repositorio de informacin, el software que distribuye la informacin y la mquina o mquinas 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 informacin, que la procesa y que luego la muestra en la propia mquina consumidora se denomina cliente.

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 tiempo. 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 clientes inserten 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 realizan modificaciones en el software de clienie, es necesario disear el software, depurarlo e instalarlo en las mquinas cliente. lo que resulta ser ms complicado y ms caro de lo que en un principio cabria 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 servidor en un momento dado, por lo que cualquier pequeo retardo puede llegar a ser verdaderamente critico. Para minimizar la latencia. los programadores hacen un gran esfuerzo para tratar de descargar las tareas de procesamiento que er ocasiones se descargan en la mquina cliente, pero en otras ocasiones se descargan en otras mquinas situadas junto al servidor, utilizando 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 informacin 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 hasta la distribucin de cualquier tipo de datos, como por ejemplo cotizaciones burstiles, datos cientficos, informacin 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 difciles de disear y de utilizar, y el usuario se vea obligado a aprender una nueva interfaz en cada caso. De este modo, se lleg a un punto en que era necesario resolver el problema global de la informtica 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 lo que lo nico que hace es conectarse con un servidor en cada momento c 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 este le devolva un archivo, que el software explorador de la mquina (es dccir, el cliente) se encargaba de interpretar, efectuando todas las tareas de formateo en la propia mquina local. Pero al cabo de muy poco tiempo, los propietarios de servidores 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 servidor. realizar bsquedas en una base de datos instalada en el servidor, aadir nueva informacin al servidor o realizar un pedido (lo que requiere medidas especiales de seguridad). Estos 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 informacin 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 haca. No resultaban peculiarmente interactivos y tendan a sobrecargar al servidor tanto como a la propia red Internet, porque cada vez que haca falta hacer algo que requera programacin, era necesario devolver la informacin al servidor para que ste la procesara. De esta forma, poda tardarse varios segundos o incluso minutos en averiguar simplemente que habamos 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 resolverse incorporando la capacidad de ejecutar programas en el extremo cliente, bajo control del explorador. Esto se denomina programacin del lado del cliente. Programacin del lado del cliente

El diseo inicial de la Web. basado en una arquitectura servidor/explorador, permita disponer de contenido interactivo, pero esa mteractividad era completamente proporcionada por el servidor. El servidor generaba pginas estticas para el explorador cliente, que simplemente se encargaba de interpretarlas y mostrarlas. El lenguaje bsico HTML (HyperText Karkup Language) contiene una serie de mecanismos simples para la introduccin de datos; recuadros de introduccin de texto, casillas de verificacin, botones de opcin, listas normales y listas desplcgables, as como un botn que slo podia programarse para borrar los datos del formulario o enviarlos al servidor. Ese proceso de envo se llevaba a cabo a travs de la interfaz COI (Common Gateway Interface) incluida en todos los serv idores web. El texto incorporado en el envo le dice a la interfaz CGI lo que tiene que hacer. La accin ms comn, en este caso, consiste en ejecutar un programa ubicado en un servidor en un directorio normalmente llamado egi-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 egi-bin* como parte de la direccin). Estos programas del lado del servidor pueden escribirse en casi cualquier lenguaje. Perl es uno de los lenguajes ms utilizados para este Upo de tareas, porque est diseado especficamente para la manipulacin de textos y es un lenguaje interpretado, por lo que se puede instalar en cualquier servidor independientemente de cul sea su procesador o su sistema operativo. Sin embargo, otro lenguaje. Python (www.Python.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 llegar 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 CGI depende de cuntos datos haya que enviar, de la carga del servidor y de la red Internet (adems. el propio arranque de un programa CGI liende 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 desarrollar. Por ejemplo, es casi imposible disear de manera coherente una aplicacin con grficos dinmicos, porque es necesario crear un archivo GIF (Graphics Interchange 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 formulario enviado a travs de la Web. El proceso es el siguiente: pulsamos el botn de envo de la pgina; se envan los datos al servidor, el servidor arranca un programa CGI que descubre un error, formatea una pgina H TML en la que nos informa del error y devuelve la pgina al cliente; a continuacin, es necesario que el usuario retroceda una pgina y vuelva a intentarlo. Este enfoque no slo resulta lento sino tambin poco elegante.

La solucin consiste en usai un mecanismo de programacin del lado del cliente. La mayora de las computadoras de sobremesa que incluyen un explorador web son mquinas bastante potentes, capaces de realizar tareas muy complejas; con el enfoque original basado en HTML esttico, esas potentes mquinas 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 permite 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.

F.l problema con las explicaciones 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 trmino, sigue siendo necesario disear 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-ins

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 explorador descargando un fragmento de cdigo que se inserta en el lugar apropiado deniro del explorador. Esc fragmento de cdigo le dice al explorador: A partir de ahora puedes realizar este nuevo tipo de actividad" (slo es necesario descargar el plug-in una vez). Podemos aadir nuevas formas de comportamiento, potentes y rpidas, a los exploradores mediante plug-ins, pero la escritura de un plug-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 web. El valor de un plug-in para la programacin del lado del clienie es que permite a los programadores avanzados desarrollar extensiones y aadrselas a un explorador sin necesidad de pedir permiso al fabricante del explorador. De esta forma, los plug-ins proporcionan una especie de puerta trasera que permite 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 script 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 plug-in que se encarga de interpretar ese lenguaje se activa de manera automtica en el momento de visualizar la pgina HTML. Los lenguajes de script suelen ser razonablemente fciles 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 obtiene la pgina. La desventaja es que el cdigo queda expuesto, ya que cualquiera puede verlo (y copiarlo). Generalmente, sin embargo, los programadores no llevan a cabo tareas extremadamente sofisticadas con los lenguajes de script, as que este problema no resulta particularmente grave.

Uno de los lenguajes de script que los exploradores web suelen soportar sin necesidad de un plug-in es JavaScript (el lenguaje JavaScript slo se asemeja de forma bastante vaga a Java, por lo que hace falta un esfuerzo de aprendizaje adicional para llegar a dominarlo; recibi el nombre de JavaScript simplemente para aprovechar el impulso inicial de marketing de Java), lamentablemente, cada

explorador web implcmentaba originalmente JavaScript de forma distinta a los restantes exploradores web, e incluso en ocasiones, de forma 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 tiempo hasta que los distintos exploradores adoptaron el estndar (el problema se complic porque Microsoft trataba de conseguir sus propios objetivos presionando en favor de su lenguaje VBScript, que tambin se asemejaba vagamente a JavaScript). En general, es necesario llevar a cabo la programacin de las pginas utilizando una especie de mnimo comn denominador de JavaScript, 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 difcil 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 realmente notables.

Lo que todo esto nos sugiere es que los lenguajes de script que se emplean en los exploradores web estn diseados, realmente. para resolver tipos especficos de problemas, principalmente el de la creacin de interfaces grficas de usuario (GUI) ms ricas e interactivas. 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 asi, y teniendo en cuenta que los lenguajes de script permiten realizar los desarrollos de forma ms fcil y rpida, probablemente seria conveniente ver si se puede resolver un tipo concreto de problema empleando un lenguaje de script, antes de considerar otras soluciones ms complejas, como la programacin en Java. Java

Si un lenguaje de script 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 difciles? 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, interplataforma c internacional, sino que continuamente est siendo ampliado para proporcionar nuevas caractersticas del lenguaje y nuevas bibliotecas que permiten gestionar de manera elegante una serie de problemas que resultan bastante difciles de tratar en los lenguajes de programacin tradicionales, como por ejemplo la concurrencia, el acceso a bases de datos, la programacin en red y la informtica distribuida. Java permite resolver los problemas de programacin del lado del cliente utilizando applets y Java Web Start.

Un applet es un mini-programa que slo puede ejecutarse sobre un explorador web. El applet se descarga automticamente como parte de la pgina web (de la misma forma que se descarga automticamente, por ejemplo, un grfico). Cuando se activa el applet. ejecuta un programa. Este mecanismo de ejecucin automtica forma parte de la belleza de esta solucin: nos proporciona una forma de distribuir automticamente el software de cliente desde el servidor en el mismo momento en que c! 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 programador 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 travs de Internet simplemente para descubrir que hemos escrito mal una fecha o algn otro parmetro: asimismo, la computadora 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 velocidad y la capacidad de respuesta, sino que disminuyen tambin la carga de trabajo de los servidores y el trfico de red, evitando asi 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 dia nos podemos encontrar con unos applets realmente interesantes en la Web, pero la esperada migracin masiva hacia los applets no lleg nunca a producirse. El principal problema era que la descarga de 10 MB 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 aciago destino. Pero, sea como sea, lo cieno 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 considerable 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 applets basados en Flash. Como el reproductor Flash Player est disponible 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 lo que resulta bastante ms atractivo que JavaScript. Para la programacin del lado del cliente se trata de una alternativa que merece la pena considerar. .NET y C#

Durante un tiempo, el competidor principal de los applets de Java era ActiveX de Microsoft, aunque esta tecnologa requera 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, equivalente a la mquina virtual Java (JVM, Java Virtual Machine; es la plataforma software en la que se ejecutan los programas Java) y a las bibliotecas Java, mientras que C# tiene similitudes bastante evidentes con Java. Se trata, ciertamente, del mejor intento que Microsoft ha llevado 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 CU y las razones por las que un programador podra sentirse tentado a adoptar ese lenguaje, y han respondido introduciendo significativas 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 permitir portarlo completamente a otras plataformas. Ellos afirman que no hay ningn problema para esto, y el proyecto Mono (vnnvgo- mono.com) dispone de una implementacin parcial de .NET sobre Linux, pero hasta que la implementacin sea completa y Microsoft decida no recortar ninguna pane de la misma, sigue siendo una apuesta arriesgada adoptar .NET como solucin interplataforma. 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 clientc/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 utiliza tecnologa web para una red de informacin 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 fsicamente el acceso a los equipos de la empresa. En trminos de formacin, una vez que los usuarios comprenden el concepto general 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 plataforma 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 interplataforma y seguro, como por ejemplo, un lenguaje de script o Java.

Si 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 Intel/Windows. En una intranet, nosotros somos responsables de la calidad de nuestro 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 tradicional, en la que es necesario instalar fsicamente 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 exploradores. 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 amplio conjunto de soluciones para los problemas de la programacin del lado del cliente, el mejor plan de ataque consiste en realizar un anlisis 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 situacin concreta. Esta es la mejor manera de prepararse para los problemas que inevitablemente enconnaremos 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 probablemente donde Java ha tenido su xito ms rotundo. 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 un applet de Java, como programa en lenguaje de script, 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 solicitud para que se realice una bsqueda completa en una base de datos, encargndose a continuacin el servidor de dar formato a los resultados como pgina HMTL y enviar sta al explorador (por supuesto, si el cliente dispone de un mayor grado de inteligencia, 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 cliente se llevaba a cabo utilizando Perl. Python, C-H-, o algn otro lenguaje para crear programas CG1, pero con el tiempo se han desarrollado otros sistemas ms sofisticados, entre los que se incluyen los servidores web basados en Java que permiten 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 principales razones por las que las empresas que desarrollan sitios web estn adoptando Java, especialmente porque dichas tecnologas eliminan los problemas derivados de tratar 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 permite resolver los mismos tipos de problemas que podemos resolver con otros lenguajes. En este sentido, la ventaja de Java no radica slo en su portabilidad. sino tambin en su programabilidad. su robustez, su amplia biblioteca estndar y las numerosas bibliotecas de otros fabricantes que ya estn disponibles y que continan siendo desarrolladas. 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 utilizando conceptos de bajo nivel con el fm de crear un modelo mental del programa. sta es la razn por la que necesitamos representaciones intermedias a la hora de disear programas procedimentales: en si 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 simple y mucho ms fcil de comprender que un 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 orientada 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 reutilizando cdigo de las bibliotecas existentes.

La programacin orientada a objetos y el lenguaje Java no resultan adecuados para todas las situaciones. Es importante evaluar cules son nuestras necesidades reales y determinar si Java permitir satisfacerlas de forma ptima o si. por el contrario, 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 especializadas en un futuro prximo, y si estamos sujetos a restricciones especficas que Java pueda no satisfacer, resulta recomendable investigar otras alternativas (en particular, mi recomendacin sera echarle un vistazo a Python: vase wwM'.Pyrhon.org). Si decide, a pesar de todo, utilizar el lenguaje Java, al menos comprender, despus de efectuado ese anlisis, cules sern las opciones existentes y por qu resultaba conveniente adoptur la decisin que finalmente haya tomado.Todo es un objeto

2 Si hablramos un lenguaje 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 C++ como Java son lenguajes hibridos. pero en Java los diseadores pensaron que esa hibridacin no era tan importante con en C++. Un lenguaje hbrido permite utilizar mltiples estilos programacin: la razn por la que O-*- es capaz de soportar la compatibilidad descendente con el

lenguaje (\ Puesto que C++ es un superconjunto del lenguaje C, incluye muchas de las caractersticas menos deseables de ese lenguaje, lo que hace que algunos aspectos del 0+ sean demasiado complicados.

El lenguaje Java presupone que el programador slo quiere realizar programacin orientada 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 capitulo 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 dispone de sus propios mecanismos para manipular los elementos almacenados en memoria. En ocasiones, el programador debe ser continuamente consciente del tipo de manipulacin que se esta efectuando. Estamos tratando con el elemento directamente o con algn upo de representacin indirecta (un puntero en (. OC+-M. 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 trufamos todo como un objeto, los identificado res que manipulamos son en realidad referencias a objetos 4i Podramos imaginamos una TV' (el objeto) y un mando a distancia F.ste punto puede suscitar enconadadebates Hay personas que sostienen que claramente se traa de un puntero", pero cato estii presuponiendo una determinada implcmentacion subyacente. Asimismo, lu referencias en Java se parecen mucho mas sintctica monte a la relerenctas O-*- que a los punteros. fcn la primera edicin de este libro decid utilizar el termino descriptor porque las referencias O y las referencias Java tienen diferencias notables. Yo mismo provena del mundo del lenguaje Ct y no quera confundir a los programadores de GH-. que supona que constituiran la eran mayora de personas interesadas en el lenguaje Java. En la segunda edicin, decid que referencia" era el trmino ms comnmente utilizada), y que cualqucra que proviniera del inundo de (.+-* tbu a enfrentarse a problemas mucho ms gnu es que la terminologa de las referencias, por Jo que no tema sentido usar
4i

(la referencia); mientras dispongamos 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 manipular 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.

una palabra distinta Sin embarco. Iiay persono* que estn en desacuerdo incluso con el termino referencia. En un determinado libio, pude leer que resulta completamente equivocado decir que Java soporta el paso por referencia", o que los identifica dores de los objeto* Java (de acuerdo con el autor del libro son en realidad 'referencias a objetos" Por lo que (contina el autor) todo se pasa en la prctica por valor Segn e*te autor, no se efecta un paso por referencia, sino que se "pasa una referencia a objeto por salar Podramos discutir acerca de la precisin de c-ta> complicadas cxplicacumcs. pero ere' que el enfoque que he adoptado en este libro implifica la compresin del concepto sm generar mngnn tipo de problema lio* puristas del lenguaje podran soktener que estoy mintiendo, pero a eso respondera que lo que estoy haciendo e* proporcionar una abstraccin apropiad.!l

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 queremos 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, obtendramos 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 ncw La palabra clave new significa: Crea un nuevo ejemplar de este tipo de objeto. Por tanto, en el ejemplo anterior podramos escribir: String s = new String("asdf") ;

Esto no slo dice: "Crea un nuevo objeto String". sino que tambin proporciona informacin acerca de cmo crear el objeto suministrando una cadena de caracteres inicial.

Por supuesto. Java incluye una pltora de tipos predefinidos, adems de String. Lo ms importante es que tambin podemos crear nuestros propios tipos. De hecho, la creacin de nuevos tipos es la actividad fundamental en la programacin Java, y eso es precisamente lo que aprenderemos a hacer en el resto del libro.

Los lugares de almacenamiento

Resulta til tratar de visualizar la forma en que se dispone la informacin 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:

1.

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 esta muy limitado, por lo que los registros se asignan a medida que son necesarios. No disponemos de 1111 control directo sobre los mismos, ni tampoco podremos encontrar en los programas que los registros ni siquiera existan (C y C++, por el contrario, permiten 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 proporciona un soporte directo para la pila, gracias al puntem Je pila. El puntero de pila se desplaza hacia abajo para crear nueva memoria y hacia arriba para liberarla. Se trata de una forma 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 restriccin impone una serie de lmites a la flexibilidad de los programas, por lo que aunque parte del almacenamiento dentro 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 compilador 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 almacenamiento 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 para el almacenamiento en la pila (eso suponiendo que pudiramos crear objetos en la pila en Java, al igual que se hace en C+ +).

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 forma que se pueden guardar opcionalmente en la memoria de slo lectura (ROM, readonly mvmon). especialmente en los sistemas integrados.

5.

Almacenamiento 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 principales son los objetos stream, en los que los objetos se transforman en flujos de bytes. generalmente para enviarlos a otra mquina, los objetos persistentes en los que los objetos se almacenan en disco para que puedan conservar su estado incluso despus de terminar el programa. El Unco con estos tipos de almacenamiento consiste en transformar los objetos en algo que pueda existir en el otro medio de almacenamiento, y que. sin embargo, pueda recuperarse para transformarlo en un objeto normal basado en RAM cuando sea necesario. Java proporciona soporte para lo que se denomina persistencia ligera y otros mecanismos tales como JDBC e Hibemate proporcionan soporte ms sofisticado para almacenar y extraer objetos utilizando 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 determina el tamao de cada tipo primitivo Estos tamaos no cambian de una arquitectura de mquina a otra, a diferencia 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

Todos los tipos numricos tienen signo, por lo que Java no podr encontrar ningn tipo sin signo,

El tamao del tipo boolean no est especificado de manera explcita; tan slo se define para que sea capaz de aceptar los valores literales true o false.

Las clases envoltorio para los tipos de datos primitivos permiten definir un objeto no primitivo en el cmulo de memoria que represente a ese tipo primitivo. Por ejemplo: char c 'x' ; Character ch new Characterle) ;

O tambin podra emplear: : Un ejemplo sera el conjunto de cadenas de caracteres Todas las cadenas literales y constantes que tengan un valor de tipo carcter se turnan de forma automtica y se asignan a un almacenamiento esttico especial.
7

Character ch = new Character('x*);

La caracterstica de Java SE5 denominada autoboxing permite realizar automticamente la conversin de un tipo primitivo a un tipo envoltorio: Character ch = 'x;

Y a la inversa: char c * ch;

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 realizar operaciones aritmticas de alta precisin: Biglntegcr y BigDocimal. Aunque estas clases 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, lis decir, podemos hacer con un objeto Biglnteger o Hii>L)ecimal cualquier cosa que podamos hacer con una variable int o float: simplemente deberemos utilizar llamadas a mtodos en lugar de operadores. Asimismo, como sor ms complejas, las operaciones se ejecutarn ms lentamente En este caso, sacrificamos pane de la velocidad en aras de la precisin.

Biglnteger soporta nmeros enteros de precisin arbitraria. Esto quiere decir que podemos representar valores enteros de cualquier tamao sin perder ninguna informacin en absoluto durante las operaciones.

BiuDecimal se utiliza para nmeros de coma tija y precisin arbitraria. Podemos utilizar estos nmeros, por ejemplo, para realizar clculos monetarios precisos

Consulte la documentacin del kit JDK para conocer ms detalles acerca de los constructores y mtodos que se pueden invocar para estas dos clases. Matrices en Java

Casi todos los lenguajes de programacin soportan algn tipo de matriz. La utilizacin de matrices en C y C ' es peligrosa, porque dichas matrices son slo bloques de memoria Si un programa accede a la matriz fuera de su correspondiente hlo- que de memoria o utiliza la memoria antes de la inicializacin (lo cual son dos errores de programacin comunes), los resultados sern unprcdecibles.

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 C y C+-+. Se garantiza que las matrices Java siempre se inicialicen y que no se pueda acceder a ellas fuera de su rango autorizado. Las comprobaciones de rango exigen pagar el precio de gastar una pequea cantidad de memoria adicional para cada matriz, asi 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 csias operaciones).

Cuando se crea una matriz de objetos, lo que se crea realmente es una matriz de referencias, y cada una de e*.as matrices se inicializa automticamente con un valor especial que tiene su propia palabra clave: nuil. Cuando Java se encuentra un valor nuil, comprende que la referencia en cuestin no est apuntando a ningn objeto. Es necesario asignar un objeto a cada referencia antes de utilizarla, y si se intenta usar una referencia que siga teniendo el valor nuil, se informar del error en tiempo 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 compilador se encarga de garantizar la inicializacin. rellenando con ceros la memoria correspondiente a dicha matriz.

Hablaremos con detalle de las matrices en captulos posteriores.

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 significativa del esfuerzo de programacin. Cunto va a durar la variable? Si hay que destruirla, cundo debemos hacerlo? La confusin en lo que respecta al tiempo de vida de las variables puede generar una gran cantidad de errores de programacin, y en esta seccin vamos a ver que Java simplifica enormemente 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 determina tanto la visibilidad como el tiempo de vida de los nombres definidos dentro del mismo. En C, C-H- y Java, el mbito est determinado por la colocacin de las llaves 0- de modo que. por ejemplo:

( int x 12; // x ( int q = 96; // estn disoonibles tanto x como q

slo est disponible x // q est "fuera del mbito"


II

Una variable definida dentro de un mbito slo est disponible hasta que ese mbito termina.

Todo texto situado despus de los caracteres * 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 formato libre, los espacios, tabuladoras y retornos de carro adicionales no afectan al programa resultante.

No podemos hacer lo siguiente, a pesar de que si es correcto en C y Ct-r:

< int x = 1 2 { int x * 96; // Ileqal

} J

El compilador nos indicara que la variable * ya ha sido definida Por tanto, no est permitida la posibilidad en C y C * de ocultar" una variable en un mbito ms grande, porque los diseadores de Java pensaron que esta caracterstica hacia los programa ms confusos. mbito de los objetos

Los objetos Java no tienen el mismo tiempo de vida que las primitivas. Cuando se crea un objeto Java asando new. ese objeto contina existiendo una vez que se ha alcanzado el final del mbito. Por tanto, si usamos:

{ String s = new String(1Ma string"); ) f / Fin del mbito

la referencia s desaparece al final del mbito. Sin embargo, el objeto String al que s estaba apuntando continuar ocupando memoria. En este fragmento de cdigo, no hay forma de acceder al objeto despus de alcanzar el final del mbito, porque la nica referencia a ese objeto est ahora fuera de mbito. En captulos posteriores veremos cmo pasar y duplicar una referencia a un objeto durante la ejecucin de un programa.

Como los objetos que creamos con new 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 \e/ que se lia terminado de usarlos.

listo 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 0+f y para resolverlo Java recurre a una especie de varita mgica. Java dispone de lo que se denomina depurador de memoria, que examina 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 programacin: 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, que es lo que determina cmo se compona y que 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 utype" (tipo), y de hecho tendra bastante sentido Histricamente, 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 palabra clave class (que es tan comn que la escribiremos en negrita normalmente 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 captulo). asi que no es mucho lo que podemos hacer con esta clase que acabamos de definir. Sin embargo, si que podemos crear un objeto de este tipo utilizando new: 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 ningn 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 denominados funciones miembro). Un campo es un objeto de cualquier tipo con el que podemos comunicamos a travs de su referencia o bien un tipo primitivo. Si es una referencia a un objeto, hay que inicia- lizar esa referencia para conectarla con un objeto real (usando new, como hemos visto anteriormente).

Cada objeto mantiene su propio almacenamiento para sus campos: los campos normales no son compartidos entre los distintos objetos. Aqui 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 O;

Podemos asignar valores a los campos, pero primero es preciso saber cmo refenmos a un miembro de un objeto. Para referimos a l. es necesario indicar el nombre de la referencia al objeto, seguida de un punto y seguida del nombre del miembro concreto dentro del objeto: objectReference.member

Por ejemplo. daca.i 47; data.d = =

1.1; data.b false;

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 puntos correspondientes, como por ejemplo: myPlane.leftTank.capacity = 100;

La clase DataOnly no puede hacer nada ms que almacenar datos, ya que no dispone 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 predeterminado en caso de que no se inicialice:

Los valores predeterminados son slo los valores que Java garantiza cuando se emplea la variable como miembro de una clase. Esto garantiza que las variables miembro de tipo primitivo siempre sean inicializadas (algo que C-H- no hace), reduciendo asi una fuente de posibles errores. Sin embargo, este valor inicial puede que no sea correcto o ni siquiera legal para el programa que se est escribiendo. Lo mejor es inicializar las variables siempre de forma explcita.

Esta garanta de iniciazacin no se aplica a las variables locales, aquellas que no son campos de clase. Por tanto, si dentro de la definicin de un mtodo tuviramos: int x;

Entonces x adoptara algn valor arbitrario (como en C y C++), no siendo automticamente imcializada 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-H-, ya que se obtiene un error en tiempo de compilacin que nos informa de que la variable puede no haber sido micializa (muchos compiladores de C^+ nos advienen acerca de la existencia de variables no inicializadas. pero en Java esta falta de inicializacion constituye un error). Mtodos, argumentos y valores de retorno

En muchos lenguajes (como C y C++), el trmino funcin se usa para describir una subrutina con nombre. El trmino ms comnmente utilizado 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 adoptaremos el uso comn del trmino mtodo dentro del mundo de Java.

Los mtodos de Java determinan los mensajes que un objeto puede recibir. Las panes fundamentales de un mtodo son el nombre, los argumentos, el tipo de retomo y el cuerpo. Esta seria la forma bsica de un mtodo: TipoRetorno NombreKetodof / LBta de argumentos */ ) ( ) /* Cuerpo dei mtodo * /

El tipo de retomo describe el valor devuelto por el mtodo despus de la invocacin. La lista de argumentos proporciona la lista de los tipos y nombres de la informacin que hayamos pasado al mtodo. El nombre del mtodo y la lista de argumentos (que forman lo que se denomina signatura del mtodo) identifican de forma univoca al mtodo.

Los mtodos pueden crearse en Java nicamente como parte de una clase. Los mtodos slo se pueden invocar para un objeto', y dicho objeto debe ser capa/ de ejecutar esa invocacin del mtodo. Si tratamos de invocar un miodo correcto para un objeto obtendremos un mensaje de error en tiempo de compilacin. Para invocar un mtodo 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.NombreMetodolargl, 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 ) podriamos escribir: int x = a. f ();

El tipo del valor 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 mensaje 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 lista de argumentos del mtodo especifica cul es la informacin que se le pasa al mtodo. Como puede suponerse, esta informacin (como todo lo dems en Java) adopta la forma 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 estuv iramos gestionando objetos, lo que en realidad estaremos pasando son referencias 8 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 String como argumento. Para que la compilacin se realice correctamente. la definicin que habra que incluir dentro de la definicin de la clase seria como la siguiente: int storage (Strmg s) { return a . length O * 2;

Con la excepcin usual de los tipos de daios especiales que ornes hemos mencionado tatolean, char, bytc, short, int, long, flout y douhte. I.n genera). sm embargo, lo que se pasan son objetos, lo que realmente quiere decir que se pasan referencias a objetos
8

Este mtodo nos dtce cuntos bytes se requieren para almacenar la informacin contenida en un objeto String concreto (el tamao de cada char en un objeto String es de 16 bits, o dos bytes, para soportar los caracteres Unicode). El argumento es de tipo Serinj y se denomina v Una ve? que se pasa s al mtodo, podemos tratarlo como cualquier otro objeto (podemos enviarle mensajes) Aqu, lo que se hace es invocar el mtodo lcngth( ). que es uno de los mtodos definidos para los objetos 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 terminado, 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 tipo que deseemos, pero si no queremos devolver nada, tenemos que especificarlo. indicando que el mtodo devuelve un valor de tipo void. He aqu algunos ejemplos: boolean flagO ( retura true; } double naturalLogBase() ( returrt 2.71S; ) void nothmgO ( retura; ) void nothing2 () {}

Cuando el tipo de retomo es void, la palabra clave return se utili/a 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 void, 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 sene de objetos con mtodos que aceptan otros objetos como argumentos y envan mensajes a esos otros objetos. Realmente, esa descripcin es bastante precisa, pero en el siguiente capftnlo veremos cmo llevar a cabo el trabajo detallado de bajo nivel, tomando decisiones dentro de un mtodo. Para los propsitos de este capitulo, 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 C, este problema es especialmente significativo, porque cada programa es. a menudo, un conjunto manejable de nombres. Las clases C-*-+ (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 dentro de otras clases. Sin embargo. O +- sigue permitiendo utilizar datos globales y funciones globales, as que las colisiones continan siendo posibles. Para resolver este problema, C++ introdujo los denominados espacios de nombres utilizando palabras clave adicionales

Java consigui evitar todos estos problemas adoptando un enfoque completamente nuevo. Para generar un nombre no ambiguo para una biblioteca, los creadores de Java emplean los nombres de dominio de Intcmci 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 foiblcs se denominara, por ejemplo, net.mindview.utilityJ'diblcs. Despus del nombre de dominio invenido, los puntos tratan de representar subdirectorios.

En Java l.0 y Java l . l . las extensiones de dominio com. edu. orj, net. etc., se escriban en maysculas por convenio* por lo que el nombre de la biblioteca seria NET.mindview.utility.foihles Sin embargo, durante el desarrollo de Java 2 se descubri 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 colisiones 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 definida hasta ms adelante en el archivo (Java elimina los problemas denominados de referencia anticipada).

Qu sucede con las clases almacenadas en algn otro archivo? Cabria esperar que el compilador fuera lo suficientemente inteligente como para localizar la clase, pero hay un pequeo problema. Imagine que queremos emplear una clase con un nombre concreto, pero que existe ms de una definicin para dicha clase (probablemente, definiciones distintas). O peor, imagine que est escribiendo un programa y que a medida que lo escribe aade una nueva clase a la biblioteca que entra en conflicto con el nombre de otra clase ya existente.

Para resolver este problema, es preciso eliminar todas las ambigedades potenciales. Esto se consigue informando al compilador Java de exactamente qu clases se desea emplear, utilizando para ello la palabra clave import import le dice al compilador que cargue un paquete, que es una biblioteca de clases (en otros lenguajes, una biblioteca podra es:ar compuesta 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 invenidos, basta con decir, por ejemplo: import java.til.ArrayList;

para informar al compilador que queremos utilizar la clase ArrayList de Java. Sin embargo, til contiene diversas clases, y podemos usar varias de ellas 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 forma que importar las clases individualmente. La palabra clave static

Normalmente, 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 disponibles.

Existen dos situaciones en las que esta tcnica no resulta suficiente Una de ellas es cuando deseamos disponer 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 aunque 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 ligados 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 static o acceder a un campo static. Con los campos y mtodos normales, que no tienen el atributo static. es preciso crear un objeto y usar esc objeto para acceder 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.9

Por supuesto, puesto que los mtodos ttatic no necesitan que se cree ningn objeto ames de utilizarlos, no pueden acceder directamente u ningn miembro o mtodo que no sea static. por el procedimiento de invocar simplemente esos otros miembros sin hacer referencia a un objeto nominado (ya que los miemhros y mtodos que no son static deben estar asociados con un objeto concreto).
9

Algunos lenguajes orientados a objetos utilizan los trminos ciatos 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 trminos.

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 static y le inicial izara: class StaticTest { static int i = 47;

Ahora, aunque creramos dos objetos StaticTest. existira nico espaciode almacenamiento para StaticTest.L Ambos

un

objetos compartiran el mismo StaticTest stl = new StaticTest st2 = new

campo i. Considere StaticTestO; StaticTestO;

elfragmento de cdigo siguiente:

En este punto, tanto stl.i como st2.i tienen el mismo valor, 47, ya que ambos hacen referencia a la misma posicin de memoria.

May dos formas de hacer referencia a una variable static. Como se indica en el ejemplo anterior, se la puede expresar a travs 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 static. StaticTest .1^;

1:1 operador ++ incrementa en una unidad la variable. En este punto, tanto stl.i como st2. tendrn el valor 48.

La forma preferida de hacer referencia a una variable static es utilizando el nombre de la clase. Esto no slo permite enfatizar la naturaleza de tipo stalic de la variable, sino que en algunos casos proporciona al compilador mejores oportunidades para la optimizacin.

A los mtodos static se les aplica una lgica similar. Podemos hacer referencia a un mtodo static a travs de un objeto, al igual que con cualquier otro mtodo, o bien mediante la sintaxis adicional especial NomtireClase.meto- do( ). Podemos definir un mtodo static de manera a similar: class Incrementable ( static void incremento { StaticTest.-M-,10 }
10

til compilador Java y la documentacin suministrados por Sun tienden a cambiar de manera

Podemos ver que el mtodo ncrement() de Incrementable incrementa el dato i de tipo static utilizando el operador ++. Podemos invocar incronient( ) de la forma tpica a travs de un objeto: Incrementable sf = new Incrementable() ; B t . increment{ ) ;

O,dado que increment( ) es un mtodo static. podemos invocarlo directamente a travs de su clase: Incrementable. increment () ;

Aunque static. cuando se aplica a un campo, modifica de manera evidente la forma en que se crean los datos (se crea un dato global para la clase, a diferencia de los campos que no son static. para los que se crea un campo para cada objeto), cuando se aplica esa palabra clave a un mtodo no es Lan dramtico. Un uso importante de static para los mtodos consiste en permitimos invocar esos mtodos sin necesidad de crear un objeto. Esta caracterstica resulta esencial, como veremos, al definir el mtodo main( ), que es el punto de entrada para ejecutar una aplicacin.

frecuente, y el mejor lugar para obtenerlos es directamente en el sitio web de Sun. Descargndose esos archivos podr disponer siempre de la versin ms reciente

Nuestro primer programa Java

Finalmente, vamos a ver nuestro primer programa completo. Comenzaremos imprimiendo una cadena de caracteres y a continuacin la fecha, empleando la clase Date de la biblioteca estndar de Java. // HelloDace.java import j ava.ut i1.; public ciass HelloDate ( public static void main(String11 args) { System.out.println"Helio, it's: "); System.out.println(new Date());

} 1

Al principio de cada archivo de programa, es necesario incluir las instrucciones import necesarias para cargar las clases adicionales 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,lan{>. Inicie el explorador web y consulte la documentacin de Sun (si no ha descargado la documentacin del kit JDK de http://javasun.coni, hgalo ahora. Observe que esta documentacin no se suministra con el JDK: es necesario descargarla por separado). Si consulta la lista de paquetes, podr ver todas las diferentes bibliotecas de clases incluidas con Java. Seleccione java.lang; esto har que se muestre una lista de todas clases que forman parte de esa biblioteca. Puesto que java.lang est implcitamente en todo archivo de cdigo Java, dichas clases estarn disponibles de manera automtica. No existe ninguna clase Date en ja\ a.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 (uncin Buscar* del explorador para encontrar Date. Cuando lo haga,

podra ver que aparece como java.util.Date. lo que nos permite deducir que se encuentra en la biblioteca til y que es necesario emplear la instruccin impurt java.util.* para usar Date.

Si vuelve atrs y selecciona java.lang y luego System, pudra ver que la clase System tiene varios campos; si selecciona out. descubrir que se trata de un objeto static PrintStream. Puesto que es de tipo static, no es necesario crear ningn objeto empleando new. El objeto out est siempre disponible, por lo que podemos usarlo directamente. Lo que puede hacerse con este objeto out est determinado por su tipo: PrintStream En la descripcin se muestra PrintStream como un hiper- vnculo, por lo que al hacer clic sobre l podr acceder a una lista de todos los mtodos que se pueden invocar para PrinrStream Son bastantes mtodos, v hablaremos de ellos mas 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 voy a pasar y termina con un carcter de avance de linea. Asi. en cualquier programa Java podemos escribir algo como esto. System.out .printin ("Una cadena de caracteres*);

cuando queramos mostrar informacin a trav s 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 quejara si no lo hacemos asi). Dicha clase debe contener uu mtodo denominado man( ) con la siguiente signatura y tipo de retomo: public static void mainString argsJ (

La palabra clave puhlic quiere decir que el mtodo est disponible para todo el mundo (lo que describiremos en detalle en el Capimlo 6. ContraI Je acceso). El argumento de main( ) es una matriz de objetos String. En este programa, no se emplean los argumentos (args). pero el compilador Java insiste en que se incluya ese parmetro, ya que en l se almacenarn los argumentos de la lnea de comandos.

I.

a linea que imprime la fecha es bastante interesante: System.out.prrrtln (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 instruccin* dicho objeto Date resulta innecesario. > el depurador de memoria podr encargarse de l en cualquier momento. Nosoiros no necesitamos preocupamos de borrar el objeto.

Al examinar la documentacin del JDK en ////> (java stm.com, podemos ver que System tiene muchos mtodos que no> 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(StringI1 argsi ( System.aetPropertles(i . llst(System.out 1 ; System.out.printlnfSystem.getPropertyi "ussr.ame"}j System.out.println( System.aetProperty f"java.library.path"));

) ///:-

La primera linea de main( ) muestra todas las propiedades del sistema en que se est ejecutando el programa, por lo que nos proporciona la informacin del entorno. F.l mtodo list( ) enva los resultados a su argumento. System.out Ms adelante en el libro veremos que los resultados pueden enviarse a cualquier parte, como por ejemplo, a un archivo. Tambin podemos consultar una propiedad especfica, por ejemplo, en este caso, el nombre de usuario y java.library.path (un poco mas adelante explicaremos los comentarios bastante poco usuales situados al principio y al final de este fragmento de cdigo). 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 otros fabricantes, pero en el libro vamos a suponer que el lector est utilizando el kit de desarrollo JDK (Java Developers K i t ) de Sun. el cual es gratuito. Si est utilizando otro sistema de desarrollo6, tendr que consultar la documentacin de dicho sistema para ver cmo compilar y ejecutar los programas.

El compilador jik.es de IBM es una alternativa bailante comn, y es Mgnificativamente tnt( rpido que Jova C de Sun (aunque se estn construyendo ynipo* de archivos utilizando Ant. la diferencia no c% muy significativa) Tambin hay diverso* proyectos de cdigo fuente abierto para crear compiladores Java, entornos de ejecucin y biblioteca.
6

Acceda a Internet y vaya a http; iuya.sun.com. All podra encontrar informacin y vnculos que le llevaran a tra\s del proceso de descarga e instalacin del JDK para su plataforma concreta.

Una vez insudado el JDK. y una vez modificada la informacin de ruta de la computadora para que esta pueda encontrar javac y java, descargue y desempaquete el cdigo fuente correspondiente a este libro (puede encontrarlo en u ni A JindYU'w.net). listo creara un subdirectono para cada capitulo del libro. Acceda al subdirectorio objecis v escriba. Javac HelloDate.java

Este comando no debera producir ninguna respuesta. Si obtiene algn tipo de mensaje de error querr decir que no ha instalado 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 linca de comandos, podr escribir java HelioDate y obtendr el mensaje y la fecha como salida.

Puede utilizar este proceso para compilar V ejecutar cada uno de los programas de este libro. Sin embargo, podra ver que el cdigo fuente de este libro tambin dispone de un archivo denominado huld.xml en cada capitulo, que contiene comandos Ant" para construir automticamente los archivos correspondientes a ese capitulo. Los archivos de generacin y Ant (incluyendo las instrucciones para descargarlos) se describen ms en detalle en el suplemento que podr encontrar en http://\1ind\te\v.net/Books/BetterJava. pero una vez que haya instalado Ant (desde http://iakaria.apache.org/ant) basta con que escriba ant en la linea de comandos para compilar y

ejecutar los programas de cada captulo. Si no ha instalado Ant todava, 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 comentarios comienzan con /* y continan, posiblemente a lo largo de varias lnea. Itasta encontrar */. Muchos programadores empiezan cada linea de un comando multilinca con un *. por lo que resulta bastante comn ver comentarios como ste: / Este comentario
* *

ocupa varias lineas

Recuerde, sin embargo, que en todo lo que hay entre I* y */ se ignora, por lo que no habra ninguna diferencia si dijramos: / Este comentario ocupa varias lineas /

El segundo tipo de comentario proviene de C-H*. Se irata del comentario monolinea que comienza con H y continua hasta el final de la linea. Este tipo de comentario es bastante cmodo y se suele utilizar a menudo debido a su sencillez. No es necesario desplazarse de un sino a otro del teclado para encontrar el carcter i y luego el carcter * (en su lugar, se pulsa la misma tecla dos veces), y tampoco es necesario cerrar el comentario. Asi que resulta bastante habitual encontrar comentarios de este estilo: // ste es un comentario monolinea. Documentacin mediante comentarios

Posiblemente el mayor problema de la larea de documentar el cdigo es el de mantener dicha documentacin. Si la documentacin y el cdigo estn separados, resulta bastante tedioso modificar la documentacin cada vez que se cambia el cdigo. La solucin parece simple: integrar el cdigo con la documentacin. La forma ms fcil de hacer esto es incluir todo en un mismo archivo. Sin embargo, para ello es necesario disponer de una sintaxis especial de comentarios que permita marear la documentacin, as como de una herramienta para extraer dichos comntanos y formatearlos de manera til, listo es precisamente lo que Java ha hecho.

La herramienta para extraer los comentarios se denomina Javadoc, y forma parte de la instalacin del JDK. Utiliza parte de la tecnologa del compilador Java para buscar los marcadores especiales de comentarios incluidos en el programa No slo extrae la informacin sealada por dichos marcadores, sino que tambin extrae el nombre de la clase o el nombre del mtodo asociado al comentario. De esta forma, podemos generar una documentacin de programa bastante digna con una cantidad mnima de trabajo.

La salida de Javadoc es un archivo HTML que se puede examinar con el explorador web. Javadoc permite crear y mantener un nico archivo fuente y generar automticamente una documentacin bastante til. Gracias a Javadoc. disponemos de un estndar bastante sencillo 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 Javadoc, denominadas docels, si queremos realizar operaciones especiales con la informacin procesada por Javadoc (por ejemplo, para generar la salida en un formato distinto). Los doclets se explican en el suplemento que podra encontrar en http ://AfindVlew.net/Bnoks/BetterJava.

Lo que sigue es simplemente una introduccin y una breve panormica de los fundamentos de Javadoc. En la documentacin del JDK podr encontrar una descripcin ms exhaustiva Cuando desempaquete la documentacin, vaya al subdirec- torio tooldocs (o haga clic en el vinculo tooldocs"). Sintaxis

Todos los comandos de Javadoc estn incluidos en comentarios que tienen el marcador /**. Esos comentarios terminan con */ como es habitual Existen dos formas principales de utilizar Javadoc: mediante HTML embebido o mediante marcadores de documentacin". Los marcadores de documentacin autnomos son comandos que comienzan con (u ' y se incluyen al principio de una linea de comentarios (sin embargo, un carcter inicial sera ignorado). Los marcadores de documentacin en lnea pueden incluirse en cualquier punto de un comentario Javadoc y tambin comienzan con *(&/. pero estn encenados entre llaves

Existen tres tiposr-' 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 comentano de campo aparece justo antes de la definicin de un campo y e! comentario de un mtodo aparece justo antes de la definicin del mismo. He aqu un sencillo ejemplo: //: object/Documentationl .java /** Comentario de clase */ public class Documentationl {

/** Comentario de campo / public int i; /* Comentario de mtodo public void () {)

) ///t-

Observe que Javadoc slo procesar la documentacin de los comentarios para los miembros public y proteeted. Los comntanos para los miembros prvate y los miembros con acceso de paquete ( vase el Capitulo 6, Control de acceso) se ignoran, no generndose ninguna salida para ellos (sin embargo, puede utilizar el indicador prvate para incluir tambin la documentacin de los miembros prvate). Esto tiene bastante sentido, ya que slo los miembros de tipo public y proteeted estn disponibles fuera del archivo, por lo que esto se ajusta a la perspectiva del programador de clientes.

La salida del cdigo anterior es un archivo HTML que tiene el mismo formato estndar que el resto de la documentacin Java, por lo que los usuarios estarn acostumbrados a ese formato y podrn navegar fcilmente a travs de las clases que 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 comntanos HTML al documento HTML generado. F.sto nos permite utilizar completamente las caractersticas HTML; sin embargo, el motivo principal de uso de ese lenguaje es dar formato al cdigo, como por ejemplo en //: object/Documentation2. java /**

<pre> System.out.println(new Date());

</p ro */

///:-

Tambin podemos usar edigo HTML como en cualquier otro documento web. para dar formato al texto normal de las descripciones: //: object/Documentation3. java /**

Se puede <em>incluso</em> insertar una lista: <ol> <ii Elemento uno <li> Elemento dos

<l> Elemento tres </ol>

///:-

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 reformatea todo para que se adapte al estilo estndar de la documentacin. No utilice encabezados como <h1> 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 iodos los tipos de comemanos de documentacin: de clase, de campo y de mtodo.

Algunos marcadores de ejemplo

He aqui algunos de los marcadores Javadoc disponibles para la documentacin de cdigo. Antes de tratar de utilizar Javadoc de forma seria, consulte la documentacin de referencia de Javadoc dentro de la documentacin del JDK. para \cr las diferentes formas de uso de Javadoc. @see

F.ste marcador permite hacer referencia a la documentacin de otras clases. Javadoc generar el cdigo HTML necesario, hipervmculando los marcadores (a see a los oros fragmentos de documentacin. Las formas posibles de uso de este marcador son: @see nombreclase ^see nombreclase-completamente-cualif i cado r.-see norabreclase-completamente-cualificado fcnorabre-mtodo

Cada uno de estos marcadores aade una entrada See Also (Vase tambin) hipervinculada al archivo de documentacin generado. Javadoc no comprueba los hipervinculos para ver si son vlidos. {@link paquete.clasemiembro etiqueta}

Muy similar a (q see. excepto porque se puede utilizar en linea y emplea la etiqueto como texto del hipervncuk), en lugar de "See Also.

{@docRoot}

Genera la ruta relativa al directorio ra/ de la documentacin. Resulta til para introducir hipervinculos 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 documento actual @version

Tiene la forma: versin informacin-versin

en el que Informacin-versin es cualquier informacin significativa que queramos incluir. Cuando se aade el indicador a versin en la linea de comandos Javadoc. la informacin de versin ser mostrada de manera especial en la documentacin HTML generada.

@author

Tiene la forma: author informacin-autor

donde informacin-autor es, normalmente, nuestro nombre, aunque tambin podramos incluir nuestra direccin de correo electrnico o cualquier otra informacin apropiada. Cuando se incluye el indicador (aauthor en la linea de comandos Javadoc. se inserta la informacin de autor de manera especial en la documentacin HTML generada.

Podemos incluir mltiples marcadores de autor para incluir una lista 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 permite indicar la versin del cdigo en la que se empez a utilizar una caracterstica concreta. En la documentacin HTML de Java, se emplea este marcador para indicar que versin del JDK se est utilizando.

@param

Se utiliza para la documentacin de mtodos y tiene la forma: jjparam nombreparmetro descripcin

donde nombre-parmetro es el identificador dentro de la lista de parmetros del mtodo y descripcin es un texto que puede continuar en las lineas siguientes. La descripcin se considera terminada cuando se encuentra un nuevo marcador de documentacin. Puede haber mltiples marcadores de este tipo, normalmente uno por cada parmetro. @return

Se utiliza para documentacin de mtodos y su formato es el siguiente: Sreturn descripcin

donde descripcin indica el significado del valor de retomo. Puede continuar en las lneas siguientes.

@throws

Las excepciones se estudian en el Captulo 12, Tratamiento Je errores mediante excepciones. Por resumir, se trata de objetos 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: sthrows nombre-case-completamente-cualificado descripcin

donde nombiV-clase-completamente-cualiftcado proporciona un nombre no ambiguo de una clase de excepcin definida en alguna otra parte y descripcin (que puede ocupar las siguientes lineas) indica la razn por la que puede generarse este tipo concreto de excepcin despus de la llamada al mtodo. @deprecated

Se utili/a para indicar caractersticas que han quedado obsoletas debido a la introduccin de alguna otra caracterstica mejorada. Este marcador indicativo de la obsolescencia recomienda que no se utilice ya esa caracterstica concreta, ya que es probable que en el futuro sea eliminada Un mtodo marcado como (a depreeated hace que el compilador genere una advertencia si se usa. En Jav a SE5, el marcador Javadoc (a depreeated ha quedado sustituido por la anotacin (a Depreeated (hablaremos de este tema en el Captulo 20. Anotaciones).

Ejemplo de documentacin

Me aqu de nuevo nuestro primer programa Java, pero esta vez con los comentarios de documentacin incluidos: //: object/HelloDate.java import j ava.til.; '** El primer programa de ejemplo del libro.

Muestra una cadena de caracteres y la fecha actual. author Bruce Eckel wauthor www.MindView.net

aversin 4.0 */ public class HelloDate { / * * Punto de entrada a la clase y a la aplicacin.


*

sparam args matriz de argumentos de cadena

* throws exceptions No se generan excepciones */

public static void main(String(] args) { System.out.println("Helio, it's: "); System.out.println(new Date(J);

} ) / Output: (55% match) Helio, it's: Wed Oct 05 14:39:36 MDT 2005 ///:-

a primera linea del archivo utiliza una tcnica propia del autor del libro que consiste en incluir V/f como marcador especial en la linea de comentarios que contiene el nombre del archivo fuente. Dicha lnea contiene la informacin de ruta del archivo (object indica este captulo) seguida del nombre del archivo. La ltima lnea tambin termina con un comentario, y ste (7//:-*) indica el final del listado de cdigo fuente, lo que permite actualizarlo automticamente dentro del texto de este libro despus de comprobarlo con un compilador y ejecutarlo.
1.

El marcador /* Output: indica el comienzo de la salida que generar este archivo. Con esta forma concreta, se puede comprobar automticamente para verificar su precisin. En este caso, el texto (55% match) indica al sistema de pruebas que la salida ser bastante distinta en cada sucesiva ejecucin del programa, por lo que slo cabe esperar un 55 por ciento de correlacin con la salida que aqu se muestre. La mayora de los ejemplos del libro que generan una salida contendrn dicha salida comentada de esta forma, para que el lector pueda ver la salida y saber que lo que ha obtenido es correcto. Estilo de codificacin

El estilo descrito en el manual de convenios de cdigo para Java. Code Conventions for /he Java PmgrammingLanguager, consiste en poner en mayscula la primera letra del nombre de una clase. Si el nombre de la clase est compuesto por varias ' Pam ahonur espacio tanto en el libro como en las presentacin de los seminario* no hemos podido seguir todos Jos directrices que se marcan en ese icxlo. per el lector podrA ver que el citilo empleado en el libro %c ajusia lo mximo posible al estandar recomendado en Java

palabras, stas se escriben juntas (es decir, no se emplean guiones bajos para separarlas) y se pone en mayscula la primera letra de cada una de las palabras integrantes, como en: class AIlTheColorsOfTheRainbow { // ...

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 minscula, por ejemplo: class AIlTheColorsOfTheRainbow | int anlntegerRepresentingColors; void changeTheHueCfTheColorint newHue) { // ...

//

tan largos, asi que procure que su longitud no sea excese ajusta a las directrices de ubicacin de llaves de aper-

Recuerde que el usuario se ver obligado a escribir estos nombres siva.

F.1 cdigo Java que podr encontrar en las bibliotecas Sun tambin tura y cierre que hemos utilizado en este libro. Resumen

El objetivo de este capitulo es explicar los conceptos mnimos sobre Java necesarios para entender cmo se escribe un programa sencillo. 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 forma haga esto, luego lo otro y despus lo de ms all. En los dos captulos siguientes vamos a presentar los operadores bsicos utilizados en los programas Java, y luego mostraremos como controlar el flujo del programa.

Ejercicios

Normalmente, distribuiremos los ejercicios por lodo el capitulo, 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 The Thmking in Java Annotated Soluiion Guete, a la venta en invw.AJindllew.net.

Ejercicio 1: (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 predeterminada.

Ejercicio 2: (I) A partir del ejemplo HclloDate.java de este capitulo, 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 definir este mtodo como static y de incluir la lista de argumentos, incluso aunque no la vaya a utilizar Compile el programa con javac y ejectelo con java. Si est utilizando otro entorno de desarrollo distinto de JDK. averige cmo compilar y ejecutar programas en dicho entorno.

Ejercicio 3: (l) Recopile los fragmentos de cdigo relacionados con ATypeNante y transfrmelos en un programa que

se pueda compilar y ejecutar

Ejercicio 4: (I) Transforme los fragmentos de cdigo DataOnly en un programa que se pueda compilar y ejecutar.

Ejercicio 5:(I) Modifique el ejercicio anterior de modo que los valores de los datos en DataOnly se asignen e impri man en main( ).

Ejercicio 6: (2) Escriba un programa que incluya e invoque el mtodo storage( ) definido como fragmento de cdigo

en el capitulo.

Ejercicio 7: funcional.

(1J Transforme los fragmentos de cdigo Incrcmcntable en un programa

Ejercicio 8: (3) Escriba un programa que demuestre que independientemente de cuntos objetos se creen de una clase

concreta, slo hay una nica instancia de un campo static concreto definido 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 envoltorios.

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 matriz de objetos String extrada de la lnea de comandos.

Ejercicio 11: (l) Transforme el ejemplo AUTheColorsOfrheRainbow programa que se pueda compilar y eje

en un

cutar.

Ejercicio 12: (2) Localice el cdigo para la segunda versin de HclluDutc.java. que es el ejemplo simple ci documen

tacin mediante comentarios. Ejecute Javadoc con ese archivo y compruebe los

resultados con su explorador web. Ejercicio 13: (I) Procese Dcumentationl.java. Documentation2.java y Documcntation3.jaxa con Javadoc Ven-

fique la documentacin resultante con su explorador web.

Ejercicio 14: anterior.

< l > Aada una lista HTML de elementos a l3 documentacin del ejercicio

Ejercicio 15: (l) Tome el programa del Ejercicio 2 y adale comentarios de documentacin. Extraiga esos comntanos

de documentacin Javadoc para generar un archivo HTML y visualcelo con su explorador web.

Ejercicio 16: (I) En el Capitulo 5, metalizacin v limpieza, localice el ejemplo Overlnading.java y aada documenta

cin de tipo Javadoc. Extraiga los comentarios de documentacin Javadoc para generar un archivo HTML y visualcelo con su explorador web.Operadores

En el nivel inferior* los datos en Java se manipulan utilizando 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-H- Java ha aadido tambin algunas mejoras y ha hecho algunas simplificaciones.

Si est familiarizado con la sintaxis de C o C-H-, puede pasar rpidamente por este captulo y el siguiente, buscando aquellos 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 C, que puede descargarlo gratuitamente en wwto.MindView.net. Contiene conferencias, presentaciones, ejercicios y soluciones especficamente diseados para familiarizarse con los fundamentos necesarios 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");

Puede observar que esto no es slo un montn de texto que escribir (lo que quiere decir muchos movimientos redundantes de los dedos), sino que tambin resulta bastante incmodo de leer. La mayora de los lenguajes, tanto antes como despus de Java, han adoptado una tcnica mucho ms sencilla para expresar esta instruccin de uso tan comn.

lin el Capitulo 6. Control de acceso se presenta el concepto de importacin esttica aadido a Java SE5, y se crea una pequea biblioteca que permite simplificar la escritura de las instrucciones de impresin. Sin embargo, no es necesario comprender los detalles para comenzar a utilizar dicha biblioteca. Podemos reescribir el programa del ltimo capitulo empleando esa nueva biblioteca: //: operators/HelloDate.java import java.Util.*; import static net .mj.ndview.util. Print. ; public class HelloDate ( public static void maintStringU args) { print("Helio, it's: "); print(new Date<)) ;

} ) / Output: (55% match) Helio, it's: Wed Oct 05 14:39:05 MDT 2005 ///:-

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 alguno de los sitios espejo Descomprima el rbol de cdigo y aada el directorio raz 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)

Aunque la utilizacin de nct.mindview.util.Print simplifica enormemente la mayor pane del codigo. su uso no est justificado en todas las ocasiones. Si slo hay unas pocas instrucciones de impresin en un programa, es preferible no incluir la instruccin imporf y escribir el comando completo System.out.println( ).

Ejercicio 1: (l) F.scriba un programa que emplee tanto la forma "corta" como la normal 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 llamadas normales a mtodos, pero el efecto es el mismo. La suma, el operador ms unano [+), la resta y el operador menos unariu ( ). la multiplicacin (*). la divisin (/) y la asignacin (=} funcionan de forma bastante similar a cualquier otro lenguaje 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 precisamente en crear ese efecto colateral, pero resulta conveniente pensar que el valor generado tambin esta disponible para nuestro 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 String sopona y *+=\ Precedencia

La precedencia de los operadores define la forma 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 multiplicacin 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 utilizar parntesis para que el orden de evaluacin est indicado de manera explcita. Por ejemplo, observe las instrucciones (1) y (2): //: operacors/Precedence.java public ciass Precedence ( System.out.println (**a ) } / OUtpUt: a = 5 b 1 *///:- M * a M b = M * b) :

Estas instrucciones tienen aspecto similar, pero la salida nos muestra que sus significados son bastante distintos, debido a la utilizacin de parntesis.

Observe que la instruccin System.out.println( ) incluye el operadorEn este contexto. *+* significa concatenacin de cadenas de caracteres y en caso necesario, conversin de cadenas de caracteres. Cuando el compilador ve un objeto String seguido de *+' seguido de un objeto no String. intenta convenirlo a un objeto String. Como puede ver en la salida, se ha efectuado conectamente la conversin de int a String para a y b. Asignacin

La asignacin se realiza con el operador =. Su significado es: Toma el valor del lado derecho, a menudo denominado rvo- 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 Ivalor debe ser una variable detenninada, designada mediante su nombre (es decir, debe existir un espacio fsico para almacenar el valor). Por ejemplo, podemos asignar un valor constante a una variable: pero no podemos asignar nada a un valor constante, ya que una constante no puede ser un Ivalor (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 cualquier 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 modificacin. Como programador es lo que cabria esperar en la mayora de las situaciones.

Sin embargo, cuando asignamos objetos, la cosa cambia. Cuando manipulamos un objeto lo que manipulamos es la referencia, asi que al asignar 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 c - d para sendos objetos, lo que al final tendremos es que tanto c como d apuntan al objeto al que slo d apuntaba originalmente. He aqu un ejemplo que ilustra este comportamiento: ' : operators/As9ignment.java // La asignacin de objetos tiene su truco. import static net.mindview.til.Print. *; class Tank ( int level;

) public class Assignment ( public static void main(StringH args) { Tank ti = new Tank( ) ; Tank t2 - new Tank (); ti.level = 9; t2. level = 4 7? print ("1: ti.level: N. t.2. level -. ti = t2; print("2: ti.level: " ti.level + H, t2. level: ti.level = 27; print ("3:ti. level: ", t2.level: " " * + ti.level 4t2.level); M -f t2. level); " " t ti.level t2. level) ;

} } /* Output: 1: ti.level: 9, t2.level: 47 2: M.1pv17. t2.level: 47 3: ti.level: 27, t2.level: 27 * / / / ; -

La clase Tank es simple, y se crean dos instancias de la misma itl y t2) dentro de mafn( ). Al campo level de cada objeto Tank se le asigna un valor distinto, luego se asigna t2 a ti. y despus se modifica ti. En muchos lenguajes de programacin esperaramos que II y t2 fueran independientes en todo momento, pero como hemos asignado una referencia, al cambiar el objeto 11 se modifica tambin el objeto t2. Esto se debe a que tanto 11 como (2 contienen la misma referencia, que est apuntada en el mismo objeto (la referencia original contenida en (1, que apuntaba al objeto que tenia 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).

ste fenmeno a menudo se denomina creacin de alias. y representa una 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 objeto? Podemos reescribir la asignacin a otro nivel y utilizar:
F.

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 un valor float y utilcela para ilustrar el fenmeno de la creacin de alias. 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: //: operators/PassObject.java // El paso de objetos a los mtodos puede no ser // lo que cabra esperar. import static net .mindview.uti .Print. clsss Letter { char C;

) public class PassObject ( static void f{Letter y) ( y. c * ' 2';

) public static void main (String U args) { Letter x * new Letter() ; x.c = a ? print(M1: x.c: " + x.c) ; t (x) t print (H2: x.c: M + x.c);

) } / Output: 1: x.c: a 2: x.c: 2 ///:-

En muchos lenguajes de programacin, el mtodo f() haria una copia de su argumento Letter deniro del mbito del mtodo. pero aqu. una ve/ ms. lo que se est pasando es una referencia, por lo que la linea: y.c = z ' ;

loque est haciendo es cambiar el objeto que est fuera de ( ).

El fenmeno de creacin de alias y su solucin es un tema complejo de! que se trata en uno de los suplementos en linea disponibles 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 float 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 disponibles en la mayora de los lenguajes de programacin: suma (+), resta (-), divisin (/). multiplicacin (*) y mdulo (%. que genera el resto de una divisin entera). La divisin entera trunca en lugar de redondear el resultado.

Java tambin utiliza la notacin abreviada de C/C-*-+ que realiza una operacin y una asignacin al mismo tiempo. Este tipo de operacin se denota mediante un operador seguido de un signo de igual, y es coherente con todos los operadores del lenguaje (all donde tenga sentido). Por ejemplo, para sumar 4 a la variable x y asignar el resultado a x. utilice: x += 4

Este ejemplo muestra el uso de los operadores matemticos: //: operators/MathGps.]ava // Ilustra los operadores matemticos. import java.til.*; import static net.mindview.til.Print; public class MathOps ( public static void main(Strng [3 args) { // Crea un generador de numeres aleatorios con una cierta semilla: Random rand = new Random (47),* int i, j. k; // Elegir valor entre 1 y 100c j = rand.nextInt <10 0 i 4 1; printi"j :- j);

k = rand, next Inc (100) 4 1; print("k : " k) ,* i * j * p r i n t k : " * i) ; i - j print{"j - k : " + ); i k / -jt print ("k / j : " + i) ; I * k . j; print("k j : -t i); 1-k%j/ print("k % j r " 4 i) ; j *= imprint %= k : M j); // Pruebas con nmeros en coma flotante: float a, v, w; // Se aplica tambin a los de doble precisin v rand.nextFloatO ; print("V : M 4- v) ; w - rand.nextFloat(); print ("w : " * w) / u V w; print<"v w t " + ai); u = v - w; print (v - w : " 4 u) f u = v * wf* print (wv w : M u) j u = v / w; print(Hv / w i " * u) j // Lo siguiente tambin funciona para char, // byte, short, int, long y double: u V; print Cu v : " + u) ; u -= V; print("u -= v : n 4 u) ; u * v; print ("u ** v : h -f u); u / v; print ("u /* v : " * u) ; ) ) /* Output: j : 59 k : 56 j f k : 115 j - k : 3 k / j : 0 k * j : 3304 k % j : 56 j %k : 3
V

: 0.5209454 0.0534122

v + w : 0.5843576 V - W : 0.47753322 * w : 0.028358962v / w : 9.940527 u *= u -oV : 9.940527 u *= v : 5.2778773

10.471473

u / V : 9.940527

*///:-

Para generar nmeros, el programa crea en primer lugar un objeto Random Si se crea un objeto Random sin ningn argumento, Java usa la hora actual como semilla 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 semilla (un valor de inicializacin para el generador de nmeros aleatorios que siempre genera la misma secuencia para una determinada semilla) al crear el objeto Random, se generarn siempre los mismos nmeros aleatorios en cada ejecucin del programa, por lo que la salida se podr verificar. 11 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 mtodos nevtlii ) y nextFloat( ) (tambin se pueden invocar nextl.ong( ) o nextDoubIc( )). F.I argumento de nextlnt( ) establece 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:
11

(2) Escriba un programa que calcule la velocidad utilizando una distancia

F.I numero 47 se utilizaba como nmero mgico" en una universidad en la que estudi. y desde entonce* lo utilizo.

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 forma en que est escrita la expresin. Por ejemplo, la instruccin: x * -a;

tiene un significado 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) i

El menos unario invierte el signo de los datos. El ms unario proporciona una simetra con respecto al menos unario. aunque no tiene ningn efecto. Autoincremento y autodecremento

Java, como C. dispone de una serie de abreviaturas. Esas abreviaturas pueden hacer que resulte mucho ms fcil escribir el cdigo: en cuanto a la lectura, pueden simplificarla 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 ni. la expresin ++a es equivalente a (a = a + 1) Los operadores de incremento y decremento no slo modifican la variable, sino que tambin generan el valor de la misma como resultado.

I lay dos versiones de cada tipo de operador, a menudo denominadas prefija y postfijo. Preincremento significa que el operador H- aparece antes de la variable, mientras que post-incremento significa que el operador ++ aparece detrs de la variable. De forma similar, pre-decremento 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: //: operators/AutoInc.java // I lustra los operadores -*- y import static net.mindview util.Print.j public class Autolnc ( public static void main (String [] args) {

Puede ver que para la forma prefija, se obtiene el valor despus de realizada la operacin, mientras que para la forma postn. 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.

! } / Output: i : 1 M-i : 2 i+* : 2 i : 3 : 2 i-- : 2 i : 1 ///:-

El operador de incremento es. precisamente, una de las explicaciones del por qu del nombre C*H-, que quiere decir "un paso ms all de C'*\ En una de las primeras presentaciones realizadas acerca de Java, Bill Joy (uno de los creadores de Java), dijo que Java=C+-* (C ms ms menos menos), para sugerir que Java es C++ pero sin todas las complejidades inne

cesarias. por lo que resulta un lenguaje mucho ms simple. A medida que vaya avanzando a lo largo del libro, podra ver que muchas partes son ms simples, aunque en algunos otros aspectos Java no resulta mucho ms sencillo que C+-K Operadores relacinales

Los operadores relacinales generan un resultado de tipo tmoleun Evalan la relacin existente entre los valores de los opc- randos. Una expresin relacional produce el valor true si la relacin es cierta y false si no es cierta. Los operadores relacinales son: menor que (<). mayor que (>), menor o igual que (<=). mayor o igual que (>=). equivalente (=) y no equivalente (!*). La equivalencia y la no equivalencia funcionan con todas las primitivas, pero las otras comparaciones no funcionan con el tipo boolean. Puesto que los valores boolean slo pueden ser true <> false. las relaciones mayor que" y menor que no tienen sentido.

Comprobacin de la equivalencia de objetos

Los operadores relacinales = y t= tambin funcionan con todos los objetos, pero su significado suele confundir a los que comienzan a programar en Java. He aqu un ejemplo: //: operators/Equivalence.java public class Equivalence ( public static void mam(String[] args) { Integer ni = new Integer(47); Integer n2 * new Integer<47); System.out.printlnlnl == n2); System.out.Drintlntnl 1 n2);

) } / Output: false true

*///:

La instruccin System.out.printIn(nl = n2> imprimir el resultado de la comparacin boolcana que contiene. Parece que la salida debera ser "true y luego false. dado que ambos objetos Integer son iguales, aunque el contenido de los objetos son los mismos, las referencias no son iguales. Los operadores = \ != comparan referencias a objetos, por lo que la salida realmente es false"

y luego true" Naturalmente, este comportamiento suele sorprender al principio a los programadores.

Qu pasa si queremos comparar si el contenido de los objetos es equivalente? Entonces debemos utili/ar el mtodo especial equalsf ) disponible para todos los objetos {no para las primitivas, que funcionan adecuadamente con y !=). He aqu la forma en que se emplea: //: operators/EqualsMethod.java public class EqualsMethod ( public static void mam (String U args) { Integer ni * new Integeri47); Integer n2 = new Integer(47); System.out.printlntnl.equals{n2));

) ) / Output: true *///:-

Hl resultado es ahora el que esperbamos. Aunque, en realidad, las cosas no son tan sencillas. Si creamos nuestra propia clase, como por ejemplo: // operators/EqualsMethod2.java // equalsO predeterminado no compara los contenidos. class Valu ( Lnt i;

) public class EqualsMethod2 { public static void main(StringIJ args) ( Valu vi = new ValueO; Valu v2 = new Valu(); vi.i v2.i 100; System.out.println fvl.equalsv2));

) } / Output: false *///:-

los resultados vuelven a confundimos. El resultado es falso. Esto se debe a que el comportamiento predeterminado de equals() consiste en comparar referencias. Por tanto, a menos que sustituyamos equals() en nuestra nueva clase, no obtendremos el comportamiento deseado. Lamentablemente, no vamos a aprender a sustituir unos mtodos por otros hasta el captulo dedicado a la Reutilizacin de clases, y no veremos cul es la forma adecuada de definir equals( ) hasta el Capitulo 17, Anlisis detallado de los contenedores. pero mientras tanto tener en cuenta el comportamiento de equals( ) nos puede ahorrar 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 ame (nombre) y says

(ladrido). En main( ). cree dos objetos perro con los nombres spot" (que ladre diciendo RutY!") y scruffy" (que ladre diciendo. Wurf!"). Despus, muestre sus nombres y el sonido que hacen al ladrar.

Ejercicio 6: (3) Continuando con el Ejercicio 5, crce una nueva referencia Dog y asignela al objeto de nombre spot

Realice una comparacin utilizando = y equa!s( ) para todas las referencias. Operadores lgicos

Cada uno de los operadores lgicos AND (&&). OR (||) y NOT (!) produce un valor boolean igual a true o false basndose en la relacin lgica de sus argumentos. Este ejemplo utiliza los operadores relacinales y lgicos: //: operators/Bool.java // Operadores relacinales y lgicos, import java.util; import static net.mindview.util.Print.*;

public class Bool ( public static void main(String[] args) ( Random rand = new Random!4 7); ir*t i = rand.nextInt(100); int j = rand.nextInt (100) ; print ("i = " -* i] ; print ("j = " 4 j) ;

*{(!'< 10.) || (j < 10))

) ) /* Output t X r- 58 j = 55 i > j is true i < j is false i >= j is true i <= j is false i == 3 is false i 1* j is true U < 10) 6c* (j < 10) is li < 1 0 1 || (j < 10) is

false false

///:-

Slo podemos aplicar AND, OR o NOT a valores de tipo boolean. No podemos emplear un valor que no sea boolean como si fuera un valor booleano en una expresin lgica como a

que sucede en C y el mediante

diferencia de C++. Puede ejemplolos intentos fallidos de realizar esto, marcas decomentarios *//! (esta sintaxis comentarios

desactivados de

permite la eliminacin automtica de comentarios para facilitar las pruebas). Sin embargo, las expresiones subsiguientes generan valores de tipo boolean utilizando comparaciones relacinales y a continuacin realizan operaciones lgicas con los resultados.

Observe que un valor boolean se convierte automticamente a una forma de tipo texto apropiada cuando se les usa en lugares donde lo que se espera es un valor de tipo String

Puede reemplazar la definicin de valores ni en el programa anterior por cualquier otro tipo de dato primitivo excepto boolcan. Sin embargo, teniendo 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 distinto de cero.

Ejercicio 7:(3)Escriba

unprograma

que

simule 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 determinada de forma no ambigua. Como resultado, puede ser que las ltimas partes de una expresin lgica no lleguen a evaluarse. He aqu un ejemplo que ilustra este fenmeno de cortocircuito. //: operators/ShortCircuit. java // Ilustra el comportamiento de cortocircuito // al evaluar los operadores lgicos, import stacic ne: .ramview.util. Pnnc . * ; public class ShortCircuit ( return val <2;

i static boolean test3(int val) { print("test3 (" * val + ")*); print "result: "(val < 3)); return val < 3;

i public static voad mainString[I args) { boolean b = testl(0) && test2(2) uu test3l2); print("expresaion is *' * b) ;

l } /* Output: testl(0) result: true test2(2) result: false expreasion is false *///:-

Cada una de las comprobaciones realiza una comparacin con el argumento y devuelve true o false Tambin imprime la informacin necesaria para que veamos que est siendo invocada. Las pruebas se utilizan en la expresin: testl(0) && 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 pruebas produce un resultado true. por lo que contina con la evaluacin de la expresin. Sin embargo, la segunda prueba produce 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. Literales

Normalmente, 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 aadiendo cierta informacin adicional en la forma de caracteres asociados con el valor literal. El cdigo siguiente muestra estos caracteres: //: operators/Literals.java import static net.mindview.til.Print . * ; puJblic class Literals ( public static void main( String [] args) { int il = 0x2f; //

Hexadecimal (minscula) print("il: " * Integer.toBinaryString(il)) ; int i2 = 0X2 F; // Hexadecimal (mayscula) pnnt("i2: M Integer.toBinaryStringti2)); int i3 = 0177; // Octal (cero inicial) printfi3: " * Integer.toBinaryString(i3))/ char c = Oxffff; // mximo valor hex para char print fe: " + Integer. toBinaryString (c) ) ; byte b = 0x7f; // mximo valor hex para byte printfb: w + Integer.toBinaryString(b)) ; short s = 0x7fff; // mximo valor hex para short print fs: " Integer. toBinaryString(s)) ; long ni = 20QL; // sufijo lona long n2 * 2001; // sufijo long (pero puede ser confuso) long n3 = 200; float fl = 1; float f2 1F; // sufijo float float f3 = lf; ff sufijo float double di = Id; // sufijo double double d2 = ID; // sufijo double // <Hex y Octal tambin funcionan con long)

) } /* Output: il: 101111 2: 101111 13: 1111111 c: 1111111111111U1 b: 1111111 S : 111111111111111

*///:-

Un carcter situado al final de un valor literal permite establecer su tipo La L mayscula o minscula significa Ion} (sin embargo, utilizar una I minscula es confuso, porque puede parecerse al nmero uno). Una F mayscula o minscula significa 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 Ox

o 0X seguido de 0-9 o a-f en mayscula o minscula. Si se intenta inicializar una variable con un valor mayor que el mximo que puede contener (independientemente de la forma numrica del valor), el compilador dar un mensaje de error. Observe, en el cdigo anterior, los valores hexadecimales mximos permitidos para char. byte y short. Si nos excedemos de stos, el compilador transformar automticamente el valor a int y nos dir que necesitamos una proyeccin hacia abajo para la asignacin (definiremos las proyecciones posteriormente en el capitulo). De esta forma, sabremos que nos hemos pasado del lmite permitido.

Los valores octales (base 8) se denotan incluyendo un cero inicial en el nmero y utilizando slo los dgitos 0-7.

No existe ninguna representacin literal para los nmeros binarios en C, C++ o Java. Sin embargo, a la hora de trabajar con notacin hexadecimal y octal. a veces resulta til mostrar la forma binaria de los resultados. Podemos hacer esto fcilmente con los mtodos static toBinaryString( ) de las clases Integer y Long. Observe que. cuando se pasan tipos ms pequeos a Integer.toBinaryString ). el tipo se convierte automticamente a int

Ejercicio 8: long. Utilice

(2) Demuestre que las notaciones hexadecintal y ocia! funcionan con los valores

Long.toBinarvString( ) para mostrar los resultados. Notacin exponencial

Los exponentes utilizan una notacin que a mi personalmente me resulta extraa: //: operators/Exponents.java // Me* significa 10 elevado a". public class Exponen!:5 ( public static void main(Strlng[] args) ( // *e' en mayscula o minscula funcionan igual: float expFloat = 1.39e-43f? expFloat * 1.39E-43f; System.out.println(expFloatJ; double expDouble * 47e4 7d; // ' d es opcional double expDoubie2 = 47e47; // automticamente double System.out.println(expDouble)

) ) /* Outpuc: 1.39E-43 4.7E48

///;-

En el campo de las ciencias y de la ingeniera, *e hace referencia a la base de los logaritmos naturales, que es aproximadamente 2.718 (en Java hay disponible un valor double ms preciso, que es Math.E). Esto se usa en expresiones de exponen- ciacin. como por ejemplo 1.39 x e4-\ que significa 1.39 x 2.71843. Sin embargo, cuando se invent el lenguaje de programacin FORTRAN, decidieron que e significara diez elevado a, lo cual es una decisin extraa, ya que FORTRAN fue diseado para campos de la ciencia y de la ingeniera, asi que cabria esperar que sus diseadores tendran en cuenta lo confuso de introducir esa ambigedad 12. En cualquier caso, esta costumbre fue tambin introducida en C, O * 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 tniduccin mental cuando vea una expresin como 1.39 e-43f en Java; ya que quiere decir 1.39 x 10"

Observe que no ca necesario utilizar el carcter sufijo cuando el compilador puede deducir el tipo apropiado. Con: long n3 * 200;

John Kirkhum escribe. Comenc a escribir programas informticos en 1962 en FORTRAN II en mi IBM 1620 Por aquel entonces y a lo largo de las dcadas te 1960 y 197. FORTRAN era un lenguaje donde todo se escriba en maysculas Probablemente, la razn era que muchos de los dispositivos de entrada eran anticuas unidades de teletipo que utilizaban el cdigo Baudot de cinco bits que no dispona de mmusculas. La ~E" en la notacin exponencial era tambin mayscula y no se conlundia nunca con la base de los logaritmos naturales "e" que siempre se escribe cu minscula La **E simplemente quera decir exponencial, que era la base para el sistema de numeracin que *c estaba utilizando, que normalmente era 10 En aquella poca, los programadores tambin empleaban los nmeros ocales. Aunque nunca vi que nadie lo utilizara, si yo hubiera visto un nmero octal en notacin exponencial. habra considerado que estaba en base 8. l.a pnmera vez que vi tin exponencial uiilizando una fue a finales de la dcada de 1970 y tambin a mi me pareci contuso; el problema surgi cuando empezaron a utilizarse mmscula> en FOKTAN. no al principio. De hecho, disponame^ de funciones que podan usarse cuando <c quisiera emplear la base de los logaritmos naurnles. pero todas esas funciones se escriban en maysculas.
12

no existe ninguna ambigedad, por lo que una L despus del 200 seria superiluo. Sin embargo, con: float f4 = le-43f; // 10 elevado a

el compilador normalmente considera los nmeros exponenciales como de tipo double por lo que sin la f final, nos daria un error en el que nos informara de que hay que usar una proyeccin para convertir el valor double a float

Ejercicio 9: (1) Visualice los nmeros ms grande y ms pequeo que se pueden representar con la notacin exponen

cial en el tipo lloat y en el tipo double Operadores bit a bit

t.os operadores bit a bit permiten manipular bits individuales en un tipo de datos entero primitivo. Para generar el resultado. los operadores bii 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 C. en el que a menudo se manipula el hardware directamente y es preciso configurar los bits de los registros hardware. Java se disert originalmente para integrarlo en codificadores para televisin, por lo que esta orientacin a bajo nivel segua teniendo sentido. Sin embargo, lo ms probable es que no utilicemos demasiado esos operadores bit a bit en nuestros programas.

Til operador bit a bit AND &) genera un uno en el bit de salida si ambos bus de entrada son iguales a uno; en caso contrario. genera un cero. El operador OR bit a bit (|) genera un uno en el hit de salida si alguno de los bits de entrada es un uno \ genera cero slo si ambos bus de emrada son cero. El operador bu a bu EXCLUSI\'fc OR o XOR (43A) genera un uno en el bit de salida si uno de los dos bits de entrada es un uno pero no ambos. El operador bit a bit NOT (^. tambin denominado operador de complemento o uno) es un operador unario. que slo admite un argumento (todos los demas operadores bit a bii son operadores binarios). El operador bit a bit NOT genera el opuesto al bit de entrada, es uno si el hit de entrada es cero y es cero si el bit de entrada es uno.

Los operadores bit a bit y los operadores lgicos utilizan los mismos caracteres, por lo que resulta til recurrir c un truco mnemnico para recordar cul es el significado correcto. Como los bits son pequeos slo se utiliza un carcter en los operadores bit a bit.

Los operadores bit a bit pueden combinarse con el signo = para unir la operacin y la asignacin: &=. |= y A= son operadores legtimos (puesto que - es un operador unario. no se puede combinar con el signo =),

El tipo boolean se traa como un valor de un nico bit, por lo que es algo distinto de los otros tipos primitivos. Se puede realizar una operacin AND. OR o XOR bit a bit. pero no se puede realizar una operacin NOT bit a bit (presumiblemente. para evitar 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. Asimismo, las operaciones bit .1 bit con valores booleanos incluyen un operador lgico XOR que no forma parte de la lista de operadores 'lgicos*. No se pueden emplear 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 a temados,

con un cero en el dgito menos significativo, y el segundo con un valor tambin alternado pero con un uno en el digno menos significativo (consejo: lo ms fcil es usar constantes hexadecimales para es;o). Tome estos dos valores y combnelos de todas las formas posibles utilizando los operadores bit a bit. y visualice los resultados utilizando lnte^er.toBinarvString( ). Operadores de desplazamiento

Los operadores de desplazamiento tambin sirven para manipular bits. Slo se les puede utilizar con tipos primitivos enteros. El operador de desplazamiento a la izquierda () genera como resultado el operando situado a la izquierd del operador despus de desplazarlo hacia la izquierda el nmero de bits especificado a la derecha del operador (insertando ceros en los bits de menor peso). El operador de desplazamiento a la derecha con signo () genera como resultado el operando situado a la izquierda del operador despus de desplazarlo hacia la derecha el nmero de bits especificado a la derecha del operador. El desplazamiento a la derecha con signo utiliza lo que se denomina extensin Je signo: si el valor es positivo. se insertan ceros en los bits de mayor peso; si el \ alor es negativo, se insertan unos en los bits de mayor peso. Java lia aadido Lambin un desplazamiento a la derecha sin signo >. que utiliza lo que denomina extensin con ceros: independientemente del signo, se insertan ceros

en los bits de mayor peso. Este operador no existe ni en C ni OH%

Si se desplaza un valor de tipo char. byte o short. ser convertido a int antes de que el desplazamiento tenga lugar y el resultado ser de tipo int. Slo se utilizaran los bits de menor peso del lado derecho; esto evita que se realicen desplazamientos con un nmero de posiciones superior al nmero de bits de un valor int Si se est operando con un valor Ion, se obtendr un resultado de tipo long y slo se emplearn los seis bits de menor peso del lado derecho, para asi no poder desplazar ms posiciones que el nmero de bits de un valor long.

Los desplazamientos se pueden combinar con el signo igual (= o = o >=). El Ivalor se sustituye por el Ivalor desplazado de acuerdo con lo que el rvalor marque. Existe un problema, sin embargo, con el desplazamiento a la derecha sin

signo combinado con la asignacin. Si se usa con valores de tipo byte o short, no se obtienen los resultados correctos. En lugar de ello, se transforman a int 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: //: operators/tJRShift. java II Prueba del desplazamiento a la derecha sin signo, import static net.mindview.util.Print. * ; public class URShift { public static void mam (String [] args) { int i -If print(Integer.toBinaryStri ng(il): t >>> 10; print(Integer.toBinaryStri ng ti)); long 1 -1;

print(Long.toBinaryString(l));

1 >>>* 10; print(Long.toBinaryString( 1)); short s = -1; print(Integer.toBinaryStri ng is)); s >>>* 10; print(Integer.toBinaryStri ng(s)); byte b * -1; print(Integer.toBinaryStri ng(b)); b >>>= 10; print(Integer.toBinaryStri ng(b)); b * -If print(Integer.toBinaryStri ng(b)) print(Integer.toBinaryStri ng(b>>>10));

} ) /* Output: 11111111111111111111111111111111 1111111111111111111111 1111111111111111111111111111111111111111111111111111111111111111 111111111111111111111111111111111111111111111111111111 11111111111111111111111111111111 11111111111111111111111111111111 111111111111X11111X1111111111111 11111111111111111111111111111111 11111111111111111111111111111111 11111111.11111111111111

*///:-

En el ltimo desplazamiento, el valor resultante no se asigna de nuevo a b. sino que se imprime directamente, obtenindose el comportamiento correcto.

He aqu un ejemplo que ilustra todos los operadores para el manejo de bits: II: operators/BitManipulation.java // 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 n rand, next Int O int j = rand.nextInt() ; printBinaryInt(M-1H, -1); printBinarylnt(M +1", !) ;int maxpos - 2147483647; printBinarylnt("maxpos", maxpos); int maxneg = -2147483648; printBinarylnt("maxneg", maxneg); printBinarylnt("i", i) ; printBinarylnt("-i", -i); printBinarylnt("i", -); printBinarylnt("j*, j); printBinarylnt C i & j" , i & i); printBinarylnt (" (-i) >>> 5", (-i} > 5); long 1 = rand.nextLona(); long m * rand. nextLong () ; printBinaryLong M-IL", -IL); printBinaryLong ("flL, flL) ; long 11 = 9223372036854775807L; printBinaryLong(HmaxposH, 11); long lln = -922337203685477580BL; printBinaryLong("maxneg". 1ln); printBinaryLongHl", 1); printBinaryLong("-1". -1); printBinaryLong(M-1". -1); printBinaryLong"m", m) ; pr me Bina ry Long ("1 & mM, I & m) ; printBinaryLong("1 | m", 1 | m) ; printBinaryLong ("1 ra*f IA m) ,* printBinaryLong('l << 5", 1 5); printBinaryLong(1 5", 1 >> 5); printBinaryLong("{-1) >> 5 ", (-1)>>5); printBinaryLong ("1 > 5", 1 >>> 5); printBinaryLong(M(1) >>> 5", (-1)>>>5);

} atacic void printBinarylnt (String s, int i) { print {s +- ", int: " + i ", binary:\n " Integer.toBinaryString(i));

) static void printBinaryLong(String s, long 1) { print(s + ", long: " 1 + binary:\n " Long.toBinaryString(1));

} } / * Output: -1, int: -1, bmary: 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: 100010111011011101111010110101 0-i. int: 1172028?79, binary: 100010111011011101111 0101101011 j, int: 1717241110, binary: 110011001011011000001 0100010110 i * j. mt: 570425364, binaryj 10001000000000000000000 0010100 i I j, int: -25213033, binaryi 111111100111111.10100 011110010111 i A j, int: -595638397, binary: 110111000111111101000 11110000011 i 5, int; 11497Q4736, binary: 1000100100010000101001 010100000 i 5, int: -36625900, binary:

111111011101000100100 01000010100 t~A) 5, int: 36625899, binary: 10001011101101110111101011 i >> 5, int: 97591828, binary: 101110100010010001000010100 (iI > 5, int: 36625899, binary: 10001011101101110111101011

*///:-

Los dos mtodos del final, print Binary lnt() y print Binary Long(). toman un valor Int o long. respectivamente, y lo imprimen en formato binario junto con una cadena de caracteres descriptiva. Adems de demostrar el efecto de iodos los operadores bit a bit para valores int y long. este ejemplo tambin muestra los valores mnimo, mximo, +1 y -1 para int y long para que vea el aspecto que tienen. Observe que el bit ms alto representa el signo: 0 significa positivo y 1 significa negativo. En el ejemplo se muestra la salida de la parte correspondiente a los valores int.

La representacin binaria de los nmeros se denomina complemento o dos con .signo.

Ejercicio 11: (3) Comience con un nmero que tenga un uno binario en la posicin ms significativa (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.toBinaryStringt )

Ejercicio 12: (3) Comience con un nmero cuyos dgitos binarios sean todos guales a uno. A continuacin desplcelo

a la izquierda y utilice el operador de desplazamiento a la derecha sin signo para desplazarlo a travs de todas sus posiciones binarias, visualizando los resultados con Integer.toBinaryStrng( ).

Ejercicio 13: (I) Escnba un mtodo que muestre valores char en formato binario. Ejectelo utilizando varios caracte

res diferentes.

Operador ternario if-else

El operador ternario, tambin 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 capitulo. La expresin tiene la forma: exp-booleana ? valoro j valorl

Si exp-booleana se evala como true, se evala vabrO 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 temario es mucho ms compacto. Aunque C (donde se origin este operador) se enorgullece de ser un lenguaje compacto, y el operador temario puede que se haya introducido en parte por razones de eficiencia, conviene tener cuidado a la hora de emplearlo de forma cotidiana, ya que el cdigo resultante puede llegar a ser poco legible.

El operador condicional es diferente de if-else porque genera un valor. He aqu un ejemplo donde se comparan ambas estructuras: //: operators/TernarylfElse.jav

a lnport static net.mndview.til.Print.*? pubic ciass TernaryIE1se ( static int ternarylint i) { retum i < 10 ? i 100 : i 10;

) static int scandardlfSise(int i) { ifCi < 10) return i 100; else return i 10;

) public static void main(String[] args' ( prmt (ternary O) ) ; print(ternary(10) ) ; print(standardlfElse(9)) ; print(standardlfElse 10));

) ) / Gutput: 900 100 900

100

*///r-

Puede ver que el cdigo de ternarv( ) es ms compacto de lo que sera si no dispusiramos det operador temano: la versin sm operador lemario se encuentra en standardlflllse( ) Sin embargo. standard lfEIse< ) es ms fcil de comprender y adems exige escribir muchos caracteres ms. Asi que asegrese de ponderar bien las razones a la hora de elegir el operador temario; normalmente, puede convenir utilizarlo cuando se quiera configurar una variable con uno de dos valores posibles. Operadores + y += para String

Existe un uso especial de un operador en Java: los operadores + y += pueden usarse para concatenar cadenas, como ya hemos visto. Parece un uso bastante natura! de estos operadores, an cuando no encaje demasiado bien con la forma tradicional en que dichos operadores se emplean.

Esta capacidad le pareci adecuada a los diseadores de C*H*. por lo que se aadi a C-H- un mecanismo de sobreetn-ga de operadores para que los programadores C pudieran aadir nuevos significados casi a cualquier operador. Lamentablemente. la sobrecarga de operadores combinada con alguna de las otras restricciones de C-H-. resulta una caracterstica excesivamente 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 lenguaje C#. que si que dispone de un sencillo mecanismo de sobrecarga de operadores), se consideraba que esta caracterstica segua siendo demasiado compleja, por lo que los programadores Java no pueden implementar sus propios operadores sobrecargados, a diferencia de los programadores de C+4- 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 toda secuencia de caracteres encerrada entre comillas dobles). //: operators/StrmgOperators. java mport static net.mindview.til.Print; public class StringOperators ( public static void mam (String [1 argsl { int x = 0, y = 1, 2 = 2; String s * Mx, y, z M; print (s + x y +*); printx s) i // Convierte x a String s M (sumtned) " ; // Operador de concatenacin printfs + (x + y + z)); printi"" x); // Abreviatura de Integer.toStrinoO

i ) f* Outpuc: x, y, s 012
0

x, y, z

x, y, 2 (summed) * 3 0

*///:-

Observe que la salida de la primera instruccin de impresin es *ol2en lugar de slo 3, que es lo que obtendra si se estuvieran sumando los valores enteros. Esto es porque el compilador Java convierte x. y y / a su representacin String y concatena esas cadenas de caracteres, en lugar de efectuar primero la suma. La segunda instruccin de impresin conviene 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 evaluacin de la expresin, de modo que los valores enteros se sumen realmente antes de la visualizacin.

Observe el ltimo ejemplo de main( ): en ocasiones, se encontrar en los programas un valor String vaco seguido de + y una primitiva, como forma de realizar la conversin sin necesidad de invocar el mtodo explcito ms engorroso. lnteger.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 cuando no se est del todo seguro acerca de la forma que se evaluar una expresin. Esto, que vale para muchos lenguajes tambin es cierto para Java.

Un error extremadamente comn en C y C*H sera el siguiente:

whilelx * y) ( // . . . .

El programador estaba intentando, claramente, comprobar la equivalencia (=) en lugar de hacer una asignacin. En C y C++ 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 boolcan. pero el compilador espera un valor hoolean y no realizar ninguna conversin a partir de un valor int, por lo que dar un error en tiempo de compilacin y detectar el problema 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 ley son de tipo hoolean. en cuyo caso x = y es una expresin legal, aunque en el ejemplo anterior probablemente su uso se deba a un error).

Un problema similar en C y C-H- consiste en utilizar los operadores bit a bii AND y OR en lugar de las versiones lgicas. Los operadores bit a bit AND y OR utilizan uno de los caracteres (& o |) mientras que los operadores lgicos AND y OR utilizan dos (&& y ||). Al igual que con = y =, resulta fcil confundirse V escribir slo uno de los caracteres en lugar de dos. En Java, el compilador vuelve a evitar este tipo de error, porque no permite emplear un determinado tipo de datos en un lugar donde no sea correcto hacerlo Operadores de proyeccin

La palabra proyeccin (cast) hace referencia a la conversin explcita de datos de un tipo a otro Java cambiar automticamente un tipo de datos a otro cada vez que sea apropiado. Por ejemplo, si se asigna un valor entero a una variable de coma flotante, el compilador convertir automticamente el valor int a float. El mecanismo de conversin nos permite realizar esta conversin de manera explcita, o incluso forzarla en situaciones donde normalmente no tendra lugar.

Para realizar una proyeccin, coloque el tipo de datos deseado entre parntesis a la izquierda del valor que haya que convertir. como en el siguiente ejemplo: //: operatora/Casting.java public class Casting [ public static void main(String[] args) ( int i * 200; long Ing =* (long)u lng = i; // "Ensanchamiento," por lo que no se requiere conversin long lng2 * (long)200; lng2 * 200; // Una "conversin de estrechamiento ": i = (int)lng2; // Proyeccin recruerida

} ///:-

Como podemos ver, resulla 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 automticamente un \alor int a long cuando sea necesario. Sin embargo, podemos utilizar 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-*-+f 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 Je estrecha- miento (es decir, cuando se pasa de un tipo de datos que puede albergar ms informacin a otro que no permite albergar tanta), se corre el riesgo de perder informacin. 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 explcita*'. Con una conversin Je ensanchamiento, no hace falta una proyeccin explcita, porque el nuevo tipo permitir albergar con creces la informacin del tipo anterior, de modo que nunca se puede perder informacin.

Java permite proyectar cualquier tipo primitivo a cualquier otro, excepto en el caso de honlean. que no permite efectuar ningn lipo de proyeccin. Los tipos de clase tampoco permiten efectuar

proyecciones: para convenir uno de estos tipos en otro, deben existir mtodos especiales (posteriormente, veremos que tos objetos pueden proyectarse dentro de una familia de tipos; un Olmo puede proyectarse sobre un Arbol v viceversa, pero no sobre un tipo extemo como pueda ser Roca). Truncamiento y redondeo

Cuando se realizan conversiones de estrechamiento, es necesario prestar atencin a los problemas de truncamiento y redondeo. 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 inl. el valor resultante ser 30 o 29? I \ respuesta a esta pregunta puede verse en el siguiente ejemplo: //*. operators/CastinaNumbers.java // Qu ocurre cuando se proyecta un valor float // o double sobre un valor entero? import static net.mindview.til.Print.; public class CastmgNumbers ( public static void main(String[1 args) ( double above 0.7, below = 0.4; float fabove * 0.7f, fbelow * 0.4f; print("(int)above: " + (int)above); print("(intJbelow: " * {int)below); print(H(int)fabove: " * (int)fabove); print("(int)fbelow: M (int)fbelow);

i ) /* Output: (int)abov e: 0 (int)belo w: 0

(int)fabo ve: 0 (int)fbel ow: 0 ///:-

Asi que la respuesta es que al efectuar la proyeccin de float o double a un valor entero, siempre se trunca el correspondiente nmero. Si quisiramos que el resultado se redondeara habra que utilizar los mtodos round() de java.lang.Math //: operators/RoundingNumbers. java // Redondeo de valores float y double. import static net .mindview.til .Print. ; public class RoundingNumbers ( public static void main(String[J aras) { double above * 0.7, below = 0.4; float fabove = 0.7fr fbelow = o.4f; print i "Math. round i above l : " Math.round(above!}; printi"Math.round(below): " Math.round(below)); printl"Math.round(fabovei: n + Math.round(fabove)); print'''Math. round (fbelow) : " Math.round(fbelow));

} | / Output: Math.round(abov e): 1 Math.round(belo wl: 0 Math.round(fabo ve): 1 Math.roundlfbel owJ : 0

///:-

Puesto que round( ) es pane de java.lang. no hace falta ninguna instruccin 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 dalos primitivos ms pequeos que int (es decir, cliar. Inte o short). dichos valores sern promocionados a int 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 (V, como estamos realizando una asignacin a un tipo de menor tamao, perderemos informacin). En general, el tipo de datos de mayor tamao dentro de una expresin es el que determina el tamao del resultado 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 sfzeof( > nos dice el nmero de bvtes asignado a un elemento de datos. La razn ms importante para el uso de sizeoff ) en C y C-H- es la portabilidad. Los diferentes tipos de datos pueden tener diferentes tamaos en distintas mquinas, por lo que el programador debe averiguar el tamao de esos tipos a la hora de realizar operaciones que sean sensibles al tamao. Por ejemplo, una computadora puede almacenar los enteros en 32 bits, mientras que otras podran 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+-K

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 forma parte del propio diseo del lenguaje. Compedio de operadores

El siguiente ejemplo muestra qu tipos de datos primitivos pueden utilizarse con determinados operadores concretos. Bsicamente, se irata del mismo ejemplo repetido una y otra vez pero empleando diferentes tipos de datos primitivos. El archivo se compilar sin enores porque las lineas que los incluyen estn desactivas mediante comentarios de tipo //!. //: operators/AHOps. java // Comprueba todos los operadores con todos los tipos de datos primitivo

s// para mostrar cules son aceptables por el compilador Java. 3 Operadores public class AilOps ( // Para aceptar los resultados de un test booleano: void f(boolean b) () void boolTest(boolean x. boolean y) ( / / Operadores aritmticos:

fix == y); f(x i y); f(ly); x = x ti y; X * x || y; // Asignacin compuesta: //! X y; //! x -= y; //I x *= y; //l x /= y; //! x %= y; / / [ X = 1; //l x 1; //l x >= 1; x &= y; X y; x I y; // Proyeccin: / / [ char c = (char)x;

/ / [ byte b = (bytejx; //1 short s * (short)x; //i int i s (int)x; f f I long 1 * (long)x; //1 float f = (float)x; //i double d = (double)x;

x Piensa en Java

void charTest(char x, char y) // Operadores aritmticos: x = (char) (x * y) ; x (char) (x / y) ;

X++;

X-- ; x - (char)y; x = (char)-y; // Relacinales lgicos: f (x > y) ,* f ix >= y) ; fix < y); f (x <= y) / f(x == y) ; fix !- y) ,* //! f(iX> ? //! f (x fit& y) ; //! f (x H y); // Operadores bit a bit: x* (char)-y; x = (char) (x & y) ; x = (char)(x | y); x = (char)(x A y) ; x = 1) ; X * (char)(x << (char)(x >> y

x Piensa en Java

1) ; x = 1); // Asignaci n compuest a: x += y; x -= y; x *= y; x /= y; x %* y; (char)(x >>>

X = 1; x >>o 1; x >= l; x &= y f x y; x |= y; // Proyeccin: //I boolean (boolean)x byte b = (byte)x; shore a = (short)x; int i * lint)xr* long 1 = 'long)x; float f * (float)x; double d = (double)x; bl *

> void byteTest(byte x, byte y) // Operadores aritmticos: x = (byte)

x Piensa en Java

<x* y) ; x y) x++; x--;

=(byte)(x / ;

x = (byte)+ y; x (byte)- yr

3 Operadores 186

// Relacinales y lgicos: f (x > y) j f (x >= y); f (x < y); f tx <= y); (x *= y) ,* fix fa y) ; //! f f 1 x) ; f / \ l (x y} r //i f(x || y),- // Operadores bit a bit: x = (byte)-y x = (byte) (x & y) ; x * (byte)(x | y) ; x (byte) (x A y) ; x (byte) (x 1); x = (byte) (x 1); x * (byte) (x > 1) ; // Asign acin compu esta: x -f y; x y; x *= y; x / y; x %= y; x = 1;

X >>=1;

3 Operadores 187

X >>> 1; x fca y; X A= y; x |= y; // Proyeccin: //! boolean (boolean)x; bl =

char c *= (char)x; short s = (short)x; int i = (int)x/ long 1 = (long)x; float f = (float)x; double d * (double)x; ) xi x * (short)+y; x = lshort)-y; // Relacinales y lgicos: f(x > y); <x >= y); f (x < y) ; f (x <= y); f(x == y) ; f (x U y); f f I fdx); //I f (x Scc y) ; //i fix II y);

x Piensa en Java

// Operadores bit a bit:

x * (short)(x >> 1); x = (short)(x >>> L) ; t t Asignacin compuesta: x y; x -* yj x *= y; x /- y ; x I y; x <<= 1;

X * 1/

X >>>= 1; x y; x y;

x h y;

x Piensa en Java

// Proyeccin:

//I boolean bl = (boolean):

char c - (char)x;

byte b * (byte)X/

int i * (int)x;

x Piensa en Java

long 1 = (long)x;

float f = (float)x;

double d = (double)x;

void intTest(int x, int y) ( // Operadores aritmticos: x * x y; x = x / y; x x % y; x = x + y; x * x - y;

x Piensa en Java

X+fJ X--J x * y; x = -y;

// Relacinales y lgicos: f (x > y) ,* t (x >- y); f (x < y) j f (x c y); t tx y); f (x 1= y);

U\ f ( ! x ) ;

/ / 1 f (x U U y) ;

//t f(x || y);

x Piensa en Java

// Operadores bit a bit: x * -y; x * x & y; x X I y; x = x * y; x * X 1;

X = X >> 1 ;

X = X >>> 1;

3 Operadores 193 // Asignacin compuesta: x y ; x -= y; x ** y;

X /* y; X %= y; x * 1 ,*

X = 1;

X >>>= 1; x &= y; x *= y; X I y: // Proyeccin: //! boolean bl * (boolean)x; char c = (charix; byte b = (byte)x; short s = (short)x; long 1 = (longjx; float f = (float)x; double d = (double)x;

3 Operadores 194 ) void longTest(long x, long y) { // Operadores aritmticos: x x y; X = X / y; x = x % y; x = x y; x = x y; x+; x-- x = *y; x = -y; // Relacina les y lgicos: f (x > y) ; f (x >= y) ; f (x < y) ; f (x <= y); f(x == y); f(x i* y); //! (ix) ; //! f (X & & y) ;

//i f(x || y); // Operadore s bit a bit: x = -y; x = x & y; x = X I y; x a x * y; x * x 1; x = x 1; x x > X // Asignac

3 Operadores 195 in compues ta: x += y; x y; x *= y; x /= y; x %= y; X c<= 1;

X * ; X >s 1; x y; x A= y; x |* y; // Proyeccin: //] boolean bl = (boolean)x; char c (char)x/ byte b * (byte)x; short s (short)x; nt i (int)x; float f = (float)x; double d - (double)x;

X++; x--; x +y i x <= -y; // Relacina

3 Operadores 196 les y lgicos: f Ix > y); f Ix >- y) i t (x < y) ; f lx <= y); Cx ~ y)t f (x !* y) ,* l/\ f (1X) ;

//l fIX && y); //I t(X || y); // Operadores bit a bit: //I X * X > lj // Asign acin compu esta: x y; x y; x *y; x /= y; x %= y; //i X * lj // t x >> 1; //I X > I; // X &= y; //! X y; //I x I* y; // Proyeccin: //! boolean bl = (boolean)x; char c * (char)x; byte b (byte'x; short s (short)x; int \ = lintx; long 1 = (long)x; double d (double)x/ void doubleTest(double x, double y) { // Operadores aritmticos: X < =

3 Operadores 197 X * y ; x e x i y ; x * x % y ; x * x y ; x x y ; X + *

3 Operadores 198 X x * + y ; x * y ; // Relacinales lgicos: f (x > y) f Ix >= y) \ f (x < y) j f (x < y); fx == y)/ fix ! = y); //i f(!x); //1 f (x && y) ; // f(x || y); // Operadores bit bit: // x = x > 1; / / A s i g n a c i n c o m p u e s t a

3 Operadores 199 : x y ; x = y ; x y ; x / = y ; x % = y ; //} //! //I //! x X x X <<= 1; 1; >>>* 1; y?

//! Xa y; //I X | y; // Proyeccin: //! boolean bl (boolean!x; char c ** (charjx;

3 Operadores 200 byte b = (byte)x; short s * i short)x; int i * (intlx; long 1 (long)x; float f * I float)x;

} ///:-

Observe que boolean es bastantelimitado A una variable de este tipo se le pueden asignar los valores true y false, y se

3 Operadores 201 puede comprobar si el valor es verdadero o falso. |>ero no se pueden sumar valores booleanos ni realizar ningn otro tipo de

operacin con ellos.

En char, byte v short, puede ver el efecto de la promocin con los operadores aritmticos. Toda operacin aritmtica sobre cualquiera de estos tipos genera un resultado int. que despus debe ser proyectado explcitamente al tipo original (una conversin de estrechamiento que puede perder informacin) para realizar la asignacin a dicho tipo. Sin embargo, con los valores in( no es necesaria 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 int que sean lo suficientemente grandes, se producir un desbordamiento en el resultado. como se ilustra en el siguiente ejemplo: //: operators/Overflowoava // i Sorpresa: Java permite los desbordamientos. public class Overflow | pu b l

3 Operadores 202 i c s t a t i c v o i d m a i n { S t r i n g [ ] a r g s ) { i n t b i g I n t e g e r . M A X

3 Operadores 203 _ V A L U E ; S y s t e m . o u t . p r i n t l n ( M b i g * M * b i g ) ; i n t b i g g e r *

3 Operadores 204 b i g 4 System.out.println("b igger * " biggerl ;

) ) / O u t p u t : b i g = 2 1 4 7 4 8 3 6 4 7 b i

3 Operadores 205 g g e r 4 / / / : -

No se obtiene ningn tipo de error o advertencia por pane del compilador, y tampoco se genera ninguna excepcin en tiempo de ejecucin. El lenguaje Java es muy bueno, aunque no hasta ese punto.

Las asignaciones compuestas no requieren proyecciones para chur, byte O short. an cuando estn realizando promociones que provocan los mismos 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.

3 Operadores 206 Como puede ver. con la excepcin de boolean, podemos proyectar cualquier tipo primitivo sobre cualquier otro tipo primitivo. 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 tamao; 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( ) Fn main( ). invoque el mtodo que haya escrito, utilizando varios objetos String diferentes.

3 Operadores 207 Resumen

Si tiene experiencia con algn lenguaje que emplee una sintaxis similar a la de C, podr ver que los operadores de Java son tan similares que la curva aprendizaje es prcticamente nula. Si este capitulo le ha resultado difcil, asegrese de echar un vistazo a la presentacin multimedia Thinking n C. disponible en wnr MindVtew.net. Puede encontrar las soluciones a los ejercicios seleccionados en el documento elecixonico 77>r Thinkiitg tn Java Annonital So/unon Uutde. que esta disponible para la venta en MiadYien'.ne

tControl de ejecucin

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 C. por lo que si ha programado antes con C o 0+, la mayor parte de la informacin que vamos a ver en este capitulo le resultar familiar. La mayora de los lenguajes de programacin procedimental disponen de alguna clase de instrucciones de control, y suelen existir solapamientos entre los distintos lenguajes. En Java, las palabras clave incluyen If-else. while, do-whUe, for. return. break y una instruccin de seleccin denominada switch Sin embargo. Java no soporta la despreciada instruccin goto (que a pesar de ello en ocasiones representa la forma ms directa de resolver cienos tipos de problemas). Se puede continuar realizando un salto de estilo goto, pero est mucho ms restringido que un j*oto tpico. true y false

Todas las instrucciones condicionales utilizan la veracidad o falsedad de una expresin condicional para determinar la ruta do ejecucin. Un ejemplo de expresin condicional sera a = b. Aqu, se utiliza el operador condicional = para ver si el valor de a es equivalente al valor de b La expresin devuelve true o false Podemos utilizar cualquiera de los operadores relacinales que hemos empleado en el capitulo anterior para escribir una instruccin condicional. Observe que Java no permite utilizar un nmero como boolean. a diferencia de lo que sucede en C y C++ (donde la veracidad se asocia con valores distintos cero > la falsedad con cero). Si quiere emplear un valor no boolean dentro de una prueba boolean. como por ejemplo if(a), deber primero convertir el valor al tipo boolean usando una expresin condicional, como por ejemplo if(a != 0)

if-else

La instruccin If-else representa la forma ms bsica de controlar el flujo de un programa. La clusula else es opcional, por lo que se puede ver if de dos formas distintas: if(expresinbooleanainstruccin

o if(expresinbooleana) instruccin else instruccin

La i'.xpresin-boo/eana debe producir un resultado boolean. La instruccin puede ser una instruccin simple terminada en punto y coma o una instruccin compuesta, que es un gnipo de instrucciones simples encerrado entre llaves. All donde empleemos la palabra instruccin quenemos decir siempre que esa instruccin puede ser simple o compuesta.

Como ejemplo de If-else. he aqu un mtodo test( ) que indica s un cierto valor est por encima, por debajo o es equivalente a un nmero objetivo:

/ / : control/IfElse - java import static net.mindview.util.Print. ; public class IfElse { static int result = 0; static void test(int testval. int target) { if(testval > target) result !; else if(testval < target) result -1; else result * 0; // Coincidencia

) public static void main(Stri ng[] args) { test(10 , 5); print (result) ; test(5, 10); print(res ult); test 5. 5) ; print (result) ;

} ) /* Output:

1 -1 0

///:-

En la pane central dc test( ), tambin puede ver una instruccin "else if," que no es una nueva palabra clave sino una instruccin else seguida de una nueva instruccin If.

Aunque Java, como sus antecesores C y C++, es un lenguaje dc formato libre* resulta habitual sangrar el cuerpo de las ins- tnicciones de control de flujo, para que el lector pueda determinar fcilmente dnde comienzan y dnde terminan. Iteracin

Los bucles de ejecucin se controlan mediante while, do-while v for, que a veces se clasifican como instrucciones tic iteracin, Una determinada instruccin se repite hasta que la expresin-hooleana de control se evale como false. La forma de un bucle while es:

whil#iexpresin-booleana instruccin

La expresin-booleana se evala una vez al principio del bucle y se vuelve a evaluar antes de cada sucesiva iteracin de la instruccin.

He aqui un ejemplo simple que genera nmeros aleatorios hasta que se cumple una determinada condicin. //: control/WhileTest.java // Ilustra el bucle while. public class WhileTest ( static boolean conditionO ( boolean result Math.random() < 0.99; System, out .print (result * ", return result;

) public static void main(String[] args) { while(condition()) System.out.println("Inside 'while*); System.out.orintIn I"Exited 'while'");

) ) /* (Ejectelo para ver la salida) ///:-

El mtodo condition( ) utiliza el mtodo rundnm( ) de tipo static de la biblioteca Math. que genera un valor douhle comprendido entre 0 y l (incluye 0, pero no 1.) El valor result proviene del operador de comparacin <, que genera un resultado de tipo boolean. Si se imprime un valor boolean, automticamente se obtiene la cadena de caracteres apropiada iruc**

o false. La expresin condicional para el bucle while dice; repite las instrucciones del cuerpo mientras que coadition( ) devuelva true* do-while

La forma de dowhile os do instruccin while {expresinboclear.al ;

La nica diferencia entre while y do-while es que la instruccin del bucle do-whlc siempre se ejecuta al menos una vez. me luso 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 while. for

El bucle for es quiz la forma de iteracin ms habitualmente utilizada. Este bucle realiza una inicializacin antes de la primera iteracin. Despus realiza una prueba condicional y. al final de cada iteracin, lleva a cabo alguna forma de avance de paso*. La forma del bucle for es: for(inicializacin; expresinbooleana; paso) instruccin

Cualquiera de las expresiones inicializacin. expresin-booleana o paso puede estar vaca. La expresin booleana se comprueba antes de cada iteracin y. en cuanto se evale como false. la ejecucin contina en la lnea que sigue a la instruccin for Al final de cada bucle, se ejecuta el paso.

Los bucles for se suelen utilizar para tareas de 'recuento": //: control/ListCharacters.java // Ilustra los bucles for'* enumerando ( f todas las letras minsculas ASCII. public class ListCharacters ( public etatic void maintCtringU arasi | for(char c = 0/ c c 128 ) ir (Character.isLowerCase{ c)} System.out .println("valu: " + (int)c " character: * * c);

Observe que la variable c se define en el mismo lugar en el que se la utiliza, dentro de la expresin de control correspon diente al buele for. en lugar de definirla al principio de maini ). El mbito de c es la instruccin controlada por for.

Este programa tambin emplea la elasc envoltorio" java.lang.Character. que no slo envuelve el tipo primitivo char dentro de un objeto, sino que tambin proporciona otras utilidades Aqui el mtodo stutic isLo\verCase( ) se usa para detectar si el carcter en cuestin es una letra minscula.

Los lenguajes proceditnentales tradicionales como C requieren que se definan todas las variables al comienzo de un bloque, de modo que cuando el compilador crec un bloque, pueda asignar espacio para esas variables En Java y C++-, se pueden distribuir 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 liace que el cdigo sea ms fcil de entender.

Ejercicio 1:

(l) Escriba un programa que imprima los valores comprendidos entre I y 100.

Ejercicio 2: (2) Escriba un programa que genere 25 valores int aleatorios. Para cada valor, utilice una instruccin

if-clse para clasificarlo como mayor que. menor que o igual a un segundo valor generado aleatoriamente.

Ejercicio 3: (l) Modifique el Ejercicio 2 para que el cdigo quede rodeado por un bucle hile infinito De este mo

do, el programa se ejecutar hasta que lo interrumpa desde el teclado (normalmente, pulsando Control-C).

Ejercicio 4: (3) Escriba un programa que utilice dos bucles for anidados y el operador de mdulo !%) para detectar e

imprimir nmeros primos (nmeros enteros que no son divisibles por ningn nmero excepto por s mismos y por 1).

Ejercicio 5: (A) 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 IntegertoBinarv-Strin^ ). El operador coma

Anteriormente en el capitulo, hemos dicho que el operador coma (no el separador coma que se emplea para separar definiciones y argumentos de mtodos) slo tiene un uso en Java: en la expresin de control de un bucle for. Tanto en la parte correspondiente a la inicializacin como en la parte correspondiente al paso de la expresin de control, podemos incluir una serie de instrucciones separadas por comas, y dichas instrucciones se evaluarn secuencialmente.

Con el operador coma, podemos definir mltiples variables dentro de una instruccin for. pero todas ellas deben ser del mismo tipo: //: control/CommaOperator.java public clasa CommaOperator ( public static vod mainString[J args) { for(int i 1, j * i * 10; i < 5; i+*, j i * 2) {

///:-

La definicin int de la instruccin for cubre tanto a i como a j La pane de inicializacin puede tener cualquier nmero de definiciones de un mismo Upo. La capacidad de definir variables en una expresin de control est limitada a los bucles for. No puede emplearse esta tcnica en ninguna otra de las restantes instrucciones de seleccin o iteracin.

Puede ver que. tanto en la parte de inicializacin como en la de paso, las instrucciones se evalan en orden secucncial. Sintaxis foreach

Java SE5 introduce una sintaxis for nueva, ms sucinta, para utilizarla con matrices y contenedores (hablaremos ms en detalle sobre este tipo de objetos en los Captulos 16. Matrices, y 17, Anlisis detallado de tos contenedores). Esta sintaxis se denomina sintaxis foreach (para todos), y quiere decir que no es necesario crear una variable int para efectuar un recuento a travs de una secuencia de elementos: el bucle for se encarga de generar cada elemento automticamente.

por ejemplo, suponga que tiene una matriz de valores float y que quiere seleccionar cada uno de los elementos de la matriz:

//: control/ForEachFloat.java java.util.#;

import

public elass ForEachFloat {

public static void main(StringfJ args) (

Random rand = new Random(47); float fIJ = new float[10]; forint i a 0; i < 10; i++) f[i] = rar.d .nextFloat () j for(float x : i)

System.out.println(x);

} / Output:

0.72711575

0.39982635

0.5309454

G.0534122

0.16Q2Q656

0.57799757

0.18847865

0.4170137

0.51660204

0.73734957

La matriz se rellena utilizando el antiguo bucle for, porque debe accederse a ella mediante un ndice. Puede ver la sintaxis foreach en la linea:

for(float x : f) (

Esto define una variable x de tipo float y asigna secucncialmente cada elemento de f a x.

Cualquier mtodo que devuelve una matriz es buen candidato para emplearlo con la sintaxis foreach. Por ejemplo, la clase String tiene un mtodo toCharArrayt ) que devuelve una matriz de char. por lo que podemos iterar fcilmente a iraves de los caracteres de una matriz:

//: control/ForEachString.java

public class Korfcachstring {

public static void mainString[] args) {

forchar c : "An African Swallow".toCharArray() J System.out.print(c H ");

} J /* Output:

An A f r i c a n ///:-

S w a l l o w

Como podremos ver en el Captulo 11. Almacenamiento Je objetos, la sintaxis fotvach tambin funciona con cualquier objeto que sea de tipo Iterable.

Muchas instrucciones for requieren ir paso a paso a travs de una secuencia de valores enteros como sta:

fortint i = 0; i < 100; i** 1

Para este tipo de bloques, la sintaxis foreach no funcionar a menos que queramos crear primero una matriz de valores nt. Para simplificar esta tarea, he creado un mtodo denominado range() en nct.mindview.util.Range que genera automticamente la matriz apropiada. La intencin es que range ) se utilice como importacin de tipo static:

//: control/ForEachlnt.java

import static net.mindview.util.Range.;

public class ForEachlnt (

import static net .mindview.util.Print. *,*

El mtodo rangc( ) est sobrecargado, Id 6 7 8 que quiere decir que puede utilizarse el ///:mismo mtodo con diferentes listas de argumentos (en breve hablaremos del mecanismo de sobrecarga). La primera forma sobrecargada de range( ) empieza en cero y genera valores hasta el extremo superior del rango, sin incluir ste. La segunda forma comienza en el primer valor y va hasta un valor menos que el segundo, y la lercera forma incluye un valor de paso, de modo que los incrementos se realizan 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.

i ) /* Output: 0123456*7139 5 9 S B 11 14 17

Observe que aunque range() permite el uso de la sintaxis foreach en ms lugares, mejorando asi la legibilidad del cdigo, es algo menos eficiente, por lo que se est utilizando el programa con el Un 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 prmtnh() adems de print( ). El mtodo printnb( ) no genera un carcter de nueva linea, por lo que permite escribir una linea en sucesivos fragmentos.

La sintaxis foreach no slo ahorra tiempo a la hora de escribir el cdigo. Lo ms importante es que facilita la lectura y comunica 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 matriz") Utilizaremos la sintaxis foreach siempre que sea posible a lo largo del libro. return

Diversas palabras clave representan lo que se llama un salto incondicional, lo que simplemente quiere decir que el salto en el flujo de ejecucin se produce sin realizar previamente comprobacin alguna. Dichas palabras clave incluyen return. break. continu y una forma de saltar a una instruccin etiquetada de forma similar a la instruccin goto deotTos lenguajes.

La palabra clave return tiene dos objetivos: especifica qu valor devolver un mtodo (si no tiene un valor de retomo de tipo vold) y hace que la ejecucin salga del mtodo actual devolv iendo ese valor.

Podemos reescribir el mtodo test() precedente para aprovechar esta caracterstica: //: control/IfElse2.java import static net .mindview. til. Prir.t. public class IBlse2 ( static int test(int testval, int target) ( iftestval > target) return *-1; else iftestval < target) return -1; else return 0; // Coincidencia

4 Control de ejecucin 227 )public static void main(StringlJ args) ( print(test(10, 5}); print(test(5, 10)); print(test(5, 5)) ;

} ) /* Output: 1 -1 0

*///:-

No hay necesidad de la clusula else, porque el mtodo no continuar despus de ejecutar una instruccin retum.

Si no incluye una instruccin return en un mtodo que devuelve un valor void. habr una instruccin return implcita al final de ese mtodo, asi que no siempre es necesario incluir dicha instruccin. Sin embargo, si el mtodo indica que va a devolver cualquier otro valor distinto de void. hay que garantizar que todas las rutas de ejecucin del cdigo devuelvan un valor.

4 Control de ejecucin 228 Ejercicio 6: (2) Modifique los dos mtodos test( ) de los dos programas anteriores para que admitan dos argumentos

adicionales, begin y cnd. y para que se compruebe lesiva! para ver si se encuentra dentro del rango comprendido entre begin y end (ambos incluidos). break y continu

Tambin se puede controlar el flujo del bucle dentro del cuerpo de cualquier instruccin de iteracin utilizando break y continu. break provoca la salida del bucle sin ejecutar el resto de la instrucciones. La instruccin continu detiene la ejecucin de la iteracin actual y vuelve al principio del bucle para comenzar con la siguiente iteracin

Este programa muestra ejemplos de break y continu dentro de bucles for y while //; control/BreakAndContinue.java // Ilustra las palabras clave break y continu. import static net.mindview.util.Range. public class BreakAndContinue { public static void mam(String [] args) ( for lint i * 0; i < 100; i+-*-J ( if(i if(i ** 74) V 9 break; // Fuera 0) continu; // del bucle Siguiente iteracin

System.out.print(i + " ");

4 Control de ejecucin 229 i System.out.pri ntln( ) l f Oso de foreach: forint i : range(100)J { if(i i(i ** 74) % 9 !* break; // Fuera 0) continu; // del bucle Siguiente iteracin

System.out .orint (i '*

) System.out.println{); int i 0; // Un bucle infinito": while(true) { i-f*; int j * i 27; if(j == 1269) break; // Fuera del bucle for if(i % 10 !* 0) continu; // Principio del bucle System.out .print (i -+ ' H);

) ] /* Output: O 9 10 27 36 45 54 63 72

4 Control de ejecucin 230 O 9 18 27 36 45 54 63 72 10 20 30 40 *///:-

En el bucle for. el valor de I nunca llega a 100, porque la instruccin break hace que el bucle termine cuando vale 74. Normalmente, utilizaremos una instruccin break como sta slo si no sabemos cundo va a cumplirse la condicin de terminacin. La instruccin continu 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 sintaxis foreach y como esta 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 instruccin break que har que salgamos del bucle. Adems, podemos ver que la instruccin continu devuelve el control al principio del bucle sin ejecutar nada de lo que hay despus de dicha instruccin continu (por tanto, la impresin slo se produce en el segundo bucle cuando el valor de i es divisible por 10). En la salida, podemos ver que se imprime el valor 0. porque 0 % 9 da como resultado 0.

Una segunda forma del bucle infinito es for(;;). El compilador trata tanto whilc(true) como for(;;) de la misma forma, por lo que podemos utilizar una de las dos formas segn prefiramos.

4 Control de ejecucin 231 Ejercicio 7: (1) Modifique el Ejercicio I para que el programa termine 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 Informtica. 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 compiladores, podremos ver que el control de programa contiene muchos saltos (el compilador Java produce su propio cdigo ensamblador", pero este cdigo es ejecutado por la mquina virtual Java en lugar de ejecutarse directamente sobre un procesador hardware).

Una instruccin goto es un salto en el nivel de cdigo fuente, y eso es lo que hizo que adquiriera una mala reputacin. Si un programa va a saltar de un punto a otro, no existe alguna forma 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 articulo "Gota consi dered harmfur de Edsger Dijkstra. y desde entonces la caza del goto se ha convertido en un deporte muy popular, forzando a los defensores de esa instruccin a ocultarse cuidadosamente.

4 Control de ejecucin 232 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 determinadas situaciones especiales goto representa, de hecho, la mejor forma de estructurar 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, si que dispone de algo que se asemeja a un salto, y que est integrado dentro de las palabras clave break y continu. No es un salto, sino ms bien una forma salir de una instruccin de iteracin. La razn por la que a menudo se asocia este mecanismo con las discusiones relativas a la instruccin goto es porque utiliza la misma tcnica: una etiqueta.

Una etiqueta es un identificador seguido de un carcter de dos puntos, como se muestra aqu: labeli:

Ll nico lugar en el que una etiqueta resulta til en Java es justo antes de una instruccin de iteracin. V 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 instruccin switch (de la que hablaremos enseguida) dentro de ella. Esto se debe a que las palabras clave break y continu normalmente slo interrumpirn el bucle actual, pero cuando se las usa como una etiqueta interrumpen todos los bucles hasta el lugar donde la etiqueta se haya definido: labell: iteracin-externa { iteracin-interna (break; // l) continue. // (2)

4 Control de ejecucin 233 //... continue label1; // 13)

//... break labell; // 14)

En ( I ) , la instruccin break hace que se salga de la iteracin iniema y que acabemos en la iteracin externa. En (2). la instruccin continue hace que volvamos al principio de la iteracin interna. Pero en (3). la instruccin continue label I hace que se salga de la iteracin interna r de la iteracin externa, hasta situarse en labell Entonces, contma de hcchocon la iteracin. pero comenzando en la iteracin extema. En (4). la instruccin break label I 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 finalizado.

4 Control de ejecucin 234 He aqu un ejemplo de utilizacin de bucles for //: control/LabeledFor.java // Bucles for con "break eqtiquetado" y continue etiquetado", import static net.raindview.util.Print.; public class LabeledFor ( public static void main(StringH args) { int i = 0; outer: // Aqu no se pueden incluir instrucciones fori; true ;) ( // bucle infinito inner: // Aqu no se pueden incluir instrucciones for(; i < 10; i++) ( print(Mi = M i); if(i 2) { print{ cont inue"); continue;

} if (i == 3) { print "break"); i++; // En caso contrario, nunca // se incrementa. break;

) if (i = 7) { print("continue outer); i+4-; f t En caso contrario, i nunca // se incrementa, continue

4 Control de ejecucin 235 outer;

i if (i 8) ( print{"break outer"); break outer;

i for(int k 0; k < 5/ k++) { if (k *= 3) ( print("continue inner"); continue inner; }// Aqu no se puede ejecutar break o continue cara saltar a etiquetas I

Observe que break hace que salgamos del bucle for. y que la expresin de incremento no se ejecuta

4 Control de ejecucin 236 hasta el final de la pasada a travs del bucle for. Puesto que break se salta la expresin incremento, el incremento se realiza directamente en el caso de i = 3. La instruccin continu outer en el caso de i = 7 tambin lleva al principio del bucle y tambin se salta el incremento, por lo que en este caso tenemos tambin que realizar el incremento directamente.

S no fuera por la instruccin break outer no habra forma de salir del bucle externo desde dentro de un bucle interno, ya que break por si misma slo permite salir del bucle ms interno (lo mismo cabria decir de continu).

Por supuesto, en aquellos casos en que salir de un bucle implique salir tambin del mtodo, basta con ejecutar return

Me aqu una demostracin de las instrucciones break y continu etiquetadas con bucles while: //: control/LabeledWhile.java // Bucles while con "break etiquetado" y "continu etiquetado", import static nec.mindview.util.Print.; public clacc LabelcdWhile { public static void man(Stringll args) { int i * Oj outer: whiletrue) ( print("Outer while loop"),while(txue> { i+*; print i "i = " i}; ifU ** 1) { print("conti nu"); cont inue

4 Control de ejecucin 237 ) if(i 3) l print("continu outer"); continu outer;

} if ti 5) { print("break ")? break;

} if(i 7)

i 4 Control de ejecucin 238 {print <"break outer"); break outer;

) } / Output: Outer while loop 1*1 continu i = 2 i - 3 continu outer Outer while loop i * 4 i = 5 break Outer while loop i * 6 i = 7 break outer ///:-

i 4 Control de ejecucin 239 Las mismas reglas siguen siendo ciertas para hile

Una instruccin continu normal hace que saltemos a la parte superior del bucle ms interno y continuemos alli la ejecucin.
1.

Una instruccin continu etiquetada hace que saltemos hasta la etiqueta y que volvamos a ejecutar el bucle situado justo despus de esa etiqueta.
2.

3.

Una instruccin break hace que finalice el bucle.

4.

Una instruccin break etiquetada hace que finalicen todos los bucles hasta el que tiene etiqueta, incluyendo

la

este ltimo.

i 4 Control de ejecucin 240 Ls importante recordar que la nica razn para utilizar etiquetas en Java es si tenemos bucles anidados y queremos ejecutar una instruccin break o continu a travs de ms de un nivel.

En el artculo "Goto considcredharmfur de Dijkstra. la objecin especifica que l haca era contra la utilizacin de etiquetas 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 mas difciles 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 transferir el control de forma arbitraria. Tambin merece la pena observar que este es uno de esos casos en los que se hace ms til una determinada caracterstica del lenguaje reduciendo la potencia de la correspondiente instruccin. switch

La palabra clave switch a veces se denomina instruccin de seleccin. 1.a instruccin switch permite seleccionar entre distintos fragmentos de cdigo basndose en el valor de una expresin entera. Su forma general es: switch(selector-entero) { case valor-enterol : instruccin; break; case valor-entero2 : instruccin; break; case valor-entero3 : instruccin; break; case valor-entero* s instruccin; break; case valor-enteroS : instruccin; break; default: instruccin;

i 4 Control de ejecucin 241

Selector-entera es una expresin que genera un valor entero. La instruccin swilch compara el resultado de selector-entero con cada valor-entero Si encuentra una coincidencia, ejecuta la correspondiente instruccin (una sola instruccin o mltiples instrucciones: no hace falta usar llaves). Si no hay ninguna coincidencia, se ejecuta la instruccin de default

Observar en la definicin anterior que cada case finaliza con una instruccin break. lo que hace que la ejecucin salte al final del cuerpo de la instruccin switeh. sta es la forma convencional de construir una instruccin switeh, pero la instruccin break es opcional. Si 110 se incluye, se ejecutar el cdigo de las instrucciones case situadas a continuacin hasta que se encuentre una instruccin break. Aunque normalmente este comportamiento no es el deseado, puede resultar ltil en ocasiones para los programadores expertos. Observe que la ltima instruccin, situada despus de la clusula default, no nene una instruccin break porque la ejecucin contina justo en el lugar donde break baria que continuara. Podemos incluir, sin que ello represente un problema, una instruccin break al final de la clusula default si consideramos que resulta importante por razones de estilo.

La instruccin switeh es una forma limpia de implementar selecciones multiva (es decir, selecciones 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 char. Si se desea emplear, por ejemplo, una cadena de caracteres o un nmero en coma flotante como selector, no funcionar en una instruccin switeh Para los tipos no enteros, es preciso emplear una serie deinstrucciones if. Al final delsiguiente capitulo. veremos que la nueva caracterstica enum de Java SE5 ayuda a suavizar esta restriccin, ya que los valores enuni estn

i 4 Control de ejecucin 242 diseados para funcionar adecuadamente con la instruccin switeh.

He aqu un ejemplo en el que se crean letras de manera aleatoria y se determina si son vocales o consonantes: //: control/VowelsAndConsonants.java // Ilustra la instruccin switeh. import j ava.til.; import static net .tr.indview. til .Print. * ; public class VowelsAndConsonants { public static void roain(String[] argsi { Random rand = new Random(4 7) 7 for(int i 0; i < 100; i+4) { int c = rand. next Int (26) ' a'.printnb { (char) c + ", " + c + *:

) switeh(c) {

i 4 Control de ejecucin 243

} } /* Output: y, 121: Sometimes a vowel n, 110: consonant z, 122: consonant b, 98: consonant r, 11*5: consonant n, 110: consonant y, 121: Sometimes a vowel g. 103: consonant c, 59: consonant f, 102: consonant o, 111: vowel

i 4 Control de ejecucin 244

i 4 Control de ejecucin 245 w, 119: Sometimes a vowel 2. 122: consonant

///:-

Puesto que Kandom.ncxtlnf(26) genera un valor comprendido entre y 26. basta con sumar a' para generar las letras minsculas. Los caracteres encerrados entre comillas simples en las instrucciones case tambin generan valores, enteros que se emplean para comparacin.

Observe cmo las instrucciones case pueden apilarse" unas encima de otras para proporcionar mltiples coincidencias para un determinado 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 efectuarla el salto requerido y continuara simplemente procesando el caso siguiente.

Ln la instruccin: nt c rand.nextlnt (26)

i 4 Control de ejecucin 246 Kandom.nexf lnt( ) genera un valor int aleatorio comprendido entre 0 y 25, al que se le suma el valor a*. Esto quiere decir que a* se convierte automticamente a int para realizar la suma.

Para imprimir c como carcter, es necesario proyectarlo sobre el tipo char; en caso contrario, generara una salida de tipo entero.

Ejercicio 8: (2) Cree una instruccin switch que imprima un mensaje para cada case, y coloque el switch dentro de un

bucle for 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 continuacin, elimine las instrucciones break y vea lo que sucede.

Ejercicio 9: (4) Una Secuencia Je Fihonacci es la secuencia de nmeros I. I, 2, 3. 5. 8. 13, 21, 34, etc., donde cada

i 4 Control de ejecucin 247 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 Fihonacci comenzando por el principio de la secuencia; por ejemplo, si ejecuta java Fibonacci 5 (donde Fihonacci es el nombre de la clase) la salida seria: I. I.

2,

3, 5.

Ejercicio 10: (5) Un nmero vampiro tiene un nmero par de dgitos y se forma multiplicando una pareja de nmeros

que contengan la mitad del nmero de dgitos del resultado. Los dignos se loman del nmero original en cualquier orden. No se permiten utilizar parejas de ceros finales Entre los ejemplos tendramos:

1260 = 21 * 60 1827 = 21 * 87 2187 = 27 81

i 4 Control de ejecucin 248 Escriba un programa que determine todos los nmeros vampiro de 4 dgitos (problema sugerido por Dan Forhan). Resumen

Este capitulo concluye el estudio de las caractersticas fundamentales que podemos encontrar en la mayora de los lenguajes de programacin: calculo, precedencia de operadores, proyeccin de lipos y mecanismos de seleccin e iteracin. Ahora estamos listos para dar los siguientes pasos, que nos acercarn al mundo de la programacin orientada a objetos F.l siguiente captulo cubrir las importantes cuestiones de la inicializacin y limpieza de objetos, a lo que seguir, en el siguiente capitulo. el concepto esencial de ocultacin de la implementacin.

Pueden encontrarse las soluciones a los ejercicios seleccionados en e! documento electrnico The Thmktnv m Java AmwnticJ Stilurion Guhh. disponible puro la venta en www.Stindllcw.net.Initialization y limpieza

A medida que se abre paso la revolucin informtica, la programacin no segura se ha convertido 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 C se deben a que el programador 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 terminado de utilizar, ya que en ese momento deja de preocupamos. Al no borrarlo, los recursos utilizados por ese elemento quedan retenidos y resulta fcil que los recursos se agoten (especialmente la memoria)

C"H- introdujo el concepto de constructor. un mtodo especial que se invoca automticamente cada vez que se crea un objeto. Java tambin adopt el concepto de constructor y adems dispone de un depurador de memoria que se encarga de liberar automticamente los recursos de memoria cuando ya no se los est utilizando. En este capitulo, se examinan las cuestiones relativas a la inicializacin y la limpieza, asi como el soporte que Java proporciona para ambas tarcas

Inicializacin garantizada con el constructor

Podemos imaginar fcilmente que seria sencillo crear un mtodo denominado initialize( ) para todas las clases que escribiramos. El nombre es una indicacin de que es necesario 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 garantizar la inicializacin de todos los objetos proporcionando un constructor. Si una clase tiene un constructor. Java invoca automticamente esc constructor cuando se crea un objeto, antes incluso de que los usuarios puedan llegar a utilizarlo. De este modo, la inicializacin 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 colisionar con otro nombre que quisiramos emplear como miembro de la clase Fl segundo problema es que debido a que el compilador es responsable de mvocar el constructor, debe siempre conocer qu mtodo invocar. La solucin en O* parece la ms fcil y lgica, de modo que tambin se usa en Java: el nombre del constructor coincide con el nombre de la clase. De este modo, resulta fcil invocar esc mtodo automticamente durante la inicializacin.

He aqu una clase simple con un constructor. //: initialization/SimpleConstructor.java // Ilustracin de un constructor simple. class Rock { Rock O ( // ste es el constructor Syst em.out.print("Rock M);

} public class SimpleConstructor { public static void main(StringU args) ( for (int i * 0; i < 10; i-M-)

new Rock()?

) ) /* Output: Rock Rock Rock Rock Rock Rock Rock Rock Rock Rock ///:-

Ahora, cuando se crea un objeto: new Rock();

se asigna el correspondiente espacio de almacenamiento y se invoca el constructor. De este modo, se garantiza que el objeto esta apropiadamente inicial izado antes de poder utilizarlo.

Observe que el estilo de codificacin consistente en poner la primera letra 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 constructor que no tome ningn argumento se denomina constructor predeterminado. Normalmente, la documentacin de Java utiliza el trmino constructor sin argumentos, pero el trmino 'constructor predeterminado se ha estado utilizando durante muchos aos antes de que Java apareciera, por lo que prefiero utilizar este ltimo trmino. De todos modos, como cualquier otro mtodo, el constructor puede tambin tener argumentos que nos permiten especificar cmo hay que crear el objeto. Podemos modificar fcilmente el ejemplo anterior para que el constructor tome un argumento: // : initialization/SimpleConstructor2.java // Loa constructores pueden tener argumentos. class Rock2 | Rock2 {int i) ( System.out.print("Rock " + i + * *);

} public class SimpleConstructor2 ( public static void main(String[] args) { fortint i * 0; i < 8; new Rock2(i); \ } / Output; Rock 0 Rock 1 Rock 2 Rock 3 Rock 4 Rock S Rock 6 Rock 7 ///:-

Los argumentos del constructor proporcionan una forma de pasar parmetros para la inicializacin de un objeto. Por ejemplo. si la clase Tree (rbol) tiene un constructor que toma como argumento un nico nmero entero que indica la altura del rbol, podremos crear un objeto Tree como sigue:

Tree t = new Tree (12);

// rbol de 12 metros

Si TTee(int) es el nico constructor del que disponemos, el compilador no nos permitir crear un objeto Tree de ninguna otra forma.

Los constructores eliminan una amplia clase de problemas y hacen que el cdigo sea ms fcil de leer Por ejemplo, en el fragmento de cdigo anterior, no vemos ninguna llamada explcita a ningn mtodo initialize( ) que est conccptualmente separado del acto de creacin del objeto. En Java, la creacin y la inicializacin son conceptos unificados: no es posible tener la una sin la otra.

El constructor es un tipo de mtodo poco usual, porque no tiene valor de retorno. Existe una clara diferencia entre esta circunstancia y los mtodos que devuelven un valor de retomo void. en el sentido de que estos ltimos mtodos no devuelven nada, pero seguimos teniendo la opcin de hacer que devuelvan algo. Los constructores no devuelven nada nunca, y no tenemos la opcin de que se comporten de otro modo (la expresin new devuelve una referencia al objeto recin creado, pero el constructor mismo no tiene un valor de retomo). Si hubiera v alor de retomo y pudiramos seleccionar cul es. el compilador necesitaria saber qu hacer con ese valor de retomo.

Ejercicio 1: (I) Cree una clase que contenga una referencia de tipo String no inicializada. Demuestre que esta referen

cia la inicializa Java con el valor nuil

Ejercicio 2: (2i Cree una clase con un campo String que se inicialice en el punto donde se defina, y otro campo que

sea tmcializado por el constructor, (ul es la diferencia entre las dos tcnicas? Sobrecarga de mtodos

t na 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 parte, es un nombre que sirve para designar una accin. Utilizamos nombres para referirnos a todos los objeto* y mtodos. Una serie de nombres bien elegida crear un sistema que resultar ms fcil de entender \ modificar por otras personas. En cierto modo, este problema se parece a] acto de escribir literatura, el objetivo es comunicarle 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 polisemitas. 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 perro": seria absurdo vemos forzados a decir "camisaLava la camisa. cocheLava el coche" y perroLava el perro" simplemente para que el oyente no se vea forzado a distinguir cul es la accin que tiene que realizar. La mayora de los lenguajes humanos son redundantes, de modo que podemos seguir determinando el significado aun cuando nos perdamos algunas de las palabras. No necesitamos identificadores unvocos podemos deducir el

significado a partir del contexto.

La mayora de los lenguajes de programacin (> C en particular) exigen que dispongamos de un identificador unvoco para cada mtodo (a menudo denominados funciones en dichos lenguajes). As que no se puede tener una funcin denominada prinM ) para imprimir enteros > otra denominada igualmente print( ) para imprimir nmeros en coma flotante, cada una de las funciones necesitar un nombte distintivo

Ln Java (y en C+^). hay otro factor que obliga a sobrecargar los nombres de los mtodos: el constructor Puesto que el nombre del constructor est predeterminado por el nombre de la clase, slo puede haber un nombre de constructor. Pero entonces. qu sucede si queremos crear un objeto utilizando varias formas distintas* Por ejemplo, suponga que construimos una clase cuyos objetos pueden inicializarse de la forma normal o leyendo la informacin de un archivo, l-larn falta dos constructores. el constructor predeterminado v otro que tome un objeto Strinj como argumento, a travs del cual suministraremos el nombre del archivo que hay que utilizar para inicializar el objeto. Ambos mtodos sern constructores, asi que lendran el mismo nombre: el nombre de la clase. Por tanto, la sobrecarga de mtodos resulta esencial para poder utilizar el mismo nombre de mtodo con diferentes tipos de argumentos Y. aunque la sobrecarga de mtodos es obligatoria para los consiruetores. tambin resulta til de manera general y puede ser empleada con cualquier otro mtodo

Me aqu un ejemplo que muestra tanto constructores sobrecargados como mtodos normales sobrecargados : int iallzat ion /Overloadmg .java Ilustracin dei mecanismo de sobrecarga ! t canco ae conscruccores como ae mtoaos normales _mpcrt sratic r.et .mr.dview.uc il. Print. * . eiass Tree ( int height; Tree() { print t" Plantmg a seedl ing" > ; neight = 0;

) Tree'int mitialHeight ( height - imt lalHeight ? print Creatina new Tree Chat is " -* height - ' feet tall")r

I vold info i) 1 printCTree is u - height * " feet cali"';

} vcid inf o <Str :.ng si { print Is - Tree is " + height * " feet tall"l;

i public class Overloading ( public static void mam (String U args) { forint i 0? i < 5; i-*-+J { Tree t = new Tree(i); t.info(); t.info("overloaded method");

} // Constructor sobrecargado: new Tree();

} } f * Output: Creating new Tree that is 0 feet tall Tree is 0 feet tall overloaded method: Tree is 0 feet tall Creating new Tree that is I feet tall Tree is 1 feet tall overloaded method: Tree is 1 feet call Creating new Tree that is 2 feet tall Tree is 2 feet tall overloaded method: Tree is 2 feet tall Creating new Tree that is 3 feet tall Tree is 3 feet tall overloaded method: Tree is 3 feet tall Creating new Tree that is 4 feet tall Tree is 4 feet tall overloaded method: 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 forma de planta criada en vivero, en cuyo caso tendremos que indicar la altura que tiene. Para soportar este comportamiento, hay un constructor predeterminado y otro que toma como argumento la altura del rbol.

Tambin podemos invocar el mtodo nfo() de varias formas distintas. Por ejemplo, si queremos imprimir un mensaje adicional, podemos emplear info(String), mientras que utilizaramos info( ) cuando no tengamos nada ms que decir. Sera bastante extrao proporcionar dos nombres separados a cosas que se corresponden, obviamente, con un mismo concepto. Afortunadamente, la sobrecarga de mtodos nos permite 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, vemos que tiene bastantes sentido. De qu otro modo podra un programador indicar la diferencia entre dos meiodos 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 si. aunque normalmente no conviene emplear esta tcnica, dado que produce cdigo difcil de mantener: //: initialization/OverloadingOrder.java // Sobrecarga basada en el orden de los argumentos, import static net.mindview.util.Print.; public class OverloadingOrder { static void f (String s, int i) {

5 Inicializacin y limpieza 260 print("String: H + s 4 ", int: " + i);} static void fint i, String s) ( print (" int: " * i *- ", String: " * a ) ;

i public static void main(Strir.g [J args) ( f("String first, 11Jj f(99, "Int first");

) ) / Output: String: String first, int: 11 int: 99. String: Int first ///:-

Los dos mtodos f( ) tienen argumentos idnticos, pero el orden es distinto y eso 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 tamao, y esto puede inducir a confusin cuando combinamos este mecanismo con el de sobrecarga. El

5 Inicializacin y limpieza 261 siguiente ejemplo ilustra lo que sucede cuando se pasa una primitiva a un mtodo sobrecargado: //: initialization/PrimitiveOverloadmg. java // Promocin de primitivas y sobrecarga, import static net.raindview.util.Print. # ; public class PrimitiveOverloading { void f3(int x) ( printnb{"f3(int) ") ; } void f3(long x) ( printnb("f3(long) n); ) void f3(float x) [ printnb("f3(float) M); ) void f3(double x) ( printnb("f3(double! w); ) void f4(int xl { printnb("f4Iint) "); ) void f4 (long x) { printnb("f4(long) } void f4(float x) ( printnb("f4(float{ H); ) void f4(double x) ( printnb("f4(double) ") ; ) void f5(long x) { printnb("f5(long) "); } void f5(float x) ( printnb(f5(float) "); } void 5(double x) ( printnb("f5 I double) "l; ) void f6 (float x) ( printnb("f6(float) H); ) void f6(double x) ( printnb(HfS (double) ") ; } void f7(double x) { printnb("f7(double) "); } void testConstVal() ( printnb < "5: ") ,* f 1 (5) ; f 2 (5); f 3 (5);f4(5);fS(S);f6(5);f7(5); print0;

} void teatChar() [ char x = ' x' ? printnb("char: M); fl(x) ; f 2 IX) ;f3(x) ;f4(x);f5(X) ;f6(x) ;f7 Ix) ; print(I ;

5 Inicializacin y limpieza 262 } void testByte{) { byte x = 0; printnbI"byte; "I; f 1 (x) ;f2(x) ; f3 ix) ,*f4 ix) / f5 (x) ;f 6 (x) ; 7 (x) ; print () ;

) void testShortO ( short x = 0; printnb("short: ") ; fl(x); L 2 (x);3(x);f4(x); f S ( x ) ;f6(x);f7(x); print!);

) void testlntU ( int x = 0; printnb{ "int: );

fl(X) ;f2(x) ;f3(x);f4 fX);f5 Ix) ;f6(x) ;f7(x) ; print(); } void testLongO j long x = 0; printnb("long: n); fl(x) ; 2 (x) ; f 3 (x) ; f 4 (x) ; f 5 (x) ; f 6 (x) ;f 7(x) ; printO;

5 Inicializacin y limpieza 263 void testFloat() { float x = 0/ printnbI"float: "); fl (xj ;f2 (x) ; f 3 (x) ,*f4 (x) ; f5 tx) ;f6 (x) ;f7 (x) ; print ( ) ;

J void testDouble() ( double x = 0; printnb("double: ") ; fl (x) ; f2 (x? ; f3 (x) ; f4 (x) ; f5 (x) ;f ix) ;f7 (x) ; print () ;

l public static void main(String I] args) { PrimitiveOverloading p = new PrimitiveOverloading(); p.testConstVal(); p.testChar(); p.testByte (I p.testShort () ; p.testlnt(J ; p.testLong(); p.testFloat I) ; d.testDouble()j

5 Inicializacin y limpieza 264 ) } / Output: 5: fl(int) f2(int > f3(int) f4(int) f5(long) f6(loat? f7(double) char: fl(char) f2(int) f3(int) f4iint> f5(long) f6(float) f7(double) byte: fl(byte) f2(byte) f3(short) f4(int) f5(long) f6(float) f7(double) short: fl(short) f2(short) f3(short) f4(int) f5(long) f6(float) f7(double) int: fl (int) f2(mt) f3(xnt) f4(int) f5(long) f6(float) f7(double) long: fl(long) f2(long) f3(long) f4(long) f5(long) f6(float) f7(double) float: fl (float) f 2 (float) f3 (float) f4*(float) f5(float> f6(float) f7(double) double: fl(double) f2(double) f3(double) f4(double) f5(double) f6(double' f7(double' ///;-Puede ver que el valor constante 5 se trata como int. por lo que si hay disponible un mtodo sobrecargado que toma un obje- io int. se utilizar dicho mtodo. En todos los dems casos, si lo que tenemos es un tipo de datos ms pequeo que el argumento del mtodo, dicho tipo de datos ser promocionado. char produce un efecto ligeramente diferente, ya que, si no se encuentra una correspondencia exacta con char, se le promociona a int

Qu sucede si nuestro argumento es mayor que el argumento esperado por el mtodo sobrecargado? Una modificacin del programa anterior nos da la respuesta: //: initialization/Demotion.java / Reduccin de primitivas y sobrecarga, import static net.mindview.util.Print.; public class Demotion { void testDoubleO { double x e 0; print("double argument:") ; fllx) ;f2 I (float)* ;f3{(long Ix);4<(int)x); f5((short)x); f 6 [ (byte)x);f7((cha r)x);

) public static void main(String[] args) ( Demotion p = new DemotionO; p.testDoubleO ;

5 Inicializacin y limpieza 265 ) } / Output: double argument: f1idoubl e) f2(float ) f3(long) IA int 1 f5(short i f 6 (byte) f7(char)

*///-

Aqu, los mtodos admiten valores primitivos ms pequeos. Si e! argumento es de mayor anchura, entonces ser necesario efectuar una conversin de estrechamiento mediante una proyeccin Si no se hace esto, el compilador generara un mensaje 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 argumentos de los mtodos? Por que no distinguir entre los mtodos basndonos en sus valores de retomo? Por ejemplo, los siguientes dos mtodos, que tienen el mismo nombre y los mismos argumentos, pueden distinguirse fcilmente: void f () () int f(} ( retum 1; }

5 Inicializacin y limpieza 266 Esto podra funcionar siempre y cuando el compilador pudiera determinar inequvocamente el significado a partir del contexto, como por ejemplo en int x = f(). Sin embargo, el lenguaje nos permite invocar un mtodo e ignorar el valor de retorno. que es una tcnica que a menudo se denomina invocar un mtodo por su efecto colateral, ya que no nos preocupa el valor de retomo, sino que queremos que tengan lugar los restantes efectos de la invocacin al mtodo. Luego entonces, si invocamos el mtodo de esta forma:

0f

Cmo podra Java determinar qu mtodo f( ) habra que invocar? Y cmo podra determinarlo alguien que estuv iera 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 predeterminado (tambin denominado constructor sin argumentos") es aquel que no tiene argumentos y que se utiliza para crear un "objeto predeterminado. Si creamos una c'ase que no tenga constructores, el compilador crear automticamente un constructor predeterminado Por ejemplo: //: initialization/DefaultConstruotor .java class Bird {) publie class DefaultConstructor ( public static void mainlStringH args) { Bird b * new Birdfl; // Default!

5 Inicializacin y limpieza 267 )

) ///:- La expresin new BirdO

crea un nuevo objeto y llama al constructor predeterminado, incluso aunque no se haya definido uno de manera explcita Sin ese constructor predeterminado no dispondramos de ningn mtodo al que invocar para construir el objeto. Sin embargo, si definimos algn constructor (con o sin argumentos), el compilador no sintentkar ningn constructor por nosotros: //: initialization/NoSynthesis.java class 8ird2

5 imcializacion y limpieza 268 (5ii*d2 int i> {) Pird2'double d- (} class NoSynthesis { public static void main (String (J args) ( ' Brd2 b * new Bird2<I; // No hay predeterminado Bird2 b2 = r.ew Blrd2U>? Blrd2 b3 = new Birdl.l;

Si escribimos: new Eira2(i

el compilador nos indicar que no puede localizar ningn constructor que se corresponda con la instruccin que hemos esculo. Cuando no definimos explcitamente ningn constructor, es como si el compilador dijera: "Es necesario utilizar algn constructor, asi que djame definir uno por ti". Pero, si escribimos al menos constructor, el compilador dice: Has escrito un constructor, as que tu sabrs lo que ests haciendo: si no has incluido uno predeterminado es porqti; no quieres hacerlo"

Ejercicio 3: (1) Cree una clase con un constructor predeterminado (uno que no tome ningn argumento) qu imprima

un mensaje. Cree un objeto de esa clase

5 imcializacion y limpieza 269 Ejercicio 4:(11 de tipo String e Aada un constructor sobrecargado al ejercicio anterior que admita un argumento

imprima la correspondiente cadena de caracteres junto con el mensaje.

Ejercicio 5:(2) Cree una clase denominada l)og con un mtodo sobrecargado bark( ) (mtodo "ladrar). Este mtodo

debe estar sobrecargado basndose en diversos tipos de datos primitivos y debe imprimir diferentes tipos de ladridos, gruidos, etc., dependiendo de la versin sobrecargada que se invoque. Escriba un mtodo main ) que invoque todas las distintas versiones

Ejercicio 6:(I) Modifique el ejercicio anterior de modo que dos de los mtodos sobrecargados tengan dos argumen

5 imcializacion y limpieza 270 tos (de dos tipos distintos), pero en orden inverso uno respecto del otro. Verifique que estas definiciones funcionan.

Ejercicio 7:(I) Cree una clase sin ningn constructor > luego cree un objeto de esa clase en niain( ) para verificar que

se sintetiza automticamente el constructor predeterminado. La palabra clave this

Si tenemos do> objetos del mismo tipo llamados a y b podemos preguntamos cmo es posible invocar un mtodo peelt ) para ambos objetos [nata, la palabra inglesa pee! significa "pelar una fruta, que en este ejemplo de programac 011 es una banana):

inicializatlon/BananaFeel.java [ / ... *'/ I

class Sanana | void peel i int 11 public class BananaPeel |

pblic static vold mam (String ti argsl { Banana * - new Banana (), b = new Banana M;
a. b.

peel il) ; peel (2) ;

5 imcializacion y limpieza 271 )

} ///.-

Si slo hay un nico mtodo denominado pt*el( ). cmo puede ese mtodo saber si est siendo llamado para el objeto a o para el objeto b '

Para poder escribir el cdigo en una sintaxis cmoda orientada a objetos, en la que podamos enviar un mensaje a un objeto. el compilador se encarga de realizar un cierto trabajo entre bastidores por nosotros. Existe un primer argumento secreto 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);

Esto se realiza internamente y nosotros no podemos escribir estas expresiones y hacer que el compilador las acepte, pero este ejemplo nos basta para hacemos una idea de lo que sucede en la practica.

5 imcializacion y limpieza 272 Suponga que nos encontramos dentro de un mtodo >queremos obtener lareferencia al objeto actual 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 lenguaje incluye una palabra clave especifica: this La palabra clave this. que slo se puede emplear en mtodos que no sean de tipo static devuelve la referencia al objeto para el cual ha sido invocado el mtodo. Podemos tratar esta referencia del mismo 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. sino que simplemente basta con invocar el mtodo La referencia this actual ser utilizada automticamente para el otro mtodo. De este modo, podemos escribir: //: initialization/Apricot.j ava public class Apricot ( void pick() ( /* , , * #/ J void pitO { pickO; /* ... */ )

} ///;-

Dentro de pit( ), podramos decir t!tis.pick( ) pero no hay ninguna necesidad de hacerlo. 13 El compilador se encarga de hacerlo automticamente por nosotros. La palabra clave this slo se usa en aquellos casos especiales en los que es necesario utilizar explcitamente la referencia al objeto actual. Por ejemplo, a Alguna* personas escriben obsesivamente thU leanle de cada llamada a mtodo * referencia a un campo argumentando que eso hace que el ctSdigo >ea miU claro y ms explcito" Mi consejo es que no lo haga. l-.Mste una razn por la que utilizamos los lenguajes de alto nivel, y esa razn es que estos lenguajes \c encargan de hacer buena parte del trabajo por no>otro>. Si incluimos la palabra clave lili cuando no e> necesario, las personas que lean el cdigo se sentirn confundidas, ya que los dcm> programas que hayan ledo en cualquier parte no utilizan las palabra clave this de manera contmuu L i programadores esperan que Ihl* slo se use all donde sea necesario Hl seguir un estilo de codificacin cohcrvutc y simple permite ahorrar tiempo y dinero.
13

5 imcializacion y limpieza 273 menudo se usa en instrucciones return cuando se quiere devolver la referencia al objeto actual: //: initialization/Leaf.java // Uso simple de la palabra clave thisMM. public class Leaf ( int i - 0 / Leaf mcrement \) (

-M-; return this,*

i void prmt () { System.out.println{**i * + i);

) public static void main(String[1 args) ( Leaf x = new Leaf () x.incrementI>.increment ).increment().printl)j

) ) / Output: i * 3

5 imcializacion y limpieza 274 *///:-

Puesto que incremente) devuelve la referencia al objeto actual a travs de la palabra clave this, pueden realizarse fcilmente mltiples operaciones con un mismo objeto.

La palabra clave this tambin resulta til para pasar el objeto actual a otro mtodo nitializtion/PaaingThis.java class Person ( public void eat(Apple apple' ( Apple peeled = apple.getPeeied1 > , System.out.crincin Yurarcy"1 ;

jlass Peeler |

5 imcializacion y limpieza 275 sratic Apple peel Apple applej ( / i ... pelar

return apple; // Pelada

class Apple {

Asele oecPesledi) { return Peeler.pee!<this); \

5 imcializacion y limpieza 276 punlic class PassingThis ( publlc static void mainlStringlI args { r.ew Per son () . eac (new Apple (l 1;

} ) /* Outpuc:

Vummy

//":-

El objeto Apple (manzana) necesita invocar Peeler.peel( ). que es un mtodo de utilidad externo que lleva a cabo una operacin que. por alguna razn, necesita ser externa a Apple (quiza ese mtodo externo pueda aplicarse a muchas clases distintas y no queremos repetir el cdigo). Para que el objeto pueda pasarse a si mismo al mtodo extemo, es necesario emplear this

5 imcializacion y limpieza 277 Ejercicio 8: el) Cree una clase con dos mtodos Dentro del primer mtodo invoque al segundo mtodo dos veces: la

primera vez sin utilizar this > la segunda utilizando dicha palabra cla\e. Realice este ejemplo simplemente para ver cmo funciona el mecanismo, no debe utilizar esia forma de invocar a los mtodos en la prctica. Invocacin de constructores desde otros constructores

C liando se escriben varios constructores para una clase, existen ocasiones en las que conviene invocar a un constructor desde dentro de otro para no tener que duplicar el cdigo. Podemos efectuar este tipo de llamadas utilizando la palabra clave this

Normalmente, cuando escribimos this. es en el sentido de "este objeto" o el "objeto actual. > esa palabra clave genera, por si misma, la referencia al objeto acnial. Dentro de un constructor, la palabra clave this toma un significado distinto cuando se la proporciona una lista de argumentos: realiza una llamada explcita al constructor que se corresponda con esa lista de argumentos. De este modo, disponemos de una forma sencilla de invocar a (ros constructores:

mtial izat ion/Flower. java Llamada a constructores con this, import static net.mindview.til.Print.;

5 imcializacion y limpieza 278 public class Flower f mt petalCount * 0;

String s = "initial valu";

Flowerlint petis ( petalCount = petis;

printl"Constructor w/ int arg only, petalCount "

petalCount);

Flower(String ss) { print ("Constructor w/ String arg only, s * H t- ss) ; S a SS;

} Flower{String s, int petis) ( this(petis) ; //! this(s); // jNo podemos realizar dos invocaciones! this.s = s; // Otro uso de "this" print("String i int aros");

5 imcializacion y limpieza 279 ) Flower{) { this("hi", 47); print("default constructor (no args)");

i void printPetalCount() ( //! this11); // No dentro de un no-constructor! print("petalCount = + petalCount - " s = " s);

} public static void main(String[] args) { Flower x new Plowerl); x.printPetalCount(};

} ) / * Output: Constructor w/ int arg only, petalCount* 47 String L int args default constructor (no args) petalCount = 47 s = hi *///:-

5 imcializacion y limpieza 280 Ul constructor Flo\ver(String s. int petis) muestra que, aunque podemos invocar un constructor utilizando this. no podemos invocar dos Adems, la llamada al constructor debe ser lo primero que hagamos, porque de lo contrario obtendremos un mensaje de error de compilacin.

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 ambigedad. Podemos resolverla utilizando this.s. para dejar claro que estamos refirindonos al miembro de datos. F.sta forma de utilizacin resulta muy habitual en el cdigo Java y se emplea en numerosos lugares del libro.

En printPetalCount( ) podemos ver que el compilador no nos permite invocar un constructor desde dentro de cualquier mtodo que no sea un constructor.

Ejercicio 9: (1) Cree una clase con dos constructores (sobrecargados). Utilizando this. invoque el segundo constructor

desde dentro del primero.

5 imcializacion y limpieza 281 El significado de static

l'eniendo en mente el significado de la palabra clave this. podemos comprender mejor qu es lo que implica definir un mtodo 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: (aunque la inversa si es posible), y se puede invocar un mtodo static para la propia clase, sin especificar ningn objeto. De hecho, esa es la principal aplicacin de los mtodos static: es como si estuviramos creando el equivalente de un mtodo global. Sin embargo, los mtodos globales no estn permitidos en Java, y el incluir el mtodo static dentro de una clase permite a los objetos de esa clase acceder a mtodos static \ a campos de tipo static.

Algunas personas argumentan que los mtodos estticos no son orientados a objetos, ya que tienen la semntica de un mtodo global Un mtodo esttico no enva un mensaje a un objeto, ya que no existe referencia this. Probablemente se trate de

5 imcializacion y limpieza 282 : ti nico cuso en que esto puede hacerse es cuando se pasa a) mtodo static una referencia .t un objeto (el mtodo stutic tambin podra crear su propio objeto) Entonces, a travs de la referencia ique ahora ser, en la prctica, this). se pueden invocar mtodos no static y acceder a campos no itatie. Pero, normalmente s queremos hacer algo como cito, lo mejor e< que escribamos un mtodo no static normal y corriente un argumento correcto, y si se encuentra alguna vez utilizando una gran cantidad de mtodos estticos probablemente convenga que \ uelva a meditar sobre la estrategia que est empleando Sin embargo. ION mtodos y valores estticos resultan bstanle prcticos. y hay ocasiones en las que de verdad son necesarios, por lo que la cuestin de si se trata de verdadera programacin orientada a objetos es mejor dejrsela a los tericos Limpieza: finalizacin y depuracin de memoria

Los programadores son conscientes de la importancia de la inieializacin, pero a menudo se olvidan de que tambin la limpieza es importante. Despus de todo, quin necesita limpiar un valor int' Pero, con las bibliotecas, limitarse a olvidarse de un objeto despus de haber acabado de utilizarlo no siempre resulta seguro. Por supuesto. Jav a dispone del dcpuiador de memoria para reclamar la memoria ocupada por aquellos obietos que va no estn siendo utilizados. Pero pensemos en lo que sucede en algunos casos poco usuales suponga que el objeto que lia definido asigna memoria especial" sin utilizar new. El depurador de memoria slo sabe como liberar la memoria asignada con new. por lo que no sabra como liberar la memoria "especian del objeto Para estos casos lava proporciona un mtodo denominado finalize( ) que se puede definir paru la clase. He aqu como se supone que funciona ese mtodo: cuando el depurador de memoria est listo para liberar el almacenamiento utilizado por el objeto, invocar primero finalizet ) \ slo reclamar la memoria del objeto en la siguiente pasada del depurador de memoria Por tanto, si decidimos utilizar finalizet ). tendremos la posibilidad de realizar (areas de limpieza importantes en el mntenlo en que se ptoJuzi o la depuracin Je memoria.

Esta es mui posible fuente de error de programacin, porque algunos programadores, especialmente los que provienen del campo del C+. pueden confundirse inicialmente V considerar que finalizet ) es el destructor da C-t. que es una funcin que se invoca siempre cuando >e destruye un objeto F * importante entender la distincin que existe enire C * y Java a este respecto, porque en O*. los objetos siempre se destruyen ten un programa libre de errores, mientras que en Java, el depurador de memoria no siempre procesa los objetos Dicho de otro modo I Fue Je que el JepuraJoi Je memoria no procese los objetos 2. I a depuracin Je memoria nn es equivalente a ta destrucc in Je/ objeto.

5 imcializacion y limpieza 283 Si m,* recuerdan estos dos principios, se podran evitar muchos problemas. Lo que quieren decir es que. si existe alguna actividad que deba de ser realizada antes de que un objeto deje de ser necesario, deberemos realizar dicha actividad nosotros nmmos Java no dispone de mtodos destructores ni de ningn otro concepto similar, por lo que es necesario crear un mtodo normal para llevar a cabo esta tarea de limpieza. Por ejemplo, suponga que. 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 que esa imagen nunca llegue a borrarse Si incluimos una cierta funcionalidad de borrado dentro de finalizet ). entonces si un objeto se ve sometido al proceso de depuracin de memoria y se invoca finalizet I (y recordemos que no existe ninguna garanta de que esto suceda), entonces se eliminar primero la imagen de la pantalla: pero si no incluimos explcitamente esa funcionalidad de borrado, esa imagen permanecer.

IMdotnov encontrarnos con la situacin de que nunca llegue a liberarse el espacio de almacenamiento de un objeto, debido a que el programa nunca se acerca a un punto en el que exista un nesgo de quedarse sin espacio de almacenamiento. Si el programa <e completa v el depurador de memoria no llega a entrar en accin para eliminar el espacio de almacenamiento asignado a los objetos, dicho espacio ser devuelto al sistema operativo en masut en el momento de salir del programa Lsta caracterstica resulta bastante conveniente, porque el proceso de depuracin de memoria implica un cierto gasto adicional de recursos de procesamiento, y si no se lleva a cabo, el casto no se produce. Para qu se utiliza finalize()?

Per. entonces, si no debe utilizarse finalizet > como mtodo de limpieza de propsito general, para qu sirve este mtodo '

n tercer punto que hay que recordar es: 3. La depuracin Je memoria solo se preocupa Je la memoria.

5 imcializacion y limpieza 284 s decir, la nica razn para la existencia del depurador de memoria es recuperar aquella memoria que el programa ya no esta utilizando Por tanto, cualquier actividad asociada con la depuracin de memoria, y en especial el mtodo finalizet ). debe tambin encargarse nicamente de la memoria y de su desasignacin .Significa esto que. si el objeto contiene otros objetos, finalize( ) debe liberar explcitamente esos otros objetos? En realidad no el depurador de memoria s ocupa de liberar toda la memoria de objetos, independientemente de cmo estos hayan sido creados, tn resumen, finalizet ) solo es necesario en aquellos casos especiales en los que el objeto pueda asignar espacio de almacenamiento utilizando alguna tcnica distinta de la propia creacin de objetos. Algn lector especialmente atento podra argumentar: pero, si todo Java es un objeto. ,.como puede llegar a producirse esta situacin?
I

Parece que finalze() se ha incluido en el lenguaje debido a la posibilidad de que el programador realice alguna aeti\ idad de estilo C. asignando memoria mediante algn mecanismo distinto del nonnalmenre empleado en Java. Esto puede suceder. principalmente, a travs de los mtodos nativos, que son una forma de invocar desde Jav a cdigo escrito en un lenguaje distinto de Java (los mtodos nativos se estudian en el Apndice B de la segunda edicin electrnica de este libro, disponible en www.Mhutt'icu.net). C > C'-t son los nicos lenguajes actualmente soportados por los mtodos nativos, pero como desde ellos se pueden invoeai subprogramas en otros lenguajes, en la prctica podremos invocar cualquier cosa que queramos Dentro del cdigo no Java, podra invocarse la familia de funciones malloc( ) de C para asignar espacio de almacenamiento. 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 C \ C por lo que sera necesario invocarla mediante un mtodo nativo dentro de flnalize( )

Despus de estas explicaciones, el lector probablemente estar pensando que no va a tener que utilizar finalizet ) de forma demasiado frecuente 1 En efecto, cn asi dicho mtodo nn es el lugar apropiado para realizar las tareas normales de limpieza Pero, entonces dnde lleva a cabo esas tareas normales de limpieza?

5 imcializacion y limpieza 285 Es necesario efectuar las tareas de limpieza

Para limpiai 1111 objeto, el usuario de ese objeto debe invocar un mtodo de limpieza en el lugar donde desee que sta se realice Esto parece bastante sencillo, pero choca un poco con el concepto C de destructor. En Cf--. todos los objetos se destruyen: o. mejor dicho, todos los objetos deberan destruirse Si el objeto C * * se crea como local (es decir, 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 utilizando new (como en Java), el destructor se invoca cuando el programador llama al operador Cdetele tque no existe en Java. Si el programador de O+ olvida invocar delote, nunca se llamar al destructor y se producir en la prctica una fuga de memoria (adems de que las otras partes del objeto nunca llegarn a limpiarse). Este tipo de error puede ser muy difcil de localizar y es una de las principales razones para pasar de O* a Java

Por contraste. Java no permite crear objetos locales, sino que siempre es necesario utilizar new Pero en Java, no existe ningn operador delete" para liberar el objeto, porqu el depurador de memoria se encarga de liberar el espacio de almacenamiento por nosotros. Por tanto, desde un punto de vista simplista, podramos decir que debido a la depuracin de memoria Java no dispone de destructores. Sin embargo, a medida que avancemos en el libro veremos que la presencia de un depurador de memoria no elimina ni la necesidad ni la utilidad de los destructores <y recuerde que no se debe invocar finalizo! 1 directamente, por l< que dicho mtodo no constituye una solucin). Si queremos que se realice algn tipo de limpieza distinta de la propia liberacin del espacio de almacenamiento, signe siendo necesario invocar explcitamente un mtodo apropiado en Java, que ser el equivalente del destructor de C pero sin la comodidad rociada a ste.

Recuerde que no esian garantizadas 111 la depuracin de memoria 111 la finalizacin Si la maquina \ iriual Java (JVM) no esta prxima a quedarse sin memoria, puede que no pierda tiempo recuperando espacio mediante el mecanismo de depuracin de memoria.

5 imcializacion y limpieza 286 La condicin de terminacin

En general, no podemos confuir en que finaliztM ) sea invocado v es necesario crear mtodos de limpieza separado* e invocarlos explcitamente Por tanto, parece que finalize< ) slo resulta til para oscuras tareas de limpieza de memoria que la mayora de los programadores nunca van a tener que utilizar. Sin embargo, existe un caso interesante de uso de tinalize( > que no depende de que dicha funcin sea invocada iodas las veces Nos referimos a la verificacin de la condicin de terminal ion de un objeto. losliua Bloch is tontuna na. ujutitc cu su seccin "IV/ii A HnoU:mhivs" "U> Imuliltcs imjswkvibkS a menudo peligro** v generalmente inuecoarios' AVAi/iu Lntiium/n GuUU |> 20 t Aldfcxn*NVc*!c>. 2ihi| 1 L11 linntu itcaibiio pi rtil Veuner , m h t irwmt i %m 1 ti im >cminjm> ju impartinw* ctniuimainente

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 difcil de localizar Podemos utilizar finalizet ) para descubrir esta condicin, incluso aunque dicho mtodo no sea siempre invocado. Si una de las finalizaciones nos permite delectar el error, habremos descubierto el problema, que es lo nico que realmente nos importa.

He aqu un ejemplo simple de cmo podra emplearse dicho mtodo: //: imtialization/TerminationCondit ion. java // Oso de finalizet) para detectar un objeto // que no ha sido limpiado apropiadamente.

5 imcializacion y limpieza 287 class Book ( boolean checkedOut = false; Bookboolean checkOut) { checkedOut = checkOut;

} void checkln() ( checkedOut false;

1 protected void finalizet) { if(checkedOut) System.out.printlnl"Error: checked out"); // Normalmente, tambin haremos esto: l // super.finalize0; // Invocar la versin de la clase base

i public class TerminationCondition ( public static void main(String[] args) | Book novel = new Book(tru); t Limpieza apropiada: novel.checkln(J;

5 imcializacion y limpieza 288 // Falta la referencia, nos olvidamos de limpiar, new Book(trut ; // Forzar la depuracin de memoria y la finalizacin:

Syelem.gc)r

) ) /* Output: Error: checked out V//3-

La condicin de terminacin es que se supone que todos los objetos Book (libro) deben ser devueltos (chvck in) antes de que los procese el depurador de memoria, pero en maint ) hay un error de programacin por el que uno de los libros no es devuelto. Sin finali/e( ) para verificar la condicin de terminacin, este error puede ser difcil de localizar.

Observe que se utiliza System.jc< ) para forzar la finalizacin. Pero, incluso aunque no usramos esc mtodo, resulta altamente 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 memona).

5 imcializacion y limpieza 289 Por regla general, debemos asumir que la versin de finalizet ) de la clase base tambin estar llevando a cabo alguna tarea importante, por lo que convendr invocarla utilizando super. como puede verse en Book.finalize( ) En este caso, hemos desactivado esa llamada mediante comentarios, porque requiere utilizar los mecanismos de tratamiento de excepciones de los que an no hemos hablado en detalle.

Ejercicio 10: (2)Creeunaclaseconun mtodo finalizet ) que imprima un mensaje. En maint ). cree un objeto de esa

clase. Explique el comportamiento del programa.

Ejercicio 11: (4)Modifique finalizet ).

el ejercicio

anterior de modo que siempre se invoque el mtodo

Ejercicio 12: (4) Cree una clase denominada Tank (tanque) que pueda ser llenado y vaciado, y cuya condicin de ter

5 imcializacion y limpieza 290 minacin es que el objeto debe estar vacio en el momento de limpiarlo. Escriba un mtodo finalizo! ) que verifique esta condicin de terminacin. En main( ). compruebe los posibles casos que pueden producirse al utilizar los objetos lank 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 temimos 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 extrao a primera vista (el que la liberacin del espacio de almacenamiento afecte a la velocidad de asignacin de dicho espacio) pero sa es la forma 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 transportadora 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 notablemente rpida: simplemente se desplaza hacia adelante el puntero del cmulo de memoria para que apunte a un espacio vacio, 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 cima transportadora, y que si lo consideramos de esa manera comenzarn a entrar en accin los mecanismos

5 imcializacion y limpieza 291 de paginacin de memoria, desplazando informacin hacia y desde el disco, de modo que puede parecer que disponemos de ms memoria de la que realmente existe. Los mecanismos de paginacin afectan significativamente a la velocidad. Adems, despus de crear un nmero grande de objetos terminar por agotarse la memoria. El truco radica en que el depurador de memoria, mientras se encarga de liberar 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 transportadora". 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 esc modelo de cmulo de memoria infinito de alta velocidad para asignar el espacio de almacenamiento.

Para entender el proceso de depuracin de memoria en Java, resulta til analizar cmo funcionan los esquemas de depuracin de memoria en otros sistemas. Una tcnica muy simple, pero muy lenta, de depuracin de memoria es la que se denomina recuento tic 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 forma, cada vez que una referencia se sale de mbito o se la asigna el valor nuil, se reduce el contador de referencias. Este mecanismos de gestin del nmero de referencias representa un gasto adicional pequeo, pero constante, que tiene lugar mientras dura la ejecucin del progiama. El depurador de memoria recorre la lista completa de objetos, y donde encuentra uno cuyo contador de referencias sea cero, libera el espacio de almacenamiento correspondiente (sin embargo, los mecanismos basados en el recuento de referencias suelen liberar los objetos tan pronto como el contador pasa a \alcr cero), La desventaja es que. si hay una serie de objetos que se refieren circula! mente entre si. podemos encontramos con nmeros de referencia distintos de cero a pesar de que los objetos ya no sean necesarios. La localizacin de esos grupos auto-referenciales exige al depurador de memoria realizar un trabajo adicional bastante significativo. Este mecanismo de recuento de referencias se suele utilizar de forma bastante habitual para explicar uno de los posibles mecanismos de depuracin de memoria, pero no parece que se use en ninguna implementacin de mquina JVM.

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 esttico. Esta cadena debe atravesar varios niveles 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 coa el proceso de traza, entrando en el objeto al que apunta la referencia y siguiendo a continuacin todas las referencias incluidas en ese objeto. entrando en los objetos a los que esas referencias apuntan, etc., hasta recorrer todo el rbol que se origina en la referencia >ituada en la pila o en almacenamiento esttico. Cada objeto a travs del cual pasemos seguir estando viv. Observe que no se presenta el problema de los grupos auto-referenciales: los objetos de esos grupos no sern recorridos durante este proceso de construccin

5 imcializacion y limpieza 292 del rbol, por lo que se puede deducir automticamente que hay que depurarlos.

En la tcnica que acabamos de describir, la JVM utiliza un esquema de depuracin de memoria adaptativo. en el que lo que hace con los objetos vivos que localice depender de la variante del esquema que se est utilizando actualmerte. Una de esas variantes es parar y copiar, lo que quiere decir que (por razones que luego comentaremos) se detiene primero el programa (es decir, no se trata de un esquema de depuracin de memoria que funcione en segundo plano). Entonces cada objeto 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 asi el rea ocupada (y permitiendo 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 objeto. Las referencias que apunten al objeto desde el cmulo de memoria o el rea de almacenamiento esttico pueden modificarle directamente, pero puede que haya otras referencias apuntando a este objeto que sean encontradas posteriormente, durante el proceso de construccin del rbol. Estas referencias se irn modificando a medida que sean encontradas (imagine. por ejemplo, que se utilzala una tabla para establecer la correspondencia entre las antiguas direcciones y las nuevas)

I lay

dos problemas que hacen que estos denominados depuradores copiadores* sean poco eficientes. El primero es la necesidad de disponer de dos reas de cmulo de memoria, para poder mover las secciones de memoria entre una y otra, lo que eri la prctica significa que hace falta el doble de memoria de la necesaria. Algunas mquinas JVM resuelven este problema asignando el cmulo de memoria de segmento en segmento, segn sea necesario, y simplemente copiando de un segmento a otro.

5 imcializacion y limpieza 293 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 copiando 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 distinto (sta es la parte "adaptati- va"). Este otro esquema se denomina manar y eliminar, y es el que utilizaban de manera continua las anteriores versiones de la JVM 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 trazar 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- ios Sin embargo, hay que observar que no se produce ningn proceso de copia, por lo que si el depurador de memoria decide compactar un cumulo 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 no se hace en segundo plano; en lugar de ello, se detiene el programa mientras tiene lugar la depuracin de memoria. En la documentacin tcnica de Sun podr encontrar muchas referencias al mecanismo de depuracin de memoria donde se dice que se trota de un proceso de segundo plano de baja prioridad, pero la realidad es que la depuracin de memoria no estaba implementada de esa forma en las primeras mquinas JVM 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 programa.

Como hemos mencionado anteriormente, en la mquina JVM descrita aqu, la memoria se asigna en bloques de gran tamao. 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

5 imcializacion y limpieza 294 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 memoria puede normalmente 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 normal, slo se compactan los bloques creados desde la ltima pasada de depuracin de memoria; para todos los demas bloques se incrementar el contador de generacin si han sido referen- ciados desde algn sitio. Esto permite gestionar el caso normal en el que se dispone de un gran nmero de objetos temporales de corta duracin. Peridicamente, se hace una limpieza completa, en la que los objetos de gran tamao seguirn sin ser copiados (simplemente se incrementara su contador de generacin) y los bloques que contengan objetos pequeos se copiarn y compactarn La maquina JVM moni (oriza la eficiencia del depurador de memoria y. si este mecanismo se convierte en una perdida de tiempo porque todos los objetos son de larga duracin, conmuta al mecanismo de marcado y limpieza. De forma similar, la JVM controla hasta qu punto es efectiva la tcnica de marcado y limpieza, y si el cmulo de memoria comienza a estar fragmentado, conmuta al mecanismo de detencin y copiado. Aqui es donde entra en accin la parte *adap- tativa del mecanismo, 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 JVM. Una especialmente importante afecta a la operacin del cargador y es lo que se denomina compilador. fust-in-time (JIT). Un compilador JIT convierte parcial o totalmente 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. C uando 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 intermedio es bastante ms compacto que el cdigo JIT expandido), y esto puede provocar la aparicin del fenomeno de paginacin, lo que ralentiza enormemente los programas. Otra tcnica alternativa es la evaluacin lenta, que consiste en que el cdigo intermedio no se compila para generar cdigo mquina hasta el momento necesario. De este modo, puede que nunca se llegue a compilar el cdigo que nunca llegue a ejecutarse. Las tecnologas HotSpot de Java en los kits de desarrollo JDK recientes adoptan una tcnica similar, optimizando de manera mcrcmcntal un fragmento de cdigo cada vez que se ejecuta, por lo que cuanto ms veces se ejecute, ms rpido lo har. Inicializacin de miembros

Java adopta medidas especiales para garantizar que las variables se inicial icen adecuadamente antes de

5 imcializacion y limpieza 295 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 tanto, si escribimos:

void t () ( int i; i++; // Error i no inicial izada

obtendremos un mensaje de error que dice que puede no haber sido no micializada. Por supuesto, el compilador podra haber dado a i un valor predeterminado, pero el hecho de que exista una variable local 110 inicializada es un error del programador. y el valor predeterminado estara encubriendo ese error. Forzando al programador a proporcionar un valor de inicializacin. 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 Capitulo

5 imcializacion y limpieza 296 Toda es un objeto, se garantiza que cada campo primitivo de una clase contendr un valor inicial He aqui un programa que permite verificar este hecho y mostrar los valores: //; initialixation/InitialValuea.java
2,

// Muestra los valores iniciales predeterminados. import static net .mindview.util. Pnnt. * ; public class InitialValues { boclean t; char c ; byte b; short s; int i; long 1; float f; double dr InitialValues reference; void printlnitialValues(} (

) public static void main(String[] args) { Ini tiaivalues iv = new Initial Values () ,* iv.printlnitialValues0; i * Tambin podramos escribir: new InitialValues().printlnitial Values();

Puede ver que, an cuando no se Kan especificado los valores, los campos se inicializan automticamente (el valor correspondiente a citar es cero, lo que se imprime como un espacio). De modo que, al menos, no existe ningn nesgo e llegar a trabajar con variables no inicial izadas.

Cuando definimos una referencia a objeto dentro de una clase sin inicializarse como nuevo objeto, dicha referencia se int- csaliza con el valor especial nuil

5 imcializacion y limpieza 297 Especificacin de la inicializacin

Que sucede si queremos dar un valor inicial a una variable? Una forma directa de hacerlo consiste, simplemente, en asignar el valor en el punto en que definamos la variable dentro de la clase (observe que no se puede hacer esto en C++. aunque los programadores novatos de C-H* siempre lo intentan), En el siguiente fragmento de cdigo, se modifican las definiciones de los campos de la clase InitialYalues para proporcionar los correspondientes valores iniciales: //: inltialization/InitialValues2.java // Definicin de valores iniciales explcitos. public class IntialValues2 1 boolean bool = true; char ch = *x' byte b * 4 7 ; ahort s * Oxff; int i = 9 99; long lng =. 1 ; float f = 3 .14f , double d = 3.14159;

) ///:-

Tambin podemos imcializar objetos no primitivos de la misma forma. Si Depth es una clase, podemos crear una \ariable e inicialtzaria como sigue: /: irutialization/Measuremen t. java class Depth {) public class Measurement ( Deoth d = new Depth O ;

5 imcializacion y limpieza 298 // ...

} ///:-

Si no hubiramos dado a d un valor inicial y tratramos de usarlo de todos modos, obtendramos un error de tiempo de ejecucin denominado excepcin (hablaremos de este tema en el Capitulo 12. Tratamiento de errores mediante excepciones).

Podemos incluso llamar a un mtodo para proporcionar un valor de inicializacin: //: intializaton/Methodlnit .java public class Methodlnit { nt i f ( \ ; int f() { return 11; )

} ///:-

5 imcializacion y limpieza 299 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: //: initialization/MethodXnit 2.java public class MethodInlt2 { int i - f ( ) ; int j * g(lj / int f O { return 11; | i n t gint n) { return r* *10; (

) ///:-

Pero no esto: //: initiali zaton/Method!nit3.java public class MethodInlt3 ( //! int j= g(i)j // Referencia anticipada ilegal

) ///:-

5 imcializacion y limpieza 300 Este 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 torma en que se compila el programa.

Esta tcnica de inicializacin resulta bastante simple y directa. Tiene la limitacin de que tados los objetos de tipo InitialValues contendrn el mismo valor de inicializacin. En ocasiones, esto es. exactamente, lo que queremos, pero en otros casos hace falta ms flexibilidad. Inicializacin mediante constructores

Podemos emplear el constructor para realizar la inicializacin, y esto nos proporciona una mayor flexibilidad a la hora de programar, porque podemos invocar mtodos y realizar acciones en tiempo de ejecucin para determinar los valores iniciales. Sin embargo, es preciso tener presente una cosa: esto no excluye la inicializacinautomtica que tiene lugar antes de

entrar en el constructor. Asi que. si escribimos por ejemplo: //: initialization/Counter.ja va public class Counter ( int i; Counter(> { i * 7; } // . . .

5 imcializacion y limpieza 301 } ///:-

entonces i se inicializar primero con el valor 0. y luego con el valor 7. Esto es cierto para todos los tipos primitivas v tambin 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 determina mediante el orden en que se definen las variables en la clase. Las definiciones 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: //: initialisation/OrderGflnitializat ion. java t t Ilustra el orden de inicializacin. import stat-ic net.mindview.util.Prmt. Cuando se invoca el constructor para crear un // objeto Window, aparecer el mensaje: clase Window { Window<int marker) ( print I "Window < *' + marker * }

} class House ( Window wl = new Window( l ) // Antes del constructor House 1 |

5 imcializacion y limpieza 302 // Mostrar que estamos en el constructor: print("House O"); w3 = new Window1331; t f Reinlcallzar w3

) Window w2 <= new Window{2) ; // Despus del constructor voidf) { print{"ft)"); } Window w3 = new Window(3), // Al final i public class OrderOfInitialization { public static void mainlStringU argst ( House h = new House ();
h.

ftJi // Muestra que la construccin ha finalizado

) } /* Output: Window(1) Window(2) Window(3) House(J Window(33I f O *///?-

En House. 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. w3 se reinicializa dentro del constructor.

5 imcializacion y limpieza 303 Examinando la salida, podemos ver que la referencia a w3 se inicial iza 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 definiera un constructor sobrecargado que no inicializara w3 y no hubiera una inicializacin predeterminada* para 3 en su definicin? Inicializacin de datos estticos

Slo existe una nica rea de almacenamiento para un dato de tipo static, independientemente del nmero de objetos que se creen. No se puede aplicar la palabra clave static a las variables locales, as que slo se aplica a los campos. Si un campo es una primitiva 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 nuil.

Si desea colocar la inicializacin en el punto de la definicin, ser similar al caso de las variables no estticas.

Para ver cundo se inicializa el almacenamiento de tipo static, ha aqui un ejemplo: //t lntalization/StaticInitialization.java // Especificacin de valores iniciales en una definicin de clase. import static net.mindview.util.Print.; class Bowl { Bowl(int marker) { print(MBowl(" marker H)w);

5 imcializacion y limpieza 304 I void f1(int marker) ( print("fl(" + marker

) class Table ( static Bowl bowll = new Bowl(II; TableO ( print ("Table 0*1; bowl2,f1(1)?

) void f2(int marker) { print I "f2 (" + marker * "J");

) static Bowl bowl2 = new Bowl(2);

5 imcializacion y limpieza 305 ) class Cupboard { Bowl bowl3 new Bowl(3); static Bowl bowl4 = new Bowl(4); Cupboard() ( print("Cupboard O H J; bowl4.f1 (2 ); I void f3(int marker) { print("f3(" marker ")")j

) static Bowl bowls new Bowl(5);

} public class Staticlnitialization ( public static void mam (String 1] args) { print("Creating new CupboardO in main*); new Cupboard(); print("Creating new CupboardO in main"); new Cupboardl); table.f2 1); cupboard.f3(1}; l static Table table new TableO; static Cupboard cupboard = new Cupboard 0 I /* Oucpuc:

5 imcializacion y limpieza 306 Bowl(1) Bowl<2) Table() fUl) Bowl(4) Bowl(5) Bowl(3) Cupboard() f 1 (2) Creacing new Cupboard(i in main Bowl(3) Cupboard() f 1 (2) Creating new Cupboard() in main 3owl(3) '.upboard (} 1(2) 2(1) f 3 {1)

*///:-

Bowl pcmnte visualizar la creacin de una clase, mientras que Table y Cupboard tienen miembros de tipo static de Bowl dispersos por sus correspondientes definiciones de clase. Observe que Cupboard crea un objeto Bowl bow*L3 no esttico antes de las definiciones de tipo static.

5 imcializacion y limpieza 307 Examinando la salida, podemos ver que la inicializacin de static slo tiene lugar en caso necesario. Si no se crea un objeto Table y nunca se hace referencia a Table.bowll o Table.bowl2. los objetos Bowl estticos bowll y bowl2 nunca se crearn. Slo se inicializarn cuando se cree el primer objeto Table (cuando tenga lugar el primer acceso static) Despus de eso. los objetos static no se remicializan.

El orden de inicializacin es el siguiente: primero se inicializan los objetos estticos, si es que no han sido ya imcializados con una previa creacin de objeto, y luego se inicializan los objetos no estticos. Podemos ver que esto es asi examinando la salida del programa. Para examinar main( ) (un mtodo static), debe cargarse la clase Staticlnitialization. despus de lo cual se inicializan sus campos estticos table y cupboard, lo que hace que esas clases se carguen y. como ambas contienen objetos Bowl estticos, eso hace que se cargue la clase Bowl. Por tanto, todas las clases de este programa concreto se cargan antes de que d comienzo main( ). ste no es el caso usual, porque en los programas tpicos no tendremos todo s incu- lado entre si 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 utilice explcitamente la palabra clave static. el constructor es. en la prctica, un mtodo static. Por tanto, la primera vez que se crea un objeto de tipo Dog. o la primera v ez que se accede a un mtodo esttico o a un campo esttico de la clase Dog, el intrprete de Java debe localizar Dog.class. para lo cual analiza ruta de clases que en ese momento haya definido (classpath).

2.

A medida que se carga Dog.class (creando un objeto Class. acerca del cual hablaremos

5 imcializacion y limpieza 308 posteriormente) se ejecutan todos sus inicializadorcs 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 Do

de construccin asignaprimero

del objeto 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 predeterminados a todas las primitivas del objeto Dog (cero a los nmeros y el equivalente para boolean v char); asimismo,

este proceso hace que las referencias queden con el valor nuil

5 imcializacion y limpieza 309


5.

Se ejecutan las inicializaciones especificadas en el lugar en el que se definan los campos.

6.

Se ejecutan los constructores. Como podremos ver en el Capitulo 7. Reutilizacin de las clases. esto puede implicar un gran nmero de activ idades, especialmente cuando estn implicados los mecanismos de herencia.

Inicializacin static explcita

Java permite agrupar otras i nidal izae iones estticas dentro de una clusula" static especial (en ocasiones denominada bloque esttica) en una clase. F.l aspecto de esta clusula es el siguiente: //: initiali2 ation/Spoon.java public class Spoon ( static inc i; static ( i = 47*

) ///*.-

5 imcializacion y limpieza 310 Parece ser un mtodo, pero se trata slo de la palabra clave static seguida de un bloque de cdigo. Este cdigo, al igual que otras inicializacinnes estticas slo se ejecuta una vez: la primera vez que se crea un objeto de esa clase o la primera vez que se accede a un miembro de tipo static de esa clase (incluso aunque nunca se cree un objeto de dicha clase). Por ejemplo: //: initialization/ExplicitStatic.java // Inicializacin static explcita con la clusula "static", import static net.mindview.util.Print.*; class Cup { Cup I int nvarker) ( print < "Cup ( marker * )**);

! void (int marker) { print <**f(" t- marker ")'*>; I

} class Cups { static Cup cupl; static Cup cup2; static ( cupl = new Cup(l) ; cup2 - new Cup(2);

5 imcializacion y limpieza 311 ) Cups(J ( print ('CupsO H);

) I public class ExplicrtStatic | public static void main(String[J args ( print ("InGide main ()"),* Cups.cupl.f(99);// (1)

} // static Cups cupsl * new CupsO,* // 12) // static Cups cups2 * new CupsO; // (2) } / Output: Inside mainO Cup(1) Cup(2)
I

(99)

///:-

5 imcializacion y limpieza 312 Los inicializadores static para Cups se ejecutan cuando tiene lugar el acceso del objeto esttico cupl en la linea marcada con (I), osi se desactiva mediante un comentario la linea (I) y se quitan los comentarios que desactivan las lineas marcadas (2). Sise desactivan mediante comentarios tanto (I) como (2), la inicializacin static deCups nunca tiene lugar,como puede verse a la salida. Asimismo, da igual si se eliminan las marcas de comentario que estn desactivando a una y otra de las linea* marcadas (2) o si se eliminan las marcas de ambas lneas; la inicializacin esttica tiene lugar una sola vez.

Ejercicio 13: (I) Verifique las afirmaciones contenidas en el prrafo anterior.

Ejercicio 14: (!) 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 static. Aada un mtodo static que imprima ambos campos y demuestre que ambos se inicializan antes de usarlos. Inicializacin de instancias no estticas

Java proporciona una sintaxis similar, denominada inicializacin Je instancia, para inicializar las variables estticas de cada objeto. He aqui un ejemplo: ) i : initialization/Mugs.java // Inicializacin de instancia" en Java, import static

5 imcializacion y limpieza 313 net.mindview.util.Print. * ; class Mug { Mug{int marker) { print("Mug<" marker + ")") ; l void f(int marker) { print (Mf(N *- marker "));

) I public class Mugs { Muq mugl; Mug mug2;

{ mugl * new Mug(l); mug2 = new Mug(2); print("mugl 4 mug2 initialized");

) Mugs() ( print("Mugs()"J;

5 imcializacion y limpieza 314 ) Mugs t i nr. i) { print("Mugs(int) w) ;

I public static void main(String{ ] args' { print{uInside main 0"); new Mugs(); print ("new Mugs!) completed**) ? new Mugs(l); print(Wnew Mugs(l) completed');

) ) /* Output: Inside main() Mug(1) Mug(2) mugl & mug2 initialized Mugs () new KugsO completed Mug (1) Mug(2) mugl & mug2 initialized Mugs(int)

Podemos ver que la clusula de inicial i/acin de instancia: 315 Piensa en Java new Mugs 11) completed *///:- mug * new Mug|1); mug2 new Mug(21; print"mug & mug2 initialized");

parece exactamente como la clusula de inicializacin estatica. salvo porque falta la palabra clave static. Esta sintaxis es necesaria para soportar la inicializacin de clases internas annimas (vase el Captulo 10. Clases internas), pero tambin nos permite garantizar que ciertas operaciones tendrn lugar independientemente de qu constructor explcito se invoque. Examinando la salida, podemos ver que la clusula de inicializacin de instancia se ejecuta antes de los dos constructores.

Ejercicio 15: (1) Cree una clase con un campo String que se inicialice mediante una clusula de inicializacin de ins

tancia.

Podemos ver que la clusula de inicial i/acin de instancia: 316 Piensa en Java Inicializacin de matrices

Una matriz es, simplemente, una secuencia de objetos o primitivas que son todos del mismo tipo y que se empaquetan juntos. utilizando un nico nombre identificador. Las matrices se definen y usan mediante el aperador de indexacin | | Para definir una referencia de una matriz, basta con incluir unos corchetes vacos detrs del nombre del tipo: int [J al;

Tambin puede colocar los corchetes despus del identificador para obtener exactamente el mismo resultado: int al [] ;

Esto concuerda con las expectativas de los programadores de C y C++. Sin embargo, el primero de los dos estilos es una

sintaxis ms adecuada, ya que comunica mejor que el tipo que estamos definiendo es una matriz de variablesde tipo int

Podemos ver que la clusula de inicial i/acin de instancia: 317 Piensa en Java Este estilo es el que emplearemos en el libro.

El compilador no permite especificar el tamao de la matriz. Esto nos retrotrae al problema de las referencias anteriormente comentado. Todo lo que tenemos en este punto es una referencia a una matriz (habiendo asignado el suficiente espacio 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 inicializacin puede hacerse en cualquier lugar del cdigo, pero tambin podemos utilizar una clase especial de expresin de inicializacin 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 ilc espacio (el equivalente de utilizar new) Por ejemplo: int I] al - { 1. 2, 3, 4. 5 );

Pero entonces, por qu bamos a definir una referencia a una matriz sin definir la propia matriz? int [3 a2;

Bueno, la razn para definir una referencia sin definir la matriz asociada es que en Java es posible asignar una matriz a otra, por lo que podramos escribir: a2 = al;

Lo que estamos haciendo con esto es, en realidad, copiar una referencia, como se ilustra a continuacin:

Podemos ver que la clusula de inicial i/acin de instancia: 318 Piensa en Java //: initialization/ArraysOfPrimitive s.java mport static net.mindview.til.Print; public class ArraysOfPrimitives ( public static void mainString[] args) { int [] al * { l t 2, 3, 4, 5 }; int [] a2; a2 * al; forlint i = 0; i < a2.1ength; a2 [ij = a2[ij 1; for(int 1=0; 1 < al.length; i++) prmt(Mal[M i + **J H al til);

) ) /* Output: al(01 * 2 al IU = 3 al(2j * 4 al 13] = 5 al [4] * 6 *///:-

Como puede ver. a al se le da un valor de inicializacin. pero a a2 no: a a2 se le asigna posteriormente un valor que en este caso es la referencia a otra matriz. Puesto que a2 y al apuntan ambas a la misma matriz, los cambios que se realicen a travs de a2 podrn verse en al

Todas las matrices tienen un miembro intrnseco (independientemente de si son matrices de objetos o

Podemos ver que la clusula de inicial i/acin de instancia: 319 Piensa en Java matrices ce primitivas) que puede consultarse (aunque no modificarse) para determinar cuntos miembros hay en la matriz. 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 elemento mximo que se puede indexar es lengtb - 1. Si nos salimos de los lmites, C y C++ lo aceptarn en silencio y permitirn que hagamos 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 limites.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 permite crear una primitiva simple que no forme paite de una matriz): //: initialization/ArrayNew.j ava // Creacin de matrices con new. import java.ucil; import static r.et .mindview.util. Print. *; public class ArrayNew ( public static void mainString[] args) { intll a; Random rand = new Random(47) a = new int (rand.nextInt (20)) pnnt ("length of a = " + a.length); print (Arrays. toString (a)) ;

} / Output: length of a s 18 [0, 0, 0, 0, 0, 0. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0. 0, 01 V//:-

F.l tamao de la matriz se selecciona aleatoriamente utilizando el mtodo Random.ne\tlnt(), 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 matriz tiene 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 variables boolean se

Podemos ver que la clusula de inicial i/acin de instancia: 320 Piensa en Java inicializan con false).

F.l mtodo Arrays.ioString( ). que forma parte de la biblioteca estndar java.util. genera una versin imprimible de una matriz unidimensional. ' Por supuesto, comprobar cada acceso de una nuitri/ 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 ineticiencia eu los programa, si se producen en alguna seccin critica. En arus de la seguridad en Interne! ' de la productividad de los programadores, los diseadores ai Java pensaron que resultaba conveniente pagar este precio para evitar los errores asociados con las matrices Aunque el programador pueda sentirse tentado de escribir cdigo para tratar de hacer que los accesos a las matrices sean ms eficientes. esto es una perdida de tiempo, porque las optimizaciones automticas en tiempo de compilacin y en tiempo de ejecucin se encargan de acelerar los accesos a las matrices.

Por supuesto, en este caso la matriz tambin poda haber sido definida e inieializada en la misma instruccin: intlj 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 matriz de referencias. Considere el tipo envoltorio Integer, que es una clase y no una primitiva:

Podemos ver que la clusula de inicial i/acin de instancia: 321 Piensa en Java //; initialization/ArrayClassObj.java // Creacin de una matriz de objetos no primitivos. lmport java.til.* import static net.mindview.util.Print.* ; public class ArrayClassGbj ( public static void main(String[] argsl { Random rand * new Random(47) Integerf] a new Integer [rand.nextlnt(20) 1 ; print("length of a * H + a.iength); forint 1 = 0 ; i < a.iength; i++) a[i] = rand.nextlnt1500) ; // Conversin automtica print{Arrays.toString(a)};

) /* Output:(Sample) length of a = 18 [55, 193, 361, 461. 429, 368, 200, 22. 207, 288, 128, 51, 89, 309, 278, 498, 361, 20] *///:-

Aqui. incluso despus de invocar new para crear la matriz: Integerfl a <= new Integer[rand.nexLInt(20)];

es slo una matriz de referencias y la micializacin no se completa hasta que se inicialice la propia referencia creando un nuevo objeto Integer (mediante el mecanismo de conversin automtica, en este caso): ali] = rand.nextlnt(500);

Podemos ver que la clusula de inicial i/acin de instancia: 322 Piensa en Java Sin embargo, si nos olvidamos de crear un objeto, obtendremos una excepcin en tiempo de ejecucin cuendo tratemos de utilizar esa posicin vacia de la matriz.

Tambin es posible inicializar matrices de objetos mediante una lista encerrada entre llaves. He aqui dos formas de hacerlo:

//: initializationyArraylnit.java // Inical2acin de la matriz, import j ava.uti1.; public class Arraylnit { public static void main(StringU args) ( IntegerU a * ( new Integerti), new Integer12), 3, // Conversin automtica IntegerU b = new IntegerU ( new Integer(1), new Integer(2), 3, // Conversin automtica

); System.out.printiniArrays.toString\a>i; System.out.println:Arrays.toString(b));

Podemos ver que la clusula de inicial i/acin de instancia: 323 Piensa en Java ) ) / Output: 11. 2. 3] ti. 2, 31 *///:-

\n ambos casos, la coma final de la lisia de inicializadores es opcional (esta caracterstica permite un mantenimiento mas fcil de las listas de gran tamao).

Aunque la primera forma es til, es ms limitada, porque slo puede emplearse en el punto donde se define la matriz. Podemos utilizar las formas segunda y tercera en cualquier lugar, incluso dentro de una llamada a un mtodo. Por ejemplo, podramos crear una matriz de objetos String para pasarla a otro mtodo main( ). con el fin de proporcionar argumentos de linea de comandos alternativos a ese mtodo main( ): //: initialization/DynamcArray. java i ) Inicializacin de la matriz. public clase DynamcArray { public static void main(StringU aros) { Other.mainnew String[]{ "fiddle", "de", "dum" \ ) ;

Podemos ver que la clusula de inicial i/acin de instancia: 324 Piensa en Java } class Other ( public static void main(StringH args) { for(String s : args) System.out .print (s *- "

} ) /* Output: fiddle de dura *///:-

La matriz creada para el argumento de Other.main( ) se crea en el punto correspondiente a la llamada al mtodo, asi que podemos incluso proporcionar argumentos alternativos en el momento de la llamada

Ejercicio 16: (1) Cree una matriz de objetos Strinj y asigne un objeto String a cada elemento. Imprima la matriz utili

zando un bucle for.

Podemos ver que la clusula de inicial i/acin de instancia: 325 Piensa en Java Ejercicio 17: (2) Creeunaclaseconunconstructorquetome un argumento String. Durante la construccin, imprima

el argumento. Cree una matriz de referencias a objetos de esta clase, pero sin crear ningn objeto para asignarlo a la matriz. Cuando ejecute el programa, observe si se imprimen los mensajes de inicializacin correspondientes a las llamadas al constructor.

Ejercicio 18: (l) Complete el ejercicio anterior creando objetos que asociar a la matriz de referencias. Listas variables de argumentos

i segunda forma proporciona una sintaxis cmoda para crear c invocar mtodos que pueden producir un efecto similar a las lisias variables de argumentos de C (conocidas con el nombre de "varargs" en C). Esto puede incluir uu nmero desconocido de argumentos que a su vez pueden ser de tipos desconocidos. Puesto que todas las clases se heredan en ltima instancia 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 siguiente modo: //: imtialization/VarArgs.java // ffso de la sintaxis de matriz para crear listas variables de argumentos, class A () public class VarArga { static void printArray(Object(] argsl { for(Object obj : args) System.out .print (obj -t- " *) ;

Podemos ver que la clusula de inicial i/acin de instancia: 326 Piensa en Java System.out.println();

327 Piensa en Java public static void main(String[] args) ( printArray(new Object[1 (new Integer(47), new Float(3.l4), new Double(11.11) 1; printArray (new Object 1] ("one" , MtwoM, "three }); printArray(new Object U (new A(), new A(), new A()));

) ) / Output: (Sample) 47 3.14 11.11 one two three A&la46e30 A<>3e25a5 A&19821f *///:-

Podemos ver que print( ) admite una matriz de tipo Object. y recorre la matriz utilizando la sintaxis foreach imprimiendo cada objeto. Las clases de la biblioteca estndar de Java generan una salida ms comprensible, pero los objetos de las clases que hemos creado aqui imprimen el nombre de la clase, seguido de un signo de y de una serie de dgitos hexadeci- malcs. Por tanto, el comportamiento predeterminado (si no se define un mtodo toString( ) para la clasc. como veremos posteriormente en el libro) consiste en imprimir el nombre de la clase y la direccin del objeto.

Es posible que se encuentre con cdigo anterior a Java SE5 escrito como el anterior para generar listas variables de argumentos. Sin embargo, en Java SE5, esta caracterstica largo tiempo demandada ha sido finalmente aadida, por lo que ahora podemos emplear puntos suspensivos para definir una lista variable de argumentos, como puede ver en printArray( ): //: nitialization/NewVarArgs.java // Uso de la sintaxis de matrices para crear listas variables de argumentos. public class NewVarArgs (

328 Piensa en Java static void printArray(Object... args) { for(Object obj : args) System.out.print(obj + " ) System.out .printlnO ;

public static void roain(String args) { // Admite elementos individuales: printArray(new Integer(47), new Float(3.14), new Double(11.11)); printArray(47, 3.14F. 11.11); pr intArray (" one ", " two", " three ") ; printArray (new A(), new A(), new AO); // O una matriz: printArrayMObject(])new Integer [] | 1 . 2, 3, 4 }>; printArray 0; // Se admite una lista vacia

) ) /* Output: (75% match) 47 3.14 11.11 47 3.14 11.11 one two three Albab50a Ac3c749 A*150bd4d 12 3 4 ///:-

Con varargs. ya no es necesario escribir explcitamente la sintaxis de la matriz: el compilador se encargar de completarla automticamente cuando especifiquemos varatgs, Seguimos obteniendo una matriz, lo cual es la razn deque print( ) siga pudiendo utilizar la sintaxis foreach para iterar a travs de la matriz. Sin embargo, se trata de algo ms que una simple conversin automtica entre una lista de elementos y una matriz. Observe la penltima linea del programa, en !a que una matriz de elementos Integer (creados con la caracterstica de conversin automtica) se proyecta sobre una matriz Object (para evitar que el compilador genere una advertencia) y se pasa a printArray ). Obviamente, el compilador determina que esto es ya una matriz, por lo que no realiza ninguna conversin con ella. De

329 Piensa en Java modo que, si tenemos un grupo de elementos, podemos pasarlos como una lista, y si ya tenemos una matriz, se aceptar esa matriz como lista variable de argumentos.

La ultima lnea del programa muestra que es posible pasar cero argumentos a una lista vararg. Esto resulta til cuando existen argumentos finales opcionales: //: initialization/OptionalTrailingArguments.java public class OptionalTrailingArguments ( static void fdnt required. String... trailing) { System.out.print{"required: " - required + " "); for(String s : trailing) System.out .print (s -f " ) System.out.printlni ) ;

public static void mam (String [) args) ( f(l, "one"); f(2, HtwoM, "three"); f (0) ;

} ) / Output: required: 1 one required: 2 two three required: 0 ///:-

Esto muestra tambin cmo se pueden utilizar varargs con un tipo especificado distinto de Object. Aqu, todos los varargs deben ser objetos String. Se puede utilizar cualquier tipo de argumentos en las listas varargst incluyendo tipos pnmitivos. El siguiente ejemplo tambin muestra que la lista vararg se transforma en una matriz y que, si no hay nada en la lista, se tratar como una matriz de tamao cero. //: mitialization/VararaType.java

330 Piensa en Java public class VarargType ( static void f(Character... args) { System.out.print(args.getClass0 ) ; System.out.println(M length " + args.length); static void gtint... args) { System.out.print(args.getClass()); System.out.println(M length M * args.length); public static void mam (String [ 3 args) ( fCa'); f ) ; g(l); g ) ; System.out.println(Hint[] : " + new int{0J.getClassO} ;

) ) /* Output; class [Ljava.lang.Character; length 1 class [L^ava.lang.Character; length 0 class [I length 1 class [I length 0 int [] : class [I *///:-

El mtodo getClass( ) es parte de Object. y lo analizaremos en detalle en el Capitulo 14. Informacin de tipos. 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 indica que se trata de una matriz del tipo situado a continuacin. La T indica una primitiva int; para comprobarlo, hemos creado una matnz de int en la ltima lnea y hemos impreso su tipo. Esto permite comprobar que la utilizacin de varargs no depende de la caracterstica de conversin automtica, sino que utiliza en la prctica los tipos primitivos.

331 Piensa en Java Sin embargo, las listas vararg funcionan perfectamente con la caracterstica de conversin automtica. Por ejemplo: public class AutoboxmgVarargs ( public static void f(Integer... args) ( for(Integer i : args) System.out .print (i + M M System.out .printlnO i public static void main{String[J args) ( f(new Integer(l), new Integer(2)); f<4, 5, 6, 7, 8 , 9); f{10, new Integer(11), 12);

) ) /* OUtpUt:
1

4 5 6 7 8 9 10 11 12 ///:-

Observe que se pueden mezclar los lipos en una misma lisia de argumentos, y que la caracterstica de conversin automtica promociona selectivamente los argumentos int a Integer

Las listas varar# complican el proceso de sobrecarga, aunque ste parezca suficientemente seguro a primera vista:

332 Piensa en Java //: initial2ation/0verloadingVararg3.java public class OverloadingVarargs ( static void f(Character... args) { System.out.pri nti"f i rst"); for(Character c : args) System.out.print(" " + c); System, out.println(J;

} static void f(Integer... args) ( System.out.print("second"); for(Integer i : args) System, out .print (H " * ); System.out.orintln();

} static void fiLong... args) { System.out.orintln("third");

) public static void main (String [J args { fl'a', b. rcM; f (1) ; f(2, 11; f (0) ; f(OL);

333 Piensa en Java //! f () ,* // No se compilar -- ambiguo

} | /* Output: first abe second i second 2 1 second O third *///:-

En cada caso, el compilador est utilizando la caracterstica de conversin automtica para determinar qu mtodo sobrecargado hay que utilizar, e invocar el mtodo que se ajuste de la forma ms especifica.

pero cuando se invoca f( ) sin argumentos, el compilador no tiene forma 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: //:

334 Piensa en Java initialization/OverloadingVarargs 2, java // {CompileTimeError) (Wont compile) public class Overloadmgvarargs2 { static void ffloat i, Character... args) { System.out.println("first");

} static void f(Character... args) ( } System.out.print("second" ) j

public static void main(String[] args) ( m, ' a ) ,* f('a\ 1 b') ; l

) ///:-

El marcador de comentario {CompileTimeError} excluye este archivo del proceso de construccin Ant del libro. Si lo compila a mano podr ser el mensaje de error: referente to f is ambiguous. both method ffjloat.java. tang. Character...) ln Overloading Varargs2 and mcthod fjava.ang. Character...) in Overloadinglarargs2 match

335 Piensa en Java St proporciona a ambos mtodos un argumento no -vararg. funcionar perfectamente: //j initialization/OverloadinaVarargs3.java public class Overloadir.gVarargs3 { static void f(float i, Character... args) ( System.out,println("firstM);

) static void fichar c, Character... args) ( System.out .println(f'second")

} public static void main(StringU args) (

til, 'a')l re*', 'b* > ;

} ) /* Output: Cirst

336 Piensa en Java 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 varaig de tipo String. Verifique que puede pasar una lista

separada por comas de objetos String o una matriz String[| a este mtodo.

Ejercicio 20: (I) Cree un mtodo main( ) que utilice varargs en lugar de la sintaxis mainl > normal. Imprima todos los

337 Piensa en Java elementos de la matriz args resultante. Pruebe el mtodo con diversos conjuntos de argumentos de linea de comandos. Tipos enumerados

338 Piensa en Java

Una adicin aparentemente poco importante en Java SF.5 es la palabra clave enum. que nos facilita mucho las cosas cuando necesitamos agrupar y utilizar un conjunto de tipos enumerados. En el pasado, nos veiamos forzados a crear un conjunto de valores enteros constantes, pero estos conjuntos de valores no suelen casar muy bien con los conjuntos que se necesitan definir y son. por tanto, ms arriesgados y difciles de utilizar. Los tipos enumerados representan una necesidad tan comn que C. C++ y diversos otros lenguajes siempre los han tenido. Antes de Java SE5. los programadores de Java estaban obligados a conocer muchos detalles v a tener mucho cuidado si queran emular apropiadamente el efecto de cnum. Ahora. Java dispone tambin de cnum y lo lia implementado de una manera mucho mas completa que la que podemos encontrar en C C++. He aqui un ejemplo simple: //: initialization/Spicmess.java public enum Spiciness { NCT, MILD, MEDIUM, HOT, FLAMING ) m-.-

Esto crea un tipo enumerado denominado Spiciness con cinco valores nominados. Puesto que las instancias de los tipos enumerados 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 cnum. creamos una referencia de ese tipo y la asignamos una instancia: //: initialization/SimpleEnumUse.ja va public class SimpleEnumUse { public static void main(StringU args) { Spiciness howHot = Spiciness.MEDIUM; System.out.DrintlnlhowHot);

5 Imetalizacin y limpieza 339

} / Output: MEDIUM

*///:-

El compilador aade automticamente una sene de caractersticas tiles cuando creamos un tipo cnum Por ejemplo, crea un mtodo tString( ) para que podamos visualizar fcilmente el nombre de una instancia enum. y sa es precisamente la forma en que la instruccin de impresin anterior nos ha permitido generar la salida del programa. El compilador tambin crea un mtodo ordinaK ) 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: //: initialization/EnumOrder.java public class EnumOrder ( public static void raain(String[] args) { for(Spiciness s : Spiciness.vales()) System.out .println (s + ", ordinal " + s. ordinal O;

} ) /* Output: NOT, ordinal 0 MILD, ordinal 1 MEDIUM, ordinal 2 HOT, ordinal 3 FLAMING, ordinal 4 *///:-

340 Piensa en Java

Aunque los tipos enumerados enum parecen ser un nuevo tipo de datos, esta palabra clave slo provoca que el compilador realice una serie de actividades 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: //: initialization/Burrito.java public class Burrito ( Spiciness degree; public Burrito(Spiciness degree) ( this.degree = degree;) public void describe O ( System.out.print<"This burrito is switch(degree) { case NOT: System.out.printlnl"not spicy at all-"); break ,* case MILD: case MEDIUM: System.out. pnntlnC'a little hot."); break; case KOT: case FLAMING: default: System.out.println("maybe too hot."),*

public static void main(String[] args) { Burrito plain = new Burrito(Spiciness.NOT), greenChile = new Burrito(Spiciness.MEDIUM),

5 Imetalizacin y limpieza 341

)'

jalapeno = new Burrito(Spiciness.HOT>; plain.describe()? greenChile.describe(); j alapeno.describe(>;

) /* Output: This burrito is not spicy at all. This burrito is a little hot. This burrito is maybe too hot.

*///:-

Puesto que una instruccin switch se emplea para seleccionar dentro de un conjunto limitado de posibilidades, se complementa perfectamente con un tipo enum Observe cmo los nombres enuin 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 forma 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 construir un tipo enumerado equivalente que se

342 Piensa en Java

pudiera emplear de forma segura.

Este breve anlisis es suficiente para poder comprender y utilizar los tipos enumerados bsicos, pero examinaremos estos tipos enumerados ms profundamente en el Capitulo 10, Tipos enumerados.

Ejercicio 21: (l) Cree un tipo enunt con los seis tipos de billetes de euro de menor valor. Recorra en bucle los valores

utilizando values( ) e imprima cada valor y su orden correspondiente con urdiial( ).

Ejercicio 22: (2)Escribauna instruccin switch para el tipo enum del ejercicio anterior. En cada case, imprima una des

cripcin de ese billete concreto

5 Imetalizacin y limpieza 343

Resumen

Este aparentemente elaborado mecanismo de inicializacin. el constructor, nos indica la importancia critica que las tareas de inicializacin tienen dentro del lenguaje. Cuando Bjame Stroustrup. el inv entor de C++. estaba diseando 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 vana- bles es responsable de una parte significativa de los problemas de programacin. Este tipo de errores son difciles de localizar, y lo mismo cabria decir de las tareas de limpieza inapropiadas. Puesto que los constructores nos permiten garantizar una inicializacin y limpieza adecuadas (el compilador no permitir crear un objeto sin las apropiadas llamadas a un constructor), la seguridad y el control estn garantizados.

En C-H-. la destruccin tambin es muy importante, porque los objetos creados con new deben ser destruidos explcitamente. 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 similar al de los destructores, el mecanismo de depuracin de memoria de Java simplifica enormemente la programacin y mejora tambin en gran medida la seguridad de la gestin de memoria. Algunos depuradores de memoria pueden incluso limpiar otros recursos, como los recursos grficos y los descriptores de archivos Sin embargo, el depurador de memoria hace que se incremente el coste de ejecucin, resultando difcil evaluar adecuadamente ese coste, debido a la lentitud que histricamente han tenido los intrpretes de Java Aunque a lo largo del tiempo se ha mejorado significativamente la velocidad de Java, un problema de la velocidad ha supuesto un obstculo a la hora de adoptar este lenguaje en ciertos tipos de problemas de programacin.

Debido a que est garantizado 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 mecanismos de composicum o de herencia. tambin se mantiene la garanta de construccin, siendo necesaria una cierta sintaxis adicional para soportar este mecanismo. Hablaremos de la composicin, de la herencia y del efecto que ambos mecanismos tienen en los constructores en prximos captulos.

Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico Pie Thtnktng ni Java Annonucd Sohiuon Guale, que estd disponible paru la venta en wn-wAfinttl7nv.net.Control de acceso

El control de acceso (u ocultacin Je la implementation) 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 terminado hasta despus de haber sido reescrito, a menudo muchas veces. Si dejamos un fragmento de codigo encima de la mesa durante un tiempo y luego volvemos a l, lo ms probable es que veamos una forma mucho mejor de escribirlo. sta es una de las principales motivaciones para el trabajo de rediseo, que consiste en reescribir cdigo que va 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 (j)ro- gramadores de cliente) que dependen de que ciertos aspectos de nuestro cdigo continen siendo iguales. Por tanto, nosotros queremos modificar el cdigo, pero ellos quieren que siga siendo igual. Es por eso que una de las principales consideraciones en el diseo orientado a objetos es la de separar las cosas que cambian de las cosas que permanecen.

listo es particularmente importante para las bibliotecas Los consumidores de una biblioteca deben poder confiar en el elemento 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 reali/ar 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 situacin 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 clientes? 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 cdigo 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 permiten al creador de la biblioteca decu que 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 tienen una palabra clave asociada) y private. Leyendo el prrafo anterior, podramos pensar que. como diseadores de bibliotecas, conviene mantener todas las cosas lo ms privadas posible y exponer slo aquellos mtodos que queramos que el programa cliente utilice. Esto es cierto, aunque 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 capitulo, estas personas estarn 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

empaqueuir los componentes para formar una unidad de biblioteca cohesionada. Este aspecto se controla mediante la palabra clave package en Java, y los espccifi- cadores de acceso se vern afectados por el hecho de que una clase se encuentra en el mismo paquete o en otro paquete dis tinto. Por tanto, para comenzar este captulo, veamos primero cmo se incluyen componentes de biblioteca en los paquetes Con eso. seremos capaces de entender completamente el significado de los especifieadores de acceso.

package: la unidad de biblioteca

Un paquete contiene un grupo de clases, organizadas conjuntamente dentro de un mismo espado de nombres.

Por ejemplo, existe una biblioteca de utilidad que forma parte de la distribucin estndar de Java, organizada bajo el espacio de nombres java.util. Una de las clases de java.til se denomina ArrayList. Una forma de utilizar un objeto ArrayList consiste en especificar el nombre completo java.til.ArrayList //: access/FullQualification.iava public class FullQualification ( public static void mainlStrmgl] args) ( java.til.ArrayList list = new java.util.ArrayListt;

) ) ///-

Sin embargo, este procedimiento se vuelve rpidamente tedioso, por lo que suele ser ms cmodo

utilizar en su lugar la palabra clave import. Si queremos importar una nica clase, podemos indicar esa clase en la instruccin import //: access/Singlelmport.java import java.util.ArrayList? public class Sinalelmport ( public static void main(StringJ args) { ArrayList lxst = new java.til.ArrayListO ;

) ) Uh-

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 utilizar tal como hemos visto en los ejemplos del libro. import j ava.til.r

La razn para efectuar estas importaciones es proporcionar un mecanismo para gestionar los espacios de nombres. Los nombres 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 ) 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 posibilidad 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 identtficadores univoca 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 diseados para uso local, por lo que no nos hemos preocupado de los nombres de paquete. Lo cierto es que estos ejemplos si estaban incluidos en paquetes: el paquete predeterminado o innominado Ciertamente, sta es una opcin viable y trataremos de utilizarla siempre que sea posible en el resto del libro, en aras de la simplicidad. Sin embargo, si lo que pretendemos 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 anidad de compilacin (y tambin, en ocasiones, unidad de traduccin). Cada unidad de compilacin debe tener un nombre que termine en .java, y dentro de la unidad de compilacin puede haber una clase puhlic que debe tener el mismo nombre del archivo (incluyendo 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 compilacin; en caso contrario, el compilador se quejar. Si existen clases adicionales en esa unidad de compilacin, estarn ocultas para el mundo exterior al paquete, porque no son puhlic. y simplemente se tratar de clases ''soporte" para la clase puhlic principal Organizacin dei cdigo

Cuando se compila un archivo java, se obtiene un archivo de salida para cada dase del archivo .java Cada archivo de salida tiene el nombre de una de las clases del archivo .java, pero con la extensin .class. De este modo, podemos llegar a obtener un gran numero de archivos .class a partir de un nmero pequeo de archivos .java. Si el lector ha programado anteriormente en algn lenguaje compilado, estar acostumbrado al hecho de que el compilador genere algn formato intermedio (normalmente un archivo obj") que luego se empaqueta con otros del mismo tipo utilizando un montador (para crear un archivo ejecutable) o un gestor de biblioteca (para crear una biblioteca). sta no es la forma de funcionar de Java. Un programa funcional es un conjunto de archivos .class. que se puede empaquetar y comprimir en un archivo JAR (Java ARrchive). utilizando el archivador jar de Java. El intrprete de Java es responsable de localizar, cargar e interpretar2 estos archivos.

Una biblioteca es un grupo de estos archivos de clase. Cada archivo fuente suele tener una clase public y un nmero arbitrario de clases no pblicas, por lo que no slo existe un componente public para cada archivo fuente. Si queremos especificar que todos estos componentes (cada uno con sus propios archivos .java y .class separados) deben agruparse, podemos utilizar la palabra clave package.

Si usamos una instruccin package. debe aparecer como la primera linea no de comentario en el archivo. Cuando escribimos: package access;

estamos indicando que esta unidad de compilacin es parte de una biblioteca denominada access. Dicho de otro modo, estamos especificando que el nombre de clase pblica situado dentro de esta unidad de compilacin debe integrarse bajo el paraguas correspondiente al nombre access. de modo que cualquiera que quiera usar ese nombre deber especificarlo por completo o utilizar la palabra clave import en combinacin con access. utilizando las opciones que ya hemos mencionado anteriormente (observe que el convenio que se emplea para los nombres de paquetes Java consiste en emplear letras minsculas. incluso para las palabras intermedias).

Por ejemplo, suponga que el nombre de un archivo 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 minsculas): //: access/mypackage/MyClass .java package access.mypackage; public class MyClass (

// ...

>

///t-

Ahora, si alguien quiere utilizar MyClass o cualquiera otra de las clases pblicas de access. deber emplear la palabra clave import para que estn disponibles esos nombres definidos en el paquete access. La alternativa consiste en especificar el nombre completamente cualificado: //: access/QualifedMyClass.java publxc class QuaiifiedMyClass ( public static void mainString{1 args) ( access.mypackage.MyClass m = new access.mypackage.MyClass();

} ///14No hay ninguna caraeienstica de Java que nos obligue a utilizar un interprete Fxisien compiladores Java de cdigo nauvo que generan un nico archivo ejecutable.
14

La palabra clave import permite que este ejemplo tenga un aspecto mucho ms simple. //: access/ImportedMyClass.J ava import acces6.mypackage.*; public class ImportedMyClass ( publc static void mainStringH args) { MyClass TU - new MyClaso () ;

) ///:-

Merece la pena tener presente que lo que las palabras clave package c import nos permiten hacer, como diseadores de bibliotecas, es dividir el espacio de nombres global nico, para que los nombres no colisionen, independientemente de cuantas 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 sera colocar todos los archivos .class correspondientes a un paquete concreto dentro de un mismo directorio; es decir, aprovechar la estructura de archivos jerrquica del sistema operativo. 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 forma cuando ms adelante presentemos la utilidad jar.

Recopilar los archivos de un paquete dentro de un mismo suhdirectono resuelve tambin otros dos problemas; la creacin de nombres de paquete unvocos y la localizacin de aquellas clases que puedan estar perdidas en algn lugar de la estructura 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 nombre** de dominio Internet sean unvocos. seguimos este convenio nuestro nombre de paquete ser unvoco y nunca se producir una colisin de nombres (es decir, salvo que perdamos el derecho a utilizar el nombre de dominio y la persona que lo comience a utilizar se dedique a escribir cdigo Java con los mismos nombres de ruta que usted utiliz). Por supuesto, si no disponemos de nuestro propio nombre de dominio, deberemos concebir 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 pequeo esfuerzo para obtener un nombre de dominio.

La segunda parte de esta solucin 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 directorio en el que esc archivo .class resida.

El intrprete Java acta de la forma siguiente. Primero, localiza la variable de entorno CLASSPATH 15 (que se fija a travs del sistema operativo y en ocasiones es definida por el programa de instalacin que instala Java con una herramienta basada en Java en la mquina). CLASSPATH contiene uno o ms directorios que se utilizan como races para buscar los archivos .class Comenzando por esa raiz. el
15

Cumulo nos refiramos a la variable de entumo, utilizaremos letras mayscula* (CLASSPATH).

intrprete toma el nombre de paquete y sustituye cada punto por una barra inclinada para generar un nombre de ruta a partir de la raz CLASSPATH (por lo que el paquete package fno.har.haz se convertira 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 archivo .class que tenga un nombre que se corresponda con la clase que se est intentando crear (tambin 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 MindVievv.net. Inviniendo ste y pasndolo a minsculas, net.mindvievv establece mi nombre global univoco para mis clases (antiguamente, las extensiones com. edu. org. etc . estaban en may sculas en los paquetes Java, pero esto se modific en Java 2 para que todo el nombre del paquete estuviera en minsculas) Puedo subdiv idir 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 .mir.dview.simple;

Ahora, este nombre de paquete puede utilizarse como espacio de nombres paraguas para los siguientes dos archivos: / /: net/mindview/flimple/Vector, java // Creacin de un paquete, package r.et. mindview. simple ; public class Vector { public Vector(} ( System.out.println("net.mindview.simple.Vector");

} ///i-

Como hemos mencionado antes, la instruccin package debe ser la primera linea de no comentario dentro del cdigo del archivo. El segundo archivo tiene un aspecto parecido: //: net/mindview/simpie/List.java // Creacin de un paquete, package net.mindview.simple; public class List ( public List() { System.out.println("net.mindview.simple.List);

} ///:~

Ambos archivos se ubicarn en el siguiente subdirectorio de mi sistema: C:\DOC\JavaT\net\mindview\simple

Observe que la primera linea de comentario en cada archivo del libro ndica la ubicacin del directorio donde se encuentra esc archivo 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 parte de la ruta De esa parte se encarga la variable de entorno CLASSPATH, que en mi mquina es: CLASSPATH. ; D: \ JA VAN LIB; C: \DOC WavaT

Podemos ver que la variable de entorno CLASSPATH puede contener una serie de rutas de bsqueda alternativas.

Sin embargo, existe una variacin cuando se usan archivos JAR. Es necesario poner el nombre real del archivo JAR en la variable de ruta, y no simplemente la ruta donde est ubicado. Asi. para un archivo JAR denominado grape.jar. la variable de ruta incluira: CLASSPATH.;D:\JAVA\LIB;C:\flavors\grape.jar

L na vez que la variable de ruia de busqueda se ha configurado apropiadamente, el siguiente archivo puede ubicarse en cualquier directorio //: access/LibTest.}ava // Utiliza la biblioteca, import

net.mindview.simple. public class LibTest ( public static void main(String[J args) { Vector v = new Vector O; List 1 new List (} ;

i } / Output: net.mindview.simple.Vector net.mindview.simple.List *///:-

Cuando el compilador se encuentra con la instruccin import correspondiente a la biblioteca simple, comienza a explorar todos los directorios especificados por CLASSPATH. en busca del subdirectorio netfmindview/simple. y luego busca los archivos compilados con los nombres apropiados (Vector.class para Vector y List.class para List), Observe que tanto las dos clases como los mtodos deseados de Vector y List tienen que ser de tipo public

La configuracin de CLASSPATH resultaba 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 versiones ms recientes de Java se comporte de forma algo mas inteligente. Se encontrar, cuando lo instale, que aunque no configure la variable CLASSPATH. podra compilar y ejecutar programas Java bsicos Sin embargo, para compilar y ejecutar el paquete de cdigo fuente de este libro (disponible en uvnt \mJl lewnei), necesitara aadir a la variable CLASSPATH el directorio base del rbol de cdigo.

Ejercicio 1: paquete. Colisiones

(I) Cree una clase dentro de un paquete. Cree una instancia de esa clase fuera de dicho

Qu sucede si se importan dos bibliotecas mediante '** y ambas incluyen los mismos nombres? Por ejemplo, suponga que un programa hace esto: import net.mindvlew.simple./ import java.til;

Puesto que java.til.* 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 colisin, no pasa nada. Resulta bastante conveniente que esto sea asi. ya que de otro modo nos veramos forzados a escribir un montn de cosas para evitar colisiones que realmente nunca iban a suceder.

La colisin si que se producir si ahora intentamos construir un Vector: Vector v new Vector(); ^A qu clase Vector se refiere esta lnea? I I compilador no puede saberlo, como tampoco puede saberlo el lector. Asi que el compilador generara un error y nos obligar a ser ms explcitos Si queremos utilizar el Vector Java estndar, por ejemplo. deberemos escribir: java.til.Vector v = new java.til.Vector I);

Puesto que esto (junto 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.til.*, a menos que vayamos a utilizar alguna otra clase definida en java.til

Alternativamente, podemos utilizar la instruccin de importacin de una nica clase para prevenir las colisiones, siempre y cuando no empleemos los dos nombres que entran en colisin dentro de un mismo programa (en cuyo caso, no tendremos ms remedio que especificar completamente los nombres).

Ejercicio 2: I) Tome los fragmentos de cdigo de esta seccin y transfrmelos 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 herramientas, para reducir o eliminar la escritura de cdigo duplicado Considere, por ejemplo, el alias que hemos estado utilizando para Sy$tem.out.println( ). con el fin de reducir la cantidad de informacin tecleada Ksto puede ser pane de una clase denominada Print. de modo que dispondramos de una instruccin esttica de impresin bastante ms legible //: net/mindview/ut11/Print.java

// Mtodos de impresin que pueden usarse sin // cualificadores, empleando importaciones estticas de Java SE5: package net .raindvew. ut 1 ,* import java.io.*; public class Print { // Imprimir con una nueva linea: public static void print(Object obj) ( ) System.out.printlnlobj)

// Imprimir una nueva linea sola: public static void print<) ( System.out.println(); 1 H Imprimir sin salto de lnea: public static void prmtnb(Object obj) ( System.out.print(ob^ ) : j i f El nuevo printf() de Java SE5 (de C): public static PrmtStream printf(String format, Object... argsI { return System.out.printf(format, args};

) ///:-

Podemos utilizar estas abreviaturas de impresin para imprimir cualquier cosa, bien con la insercin de una nueva linea iprini( )) o sin una nueva linea (printnh( )).

Como habr adivinado, este archivo deber estar ubicado en un directorio que comience en una de las ubicaciones definidas en CLASSPATH y que luego contine con nct/mindvien Despus de compilar, los mtodos static print( ) y printnb( ) pueden emplearse en cualquier lugar del sistema utilizando una instruccin import static: //: access/PrintTest.java Usa los mtodos estticos de impresin de Print.java, import static net.mindview.util.Print.;
II

public class PrintTest ( public static void main(String[] args) | print("Available from now oni"); print (100) ,* print(100L); print(3.14159) ; I ) / Output: Available from now onl 100 100 3.14159

///-

Un segundo componente de esta biblioteca pueden ser los mtodos range( )t que hemos presentado en el Capitulo 4, Control Je la ejecucin, y que permiten el uso de la sintaxis forvach para secuencias simples de enteros:

/: net/mindview/util/Range.java // Mtodos de creacin de matrices que se pueden usar sin U cualificadores, con importaciones estticas Java SE5; package net.mindview.til; public class Range ( // Generar una secuencia I0..n! public static intU range(int n) ( int (I result = new int [n] for(int i * 0; i < n; i++) result[i] = 1; return result, i // Generar una secuencia (start..end) public static int[] range(int start, int end> { int sz = end - start; int[] result * new int Isz] j for (int i = 0; i < sz; i++) result[i] * start i;

return resulti

// Generar una secuencia [start..end> con incremento igual a step publc static int U range{int start, int end, int step) ( nt sz (end - start!/step; int[] result = new int fez]; for(int i * 0 i < sz; i++)

resuli[i] = start (i * step); retum result;

} ///:-

A partir de ahora, cuando desarrolle cualquier nueva utilidad que le parezca interesante la podr aadir a su propia biblioteca. A lo largo del libro podra ver que cmo aadiremos ms componentes a la biblioteca nct.mindvicwutil Utilizacin de importaciones para modificar el comportamiento

Una caracterstica que se echa en taita en Java es la compilacin condicional que existe en C y que permite 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 mterplataforma: dependiendo de la plataforma de destino se compilan diferentes partes del cdigo. Puesto que Java est pensado para ser automticamente un lenguaje interplataforma 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 conseguirci mismo efecto modificando el paquete que se importe dentro de nuestro programa, con el fin de conmutar entre el codigo 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 debugolT. que contengan una clase idntica con un mtodo debug( ). La

primera versin debe mostrar su argumento String en la consola, mientras que la ?egunda no debe hacer nada. Utilice una lnea static import para importar la clase en un programa de prueba y demuestre el efecto de la compilacin condicional. Un consejo sobre los nombres de paquete

Merece la pena recordar que cada vez que creamos un paquete, estamos especificando implicitamente 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 partir de la ruta indicada en CLASSPATTL Experimentar con la palabra clave package 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 funciona, si lo hace, ya sabe dnde est el problema.

Observe que el cdigo compilado se coloca a menudo en un directorio distinto de aquel en el que reside

el cdigo fuente, pero la ana al cdigo compilado deber seguir siendo localizablc por la JVM utilizando la variable CLASSPATH. Especificadores de acceso Java

Los especificadores de acceso Ja\a puhlic, prntectcd y prvate se colocan delante de cada definicin decada miembro de la clase, ya sea ste un campo o un mtodo. Cada espeeifteador de acceso slo controla el acceso para esa definicin concreta.

Si no proporciona un especificador de acceso, querr decir que ese miembro tiene acceso de paquete. Por tanto, de una forma u otra, todo tiene asociado algn tipo de control de acceso. En las secciones siguientes, vamos a analizar los diversos tipos de acceso. Acceso de paquete

En los ejemplos de los captulos anteriores no hemos utilizado especiPicadores de acceso. El acceso predeterminado no nene asociada ninguna palabra clave, pero comnmente se hace referencia a l como acceso de paquete (y tambin, en ocasiones, "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 prvate. Puesto que cada unidad de compilacin (cada archivo) slo puede pertenecer a un mismo paquete, todas las clases dentro de una misma unidad de compilacin 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 si. Cuando se colocan las clases juntas en un paquete, garantizando as el acceso mutuo a sus miembros 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 arbitraria. pero en Java nos vemos impelidos a organizaras de una forma 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 miembros prvate de una determinada clase. La nica forma 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 acceso. y colocar las otras clases que deban acceder a l dentro del mismo paquete. Entonces, las restantes clases del paquete podran acceder a ese miembro.

3.

( orno veremos en el Capitulo 7. Reutitizacin de clases, cuando se introduce la herencia, una

clase heredada puede acceder tanto a los miembros protected como a los miembros public (pero no a los miembros prvate). 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 espee i fie ador de acceso protected.

4.

Proporcionar mtodos de acceso minadores (tambin denominados mtodos get set") que permitan leer y cambiar el valor ste es el enfoque ms civilizado en trminos de programacin orientada a objetos, y resulta fundamental en JavaBeans. como podr ver en el Capitulo 22. Interfaces grficas de usuario.

public: acceso de interfaz

Cuando se utiliza la palabra clave public. esta quiere decir que la declaracin de miembros situada inmediatamente a continuacin suya esui disponible para todo el mundo, y en particular para el programa cliente que utilice la biblioteca. Suponga que definimos un paquete dessert que contiene la siguiente unidad de compilacin. //< access/dessert/Cookie. lava i ) Crea una biblioteca, package accesa.dessert; public clase Cookie ( public Cookie() j System.out.printlnI"Cookie constructor"!j

) void bite<) ( System.out.printlnCbite") }

} ///:-

Recuerde que el archivo de clase producido por C'ookie.java debe residir en un subdirectorio denominado dessert, dentro de un directorio access (que hace referencia al Capitulo 6, Contml 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 examinara 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.

Si ahora creamos un programa que usa Cookie: //: access/Dinner.ja va // Usa la biblioteca, import access.dessert. ; public class Dinner { public static void mam {String [] argsj ( Cookie x = new Cookie () ; //i x.bite); // No puede acceder

) } } * Output t Cookie constructor *///:-

podemos crear un objeto Cookie. dado que su constructor es public y que la clase tambin es public (ms adelante, profundizaremos ms en el concepto de clase public). Sin embargo, el miembro bite( ) es inaccesible desde de Dinncr.java ya que bite( ) slo proporciona acceso dentro del paquete dessert.

asi que el compilador nos impedir utilizarlo. El paquete predeterminado

Puede que le sorprenda descubrir que el siguiente cdigo si que puede compilarse, a pesar de que parece que no cumple con las reglas: //: access/Cake.java // Accede a una clase en una unidad de compilacin separada. class Cake { public static void main (String [] args) ( Pie x * new Pie(J; x.f H ;

) } / * Output: Pie.fi)

*///:-

En un segundo archivo del mismo directorio tenemos: //: access/Pie.jav a J t La otra clase. class Pie ( void () ( System.out-println(Pie.fO">j }

) //>*-

Inicialmente, cabra pensar que estos dos archivos no tienen nada que ver entre si. a pesar de lo cual Cake es capaz de crear un objeto Pie y de invocar su mtodo f( ) (observe que debe tener en su variable CLASSPATH para que los archivos se compilen). 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 pane 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 upo como si fueran implcitamente parte del paquete predeterminado HM de ese directorio, y por tanto proporciona acceso de paquete a todos los restantes archivos situados en ese directorio. prvate: no lo toque!

La palabra clave prvate significa que nadie puede acceder a ese miembro salvo la propia clase que lo contiene, utilizando para el acceso los propios mtodos de la clase. F.I 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 si, por lo que prvate permite modificar libremente ese miembro sin preocuparse de si afectar a otras clases del mismo paquete.

El acceso de paquete predeterminado 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 conveniente. ya que el acceso predeterminado es el que normalmente se utiliza (y el que se obtiene si nos olvidamos de aadir especificadores de control de acceso). Por tanto, lo 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 palabra clave prvate no se utiliza muy a menudo, ya que se pueden realizar los diseos sin ella. Sin embargo, la realidad es que el uso coherente de prvate tiene una gran importancia, especialmente en el caso de la programacin multihebra (como veremos en el Captulo 21, Concurrencia).

lie aqu un ejemplo del uso de prvate: . access/IceCream.java //

Ilustra la palabra clave prvate". class Sundae { private Sundae() {} static Sundae makeASundae{) ( return new Sundae( ) ; ) l public class IceCream ( public static void main(String[] args) ( //i Sundae x *= new Sundae (l ; Sundae x = Sundae .makeASundae 0 ;

} ///:-

Este ejemplo nos permite ver un caso en el que prvate 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 ellos). En el ejemplo anterior, 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.16

Existe otro efecto en c*te caso: puesto que el nico coastruclor definido es el predeterminado y este se ha definido como prvate, se impedir que nadie herede esta clase {lo cual es un lema del que hablaremos ms adelante)
16

Cualquier mtodo del que estemos seguros de que slo acta como mtodo auxiliar de la clase puede ser definido como privado para garantizar que no lo utilicemos accidentalmente en ningn otro lugar del paquete, lo que nos impedira modificar o eliminar 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 habitual 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 prvate en una clase no quiere decir que algn otro objeto no pueda tener una referencia de tipo public al mismo objeto (consulte 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 especifcador 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 llegar al captulo dedicado a la herencia (el Captulo 7. Reutilizacin ele clases). Pero para ser exhaustivos, hemos incluido aqu una breve descripcin y un ejemplo de uso de protected.

La palabra clave protected trata con un concepto denominado herencia, que toma una clase existente (a la que denominaremos clase base) y aade nuevos miembros a esa clase sin tocar la clase existente. Tambin se puede modificar el comportamiento de los miembros existentes en la clase. Para heredar de una clase, tenemos que especificar que nuestra nueva clase amplia (extends) una clase existente, como en la siguiente lnea: class Foo extends Bar {

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 resiantes clases del mismo paquete podrn acceder a los elementos protegidos.

Si volvemos al archivo Cookie.java. la siguiente clase no puede invocar el miembro bite( ) que tiene acceso de paquete: //: access/ChocolateChip.java // No se puede usar un miembro con acceso de paquete desde otro paquete, impon access. dessert. * ; public class ChocolateChip extends Cookie | public ChocolateChip(I ( System.out .println (* ChocolateChip constructor**);

) public vod chompO ( //! biteO; // No se puede acceder a bite

) public static void mainlString[] argsj ( ChocolateChip x * new ChocolateChipO; x.chomp(J;

) ) / 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 Cookie Pero como blte( ) tiene acceso de paquete y est situado en un paquete distinto, 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: //: access/cookie2/Cookie.java package access.cookie2; public class Cookie { public Cookieo { SysLem.out.printlnl"Cookie constructor");

) protected void biteO ( Svstem.out.printlnI"bite" ) ;

) i m-

ahora bite( ) estara disponible para toda aquella clase que herede de Cookie: : access/Chocc-lateChip2. java import access.ccokie2. public class ChocolateChip2 extends Cookie ( public ChocolateChipZ() ( System.out.Drintln("ChocclateChip2 constructor);

) public void chompO ( bite() / } // Mtodo protegido public static vod mainfStringH args) { ChocolateChip2 x = new ChocolateChip20; x.chomp();

} ] /* Output: Cookie constructor ChocolateChip2 constructor bite *///:-

Observe que. aunque bitc( ) tambin tiene acceso de paquete, no es de tipo public

Ejercicio 4: (2) Demuestre que los mtodos protegidos (protcctcd) tienen acceso de paquete pero no son pblicos

Ejercicio 5:(2) Cree una clase con campos y mtodos de tipo public. prvate, prntected y con acceso de paquete. Cree

un objeto de esa clase y vea los tipos de mensajes de compilacin que se obtienen cuando se intenta acceder a lodos los miembros de la clase Tenga en cuenta que las clases que se encuentran en el mismo directorio forman parte del paquete predeterminado.

Ejercicio 6: (!) 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 <ic 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.M3 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 preocupamos de que los programas cliente utilicen accidentalmente esos mecanismos internos com) 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 interfaz 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, situar los miembros pblicos al principio. 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 el 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 forman parte de la implementacin iniema. //: access/Organi ava public

zedByAccess.j

cla9s QrganizedByAccess ( private int i; // ...

} ///:Sin embargo, mucha genie utili/ la palabra cncapNU tac iti para retemse en exclusiva a la insultacin de la implementacin.
5

Esto slo facilita parcialmente la lectura, porque la interfaz y la implementacin siguen estando mezcladas En otras palabras. el lector seguir pudiendo ver el cdigo fuente (la implementacin). porque est incluido en la clase. Adems, el sistema 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, cu realidad, trabajo del explorador de clases, que es una herramienta que se encarga de examinar lodas las clases disponibles y mostramos

lo que se puede hacer con ellas (es decir, qu miembros estn disponibles) en una forma apropiuda. En Java, visualizar la documentacin del JDK 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 determinar qu clases de una biblioteca estarn disponibles para los usuarios de esa biblioteca. Si queremos que una cierta clase est disponible para un programa cliente, tendremos que utilizar la palabra clave public 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 cliente podr acceder a Widget mediante la instruccin import access.Widget/

import access.;

Sin embargo, existen una serie de restricciones adicionales:

1.

Slo puede haber una clase public por cada unidad de compilacin (archivo). La idea es que cada unidad de compilacin tiene una nica interfaz pblica representada por esa clase pblica. Adems de esa clase, puede lener tantas 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 public debe corresponderse exactamente con el nombre del archivo que contiene dicha unidad de compilacin, incluyendo el uso de maysculas y minsculas. De modo que para Widget. el nombre del archivo deber ser Widget.java y no uidget.java ni WIDGET.java. De nuevo, obtendremos un error en tiempo de compilacin si los nombres no concuerdan.

3.

Resulta posible, aunque no muy normal, tener una unidad de compilacin sin ninguna clase public. Ln 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 Widget 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, eliminando 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 conseguir 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: (I) 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 prvate (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 normalmente dentro del paquete, slo har falta definir como pblicos los mtodos de esa clase si nos vemos obligados a ello; adems. en esos casos, el compilador ya se encargara de informamos.

Observe que una clase no puede ser prvate (ya que eso hara que fuera inaccesible para todo el inundo

salvo para la propia clase) ni protected.6 Asi que slo tenemos dos opciones para especificar el acceso a una clase: acceso de paquete o public Si no queremos que nadie tenga acceso a la clase, podemos definir todos los constructores como privados, impidiendo 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: / / : access/Lunch.java // Ilustra los especiicadores de acceso a clases. Define una // clase como privada con constructores privados: class Soupl { prvate SouplO {) // (1) Permite la creacin a travs de un mtodo esttico: public static Soupl makeSoupO ( retum new Soupl () ,17

} class 5oup2 f prvate Soup2() (} // (2) Crea un objeto esttico y devuelve una referencia // cuando se solicita.(El patrn "Singleton"): private static Soup2 psl = new Soup2O; public static Soup2 access) ( retum psl;

En realidad, una dase interna puede ser privada o protegida, pero se traa de un caso opectal Hablaremos de ello en el Capitulo 10, Clases internas
17

) public void f C) (} i // Slo se permite una clase pblica por archivo: public class Lunch ( void testPrivate(> { // |No se puede hacer! Constructor privado: ) / / i Soupl soup = new Soupl O

void testStaticO ( SOUDI soup = Soupl.makeSouD();

> void testSingleton0 { Soup2. access ()*{)'?

) ///:-

Hasia ahora, la mayora de los mtodos devolvan void o un tipo primitivo, por lo que la definicin: public static Soupl makeSoupO { retum new Soupl () ; i

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 devuelve nada. Pero tambin podemos devolver una referencia a un objeto, que es lo que estamos haciendo aqui. Este mtodo devuelve una referencia a un objeto de la clase Soupl.

Las clases Soupl y Soup2 muestran cmo impedir la creacin directa de objetos de una clase definiendo lodos los constructores como privados. Recuerde que, si no creamos explcitamente al menos un constructor, se crear automticamente el constructor predeterminado (un constructor sin argumentos). Escribiendo el constructor predeterminado, garantizamos que no sea escrito automticamente. Definindolo como privado nadie podr crear un objeto de esa clase. Pero entonces, cmo podr alguien usar esta clase? En el ejemplo anterior se muestran dos opciones l n Soup 1. se crea un mtodo static que crea un nuevo objeto Soupl y devuelve una referencia al mismo, listo puede ser til si queremos realizar algunas operaciones adicionales con el objeto Soupl antes de devolverlo, o si queremos llevar 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 patrn de diseo, de lo que se habla en Thinking in Pattems fwith Java) en wwH.MindHeu net. Este patrn concreto se denomina Solitario (singieton), porque slo permite crear un 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 so puede acceder a l salvo a travs del mtodo pblico acccss( ).

Como hemos mencionado anteriormente, si no utilizamos 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 objetos de esa clase desde fuera del paquete (recuerde que todos los archivos de un mismo directorio que no tengan declaraciones package explcitas forman parte, implcitamente, del paquete predeterminado de dicho directorio). Sin embargo, si un miembro esttico de esa clase es de tipo public. el programa cliente podr seguir accediendo a ese miembro esttico, an cuando no podr crear un objeto de esa clase.

Ejercicio 8: (4) Siguiendo la forma del ejemplo Lunch.java. cree una clase denominada ConnectionManager que

gestione una matriz fija de objetos Connection. El programa cliente no debe poder crear explcitamente objetos Connection. sino que slo debe poder obtenerlos a travs de un mtodo esttico de ConnectionManacer. Cuando ConnectionManager se quede sin objetos, devolver una referencia nuil Pruebe las clases con un programa main( ).

Ejercicio 9: (2)Creeel siguiente archivo en el directorio access/local (dentro de su ruta CLASSPATH): // access/local/PackagedClas s.java package access.local; ciass PackagedClass ( public PackagedClass0 { System.out.printlnt"Creating a oackaged classJ;

A continuacin cree el siguiente archivo en un directorio distinto de access/local: // access/foreign/Foreign.ja va packaqe access.t oreign; import access.local.*; public class Foieign { public static veid mam<String 11 args) { PackagedClass pe = new PackagedClass (.);

Explique por qu el compilador crea un error. Se resolvera el error si la clase Forci^n fuera parte del paquete accessJocal? Resumen

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 clientes), que es un programador como nosotros, aunque lo que hace es utilizar la biblioteca para construir una aplicacin u otra biblioteca de mayor tamao.

Sin una serie de reglas, los programas cliente podran hacer lo que quisieran con todos los miembros de una clase, an cuando nosotros prefiriramos que no manipularan algunos de los miembros. Todo esuiria expuesto a ojos de todo el mundo.

En este capitulo hemos visto cmo se construyen bibliotecas de clases en primer lugar. la forma en que se puede empaquetar un grupo de clases en una biblioteca y. en segundo lugar, la forma en que las clases pueden controlar el acceso a sus

miembros.

Las estimaciones indican que los proyectos de programacin en C empiezan a fallar en algn punto entre las 50,000 y 100.000 lineas de cdigo, porque C tiene un nico espacio de nombres y esos nombres empiezan a colisionar, obligando a realizar un esfuerzo adicional de gestin. En Java, la palabra elave paekage. 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 problema de la colisin de nombres.

Existen dos razones para controlar el acceso a los miembros La primera consiste en evitar que los usuarios puedan husmear en aquellas partes del cdigo que no deben tocar. Esas partes son necesarias para la operacin interna de la clase, pero no forman parte de la interfaz que el programa cliente necesita Por tanto, definir los mtodos y los campos comn privados constituye un servicio para los programadores de clientes, porque asi stos pueden ver fcilmente qu cosas son .mportan- 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 permitir 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 una clase de una cierta manera y luego descubrir que una cierta reestructuracin del cdigo podra aumentar enormemente la velocidad. Si la interfaz y la implementacin estn claramente protegidas. podemos realizar las modificaciones sin forzar a los programadores de clientes a reescribir su cdigo. Los mecanismos de control de acceso garantizan que ningn programa cliente dependa de la implementacin subyacente de una clase.

Cuando disponemos de la capacidad de modificar la implementacin subyacente, no slo tenemos la libenad de mejorar nuestro diseo, sino tambin la libertad de cometer errores. Independientemente del cuidado que pongamos en la planificacin y el diseo, los errores son inevitables. Saber que resulta relativamente seguro cometer esos errores significa que tendremos 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 tase de anlisis del diseo. Pero incluso aqu existen posibilidades de modificacin. Si no definimos correctamente la interfaz a la primera podemos aadir ms mtodos siempre y cuando no eliminemos 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 (y en 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 presente 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 situaciones, el tipo de comunicacin es diferente, y la rgida adhesin a una serie de reglas de acceso puede que no sea la solucin ptima. En esos casos, el acceso predeterminado (de paquete) puede que sea perfectamente adecuado.

t.d^ mUiciono u lo ejercicio seleccionados pueden encontrarse en el documento electrnico 77#r Thtt\kin% n Java AWWLIUJ Soiulnm ChuU , dispouiblc para la venia en inm MindDi-w iwReutilizacin de clases

7 Una de las caractersticas ms atractivas de Java es la posibilidad de reutilizar 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 procedimemales como C. y no ha funcionado muy bien. Como todo lo dems en Java, la solucin que este lenguaje proporciona gira alrededor del concepto de clase. Reuttlizando el cdigo creamos nuevas clases, pero en lugar de crearlo partiendo de cero, usamos las clases existentes que alguien haya construido y depurado anteriormente.

t i truco estriba en utilizar las clases sin modificar el cdigo existente. En este capitulo vamos ver dos formas de llevar esto a cabo. La primera de ellas es bastante simple. 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 reutilizando la funcionalidad del cdigo, no su forma.

La segunda tcnica es ms sutil. Se basa en crear una nueva clase como un tipo Je una clase existente. Literalmente lo que hacemos es tomar la forma de la clase existente y aadirla cdigo sin modificarla. Esta tcnica se denomina herencia. y el compilador se encarga de realizar la mayor parte del trabajo. La

herencia es una de las piedras angulares de la programacin orientada a objetos y tiene una serie de implicaciones adicionales que analizaremos en el Capitulo 8. Poliformismo.

Resulta que buena parte de la sintaxis y el comportamiento son similares tanto en la composicin como en la herencia (lo que tiene bastante sentido, ya que ambas son formas de construir nuevos tipos a partir de otros tipos existentes). En este capitulo, vamos a presentar ambos mecanismos de reutilizacin de cdigo. Sintaxis de la composicin

Hemos utilizado el mecanismo de composicin de forma 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 Sfrlng, un par de primitivas y otro objeto de otra clase. Para los objetos no primitivos, lo que hacemos es colocar referencias dentro de la nueva clase, pero sin embargo las primitivas se definen directamente: //: reusing/SprinklerSystem.java // Composicin para la reutilizacin de cdigo. class WaterSource { private String s; WaterSource(I { System.out.println("WaterSo urce() ") ; s "Constructed" ,

i public String toStrmgt) ( return s; )

) public class SprinklerSystem { private String valvel, valve2, valve3, valve4;

private WaterSource source = new WaterSourcei); prvate nt i; private float f; public String toString) { return "valvel* " valvel N M + " " M\n" + "valve=MM + valve2+"valve3= M + valve34 "valve4= M + valve4 "source= " aource;

} public static void main(StringU args) { SorinklerSystem sprinklers = new SprinklerSystem); System.out.println(sprinklers*

) } / Output: WaterSource() valvel = nuil valve2 = nuil valve3 = nuil valve4 = nuil i - 0 f * 0.0 source *= Constructea ///:-

Uno de los mtodos definidos en ambas clases es especiaJ: 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. Asi. en la expresin contenida en SprinklerSystem.toString ): "source " + source?

el compilador ve que estamos intentando aadir un objeto String ("source = ") a un objeto WaterSource. Puesto que slo podemos aadir'* un objeto String a otro objeto String, el compilador dice voy a convertir source en un objeto String invocando toString )!** Despus de hacer esto, puede combinar las dos cadenas de caracteres y pasar el objeto String resultante a S>stcm.out.println() (o. de forma equivalente, a los mtodos estticos print( ) y printnb() que hemos definido en este libro) Siempre que queramos permitir este tipo de comportamiento dentro de una clase que creemos, nos bastar con escribir un mtodo toString).

Las primitivos que son campos de una clase se inicializan automticamente con el valor cero, como hemos indicado en el Capitulo 2. Todo es un objeto Pero las referencias a objetos se inicializan con el valor nuil, y si tratamos de invocar mtodos para cualquiera de ellas obiendremos una excepcin (un error en tiempo de ejecucin). Afortunadamente, podemos imprimir una referencia nuil sin que se genere una excepcin.

Tiene bastante sentido que el compilador no cree un objeto predeterminado para todas las referencias, porque eso obligara a un gasto adicional de recursos completamente innecesarios en muchos casos. Si queremos micializar las referencias, piulemos hacerlo de las siguientes formas:

1.

En el lugar en el que los objetos se definan. Esto significa que estarn siempre inicial izados antes de que se invoque el constructor

2.

En el constructor correspondiente a esa clase.

3.

4.

Justo antes de donde se necesite utilizar el ohjeto. Esto se suele denominar iniciulizacan diferida. Puede reducir el gasto adicional de procesamiento en aquellas situaciones en las que la creacin de los objetos resulte muy cara y no sea necesario crear el objeto todas las veces. Utilizando la tcnica de inicializacin de instancia.

El siguiente ejemplo ilustra las cuatro tcnicas: //: reusmg/Bath.java // Inicializacin mediante constructor con composicin, import static net.mindview.util.Print.*; class Soap ( private String s; Soap() { print I"Soap()"); s * "Constructed** ;

) public String toStringO { return s; }

) public class Eath ( private String ff Inicializacin en el punto de definicin: si = "Happy"4 s2 "Happy", s3, s4; private Soap castille; prvate int i; private float toy; public Bath() { print ("Inside Bathd")/ s3 * "Joy"; toy = 3.14f; castille * new SoapO;

) // Inicializacin de instancia: ( 1 - 47;) public String toStrlngI) { if(s4 * null) ft Inicializacin diferida: s4 = "Joy"; return "i * " 4 i 4 "\n" 4 "toy = " 4 - toy + "\n" "castille = " -* castille;

public static void main{String[] args) ( Bath b = new Bath I); print(b); ) ) /* Output: Inside BathO Soap I) si * Happy s2 = Happy s3 Joy s4 Joy i a 47 toy * 3.14 castille = Constructed *///:-

Observc que en el constructor Bath, se ejecuta una instruccin antes de que tenga lugar ninguna de las inicializaciones. Cuando no se realiza la inicializacin en el punto de definicin, sigue sin haber ninguna garanta de que se vaya a realizar la inicializacin antes de enviar un mensaje a una referencia al objeto, salvo la inevitable excepcin en tiempo de ejecucin.

Cuando se invoca toString( ). asigna un valor a s4 de modo que todos los campos estarn apropiadamente inicial izados 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. Utilice el mecanismo de inicializacin diferida para instanciar este objeto. Sintaxis de la herencia

La herencia es una parte esencial de Java (y todos los lenguajes orientados a objetos). En la prctica, siempre estamos utilizando la herencia cada vez que creamos una clase, porque a menos que heredemos explcitamente de alguna otra clase, estaremos heredando implcitamente de la clase raz estndar Object de Java.

La sintaxis 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 situadoantes de la llave de abertura del cuerpo de la clase, utilizando la palabra clave extends seguida del nombre de laclase base. Cuandohacemos esto*

automticamente obtenemos todos los campos y mtodos de la clase base He aqui una clase. //: reusmg/Detergent. java // Sintaxis y propiedades de la herencia. import static net .mindvi.ew.util. Print. * ? class Cleanser ( private String s = "Cleanser" public void appendString a) ( s *= a; ) public void dlluteIJ { appendl" dilateIJ"); ) public void applyi) ( append'" apply() "I ,* | public void scrubO { append f" scrub O "M ) public String

toStringO { retum s; | public static void main(StringU argel ( Cleanser x = new Cleansert;,* x.diiutel; x. apply (J; x. scrubO; print UJ ;

) public class Detergent extends Cleanser ( / / Cambio de un mtodo: public void scrubO { append(" Detergent.scrub 0"); 6Uper.scrub0; // Invocar versin de la clase base

) /,/ Aadir mtodos a la interfaz: public void team O ( append" foaml"); } // Probar la nueva clase: public static void main(StringU args) { Detergent x = new Detergent( ) x. di luteO i x applyl); x.scrub(;

x.foaml); printIxl; print("Testing base class:"); i Cleanser.main(argsl;

) / Output: Cleanser diluteu apply 0 Detergent .scrub ( i scrub i) foamO Testing base class: Cleanser dilutel) aoplyO scrub0

Esto nos permite ilustrar una serie de caractersticas. En primer lugar, en el mtodo Cleanser append(). se concatenan cadenas 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.

7 Reutilizacin de clases 401 En segundo lugar, tanto Cleanser como Detergen! contienen un mtodo main( ). Podemos crear un mtodo ntainf ) para cada una de nuestras clases; esta tcnica de colocar un mtodo maini) en cada clase permite probar fcilmente cada una de ellas. Y no es necesario eliminar el mtodo man( ) cuando hayamos terminado; podemos dejarlo para las pruebas posteriores.

An cuando tengamos muchas clases en un programa, slo se invocar el mtodo main( ) de la clase especificada en la linea de comandos. Por tanto, en este caso, cuando escribimos java Detergen!, se invocar Detergenf.main( ). Pero tambin podemos escribir jasa Cleanser para invocar a Cleanser.main( ). an cuando Gleanser 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

Aqui. podemos ver que Detergent.main( ) llama a Cleanser.main) explcitamente, pasndole los mismos argumentos de la linea 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 cspeeificador de acceso. los miembros adoptaran de forma predeterminada el acceso de paquete, lo que slo permite el acceso a los otros miembros del paquete Por tamo, dentro de este paquete, cualquiera podra usar esos mtodos si no hubiera ningn especifieador de acceso Detergent. por ejemplo, no tendra ningn problema Sin embargo, si alguna clase de algn otro paquete fuera a heredar de Cleanser. slo podri.i acceder a los miembros de tipo public Asi que. para permitir la herencia, como regla general deberemos definir todos los campos como prvate y lodos los mtodos como public (los miembros de tipo protected tambin permiten el acceso por parte de las clases derivadas; analizaremos este tema ms adelante). Por supuesto, en algunos casos particulares ser necesario hacer excepciones, pero estu directriz suele resultar bastante til.

7 Reutilizacin de clases 402 Cleanser dispone de una serie de mtodos en su interfaz: appendi ). dlute( ). applv( ). seruli( > \ ! oStrng( ), Puesto que Detergent deriva de Cleanser (gracias a la palabra clave e\tend\K automticamente obtendr todos estos mtodos como parte de su intertaz. an cuando no los veamos explcitamente definidos en Detergen! Por tanto, podemos considerar la herencia como un modo de reutilizar la clase.

Como podemos ver en scrul>( ). 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 scrubf ). no podemos simplemente invocar a serub ). ya que eso producida una llamada recursiva, que no es exactamente lo que queremos Pura resolver este problema, la palabra clave super de Java hace referencia a la superclse de la que ha heredado la clase actual Por tanto, la expresin super.scmb( ) invoca a la versin de la cluse base del mtodo serub )

Cuando se hereda, no estamos limitados 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 foamt ) es un ejemplo de esto.

En Detergen!.main( ) podemos ver que, pata un objeto Detergen!, podemos invocar todos los mtodos disponibles en Cleanser asi como eri Detergen! (es decir. foam( ))

Ejemplo 2: (2) Cree una nueva clase que herede de la clase Detergen! Sustituya el mtodo serubt )

7 Reutilizacin de clases 403 aada un nuevo

mtodo denominado stcTlize{ ). 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 pin;o confuso tratar de imaginar cul es el objeto resultante generado por una clase derivada. Desde lucra, 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 xuhobjeto de la clase base. Este subobjeto es idntico a lo 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 inicialicc 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 conocimientos y lodos los privilegios para llevar a cabo adecuadamente la inicializacin de la clase base. Java inserta automticamente llamadas al constructor de la clase base dentro del constructor de la clase derivada. El siguiente ejemplo muestra este mecanismo en accin con tres niveles de herencia //: reusing/Cartcon.java // Llamadas a constructores durante la herencia. import static net.mindview.til.Print. class Art { Art(; ( print("Art constructor"); )

7 Reutilizacin de clases 404 } class Drawing extends Art { Drawlng(} { print("Drawing constructor"); }

} public class Cartoon extends Drawing ( public CartoonO { print<"Cartoon constructor"); } public static void mainStringU args) ( Cartoon x = new Cartoon ()

) ) / Output: Art constructor Drawing constructor Cartoon constructor *///:-

Como puede ver. la construccin tiene lugar desde la base hacia afuera', por lo que la clase base se iniciaiiza 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 predeterminado que invocara al constructor de la clase base.

7 Reutilizacin de clases 405 Ejercicio 3:(2) Demuestre la afirmacin anterior.

Ejercicio 4: (2) Demuestre que los constructores de la clase base (a) se invocan siempre y (b) se invocan antes que los

constructores de la clase derivada.

Ejercicio 5: (I) Cree dos clases, A y B. con constructores predeterminados (listas de argumentos vacias) que impriman

un mensaje informando de la construccin de cada objeto. Cree una nueva clase llamada C que herede de A, y cree un miembro de la clase B dentro ile C. No cree un constructor para C. Cree un objeto de la clase C y observe los resultados. Constructores con argumentos

7 Reutilizacin de clases 406 El ejemplo anterior tiene constructores predeterminados; es decir, que no tienen argumentos. Resulta fcil para el compilador invocar estos constructores, porque no existe ninguna duda acerca de qu argumentos hay que pasar. Si no existe un constructor predeterminado en la clase base, o si se quiere invocar un constructor de la clase base que tenga argumentos, ser necesario escribir explcitamente una llamada al constructor de la clase base utilizando la palabra clave super y la lista de argumentos apropiada: //: reusing/Chess.java // Herencia, constructores y argumentos, import static net.mindview.til.Print.*; class Game ( Gamelint i) ( print I "Game constructor'*} ;

} class BoardGame extends Game { BoardGametint i) ( super(i); orint("BoardGame constructor")j

} i public class Chess extends BoardGame (

7 Reutilizacin de clases 407 ChessO ( super 111); printI"Chess constructor* )

) public static void mam (String n args) ( Chess x = new ChessO/

) | / Output: Game constructor SoardGame constructor Chess constructor ///:

Si no se invoca el constructor de la clase base en BoardCame( ). el compilador se quejar de que no puede localizar un

constructor de la forma Came( ). Adems, la llamada al constructor de la clase base debe ser la primera cosa que se haga

7 Reutilizacin de clases 408 en el constructor de la clase derivada (el compilador se encargar de recordrselo si se olvida de ello)

Ejercicio 6: (I) Utilizando Chess.ja> a. demuestre las afirmaciones del prrafo anterior.

Ejercicio 7: (I) Modifique el Ejercicio 5 de modo que A y B tengan constructores con argumentos en lugar deconstructores predeterminados. Escriba un constructor para C que realice toda la micializacion dentrodel cons

tructor de C.

Ejercicio 8: (I} Cree una clase ba.se que slo tenga un constructor no predeterminado y una clase derivada que tenga

7 Reutilizacin de clases 409 un constructor predeterminado (sin argumentos) y otro no predeterminado En los constructores de la clase derivada, invoque al constructor de la clase base.

Ejercicio 9: (2) Cree una clase denominada Roof que contenga una instancia de cada una de las siguientes clases (que

tambin deber crear): Componen! 1. Componen!! y Componente Derive una clase Stem de Roo! que tambin contenga una instancia de cada componente**. Todas las clases deben tener constructores predeterminados que impriman un mensaje relativo a la clase.

Ejercicio 10: (I) Modifique el ejercicio anterior de modo que cada clase slo tenga constructores no predetemunados Delegacin

Una tercera relacin, que no est directamente soportada en Java, se denomina delegacin. Se encuentra 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 lodos los mtodos del objeto miembro en nuestra nueva clase (como en la herencia). Por ejemplo, una nave espacial (spaceship) necesita un mdulo de control:

7 Reutilizacin de clases 410 //: reusing/SpaceShipControls. java public class SpaceShipControls ( void upint velocity) () void downtint velocity) (} void Ieft(nt velocity) () void rightint velocity) {) void forwardlint velocity) {) void backmt velocity) () void turboBoost() ()

) ///i-

Una forma de construir la nave espacial consistira en emplear el mecanismo de herencia: //: reusing/SpaceShip.java public class SpaceShip extends SpaceShipControls { private String ame; public SpaceShipfString namel { this.name = ame; } public String toStringO ( retum ame? } public static void main .1 String[] argsl ( SpaceShip protector * new SpaceShip("N5EA Protector"); protector.forward100)/

7 Reutilizacin de clases 411 I

) ///:-

Sin embargo, an objeto spaceship no es realmente "un upu de" objeto SpaccShlpControIs. atm cuando podamos, por ejemplo, decirle" al objeto SpaceShip que avance hacia adelante (foroard< )), Resulta nuts preciso decir que la nave espacial (el objeto SpaceShip) contiene un modulo de control (objeto SpaecSblpControb). y que. al mismo tiempo, todos los mtodos de SpaceShipC'ontrols deben quedar expuestos en el objeto SpaceShip. El mecanismo de delegacin permite resolver este dilema: //; reusing/SpaceShipDelegation. -java public class SpaceShipDelegation ( private String ame; private SpaceShipControls controls =new SpaceShipControlsi}; public SpaceShipDelegation iString name) ( this, name = name;

i // Mtodos delegados: public void back(int velocity) ( Controls.back(velocity);

7 Reutilizacin de clases 412 } public void down(int velocity/ ( controls.down(velocity) ;

) public void forward tint velocity) { controls.forward(velocity);

) public void left(int velocity) { controls.left(velocity);

) public void right(int velocity) ( controls.right(velocity);

) public void turboBoostO { controls.turboBoost() ;

7 Reutilizacin de clases 413 ) public void up(int velocity) ( controls.up(velocity) ; i public static void main(String[] argsJ ( SpaceShipDelegation protector new SpaceShipDelegation<WNSEA Protector*); protector.forward(1001;

) ///:-

Como puede ver los mtodos se dirigen hacia el mtodo controls subyacente, y la interfaz es, por tanto, la misma que con la herencia. Sin embargo, tenemos ms control con la delegacin, ya que podemos decidir proporcionar nicamente un sub- conjunto de los mtodos contenidos en el objeto miembro.

Aunque el lenguaje Java no soporta directamente la delegacin, las herramientas de desarrollo si que

7 Reutilizacin de clases 414 suelen hacerlo. Por ejemplo, el ejemplo anterior se ha generado automticamente utilizando el entorno integrado de desarrollo JetBrains Idea.

7 Reutilizacion de clases 415 I Ejercicio 11: (3) Modifique Detergent.java de modo que utilice el mecanismo de delegacinCombinacin de la composicin y de la herencia

Resulta bastante comn utilizar los mecanismos de composicin y tie herencia conjuntamente F.l siguiente ejemplo muestra la creacin de una clase ms compleja utilizando tanto la herencia como la composicin, junto con la necesaria iniciali- zacion mediante los constructores // reusing/PlaceSetting.java Combinacin de ia composicin y la herencia, impart static net. mmdview. uti 1 .Print. * ; clans Plate ( Plate l int i) ( ) ) print 1"Plate constructor" ) i

clase DinnerFlate extends Plate { DinnerPlate I int i) { super(1); print i"DinnerPlate constructor");

) 1 class tensil { UtensilUnt i) { print("Utensil constructor");

7 Reutilizacion de clases 416 I )

} class Spoon extends Utensil ( Spoon(int i) { super(i) print("Spoon constructor");

i i class Fork extends Utensil ( Fork(int 1) ( super(i) i print("Fork constructor");

i class Knife extends Utensil (

7 Reutilizacion de clases 417 I Knife(int i) ( super(i); printC'Knife constructor");

) i // Una forma cultural de hacer algo: class Custom ( Custom(int i) ( print("Custom constructor");

) public class PlaceSetting extends Custom { private Spoon sp; private Pork frk; prvate Knife kn; prvate DinnerPlate pl; public PlaceSetting(int i) { super(i * 1) ,* sp = new Spoon (i -+ 2 ) ; frk * new Forkti * 3) r kn = new Knife (i + 4> ; pl = new DinnerPlate(i + 5); prlnt<"PlaceSettina constructor"!;

7 Reutilizacion de clases 418 I > public static void maintStrngi] args] ( i PlaceSetting x = new PlaceSetting(9);

) / Output: Custom constructor tensil constructor Spoon constructor tensil constructor Fork constructor tensil constructor Knife constructor Pate constructor DinnerPlate constructor PlaceSetting constructor ///=-

Aunque el compilador nos obliga a iniciali/ar la clase base y requiere que lo hagamos al principio del constructor, no se asegura de que imcialicemos los objetos miembro, asi que es preciso prestar atencin a este detalle.

Resulta asombrosa la forma tan limpia en que las clases quedan separadas. No es necesario siquiera disponer del cdigo fuente de los mtodos paru poder reutilizar ese cdigo. Corno 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 objeto. La razn es. probablemente, que en Java la prctica habitual es olvidarse

7 Reutilizacion de clases 419 I de los objetos en lugar de destruirlos, permitiendo 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 tiempo de vida que obligan a realizar la limpieza. Como hemos mencionado en el Capitulo 5. mcializocumy limpieza, no podemos saber cundo va a ser invocado el depurador de memoria, o ni siquiera si va a ser invocado. Por tanto, s queremos limpiar algo concreto relacionado con una clase, deberemos escribir explicitamente un mtodo especial para hacerlo y aseguramos de que el programador de clientes sepa que debe invocar dicho mtodo. Adems, como se describe en el Captulo 12. Tratamiento de errores con excepciones, debemos protegemos frente a la aceleracin de posibles excepciones incorporando dicha actividad de limpieza en una clusula finall>

Considere un ejemplo de un sistema de diseo asistido por computadora que dibuje imgenes en la pantalla. //: reusing/CADSystem.java // Cmo garantizar una limpieza adecuada. package reus i ng; import static net.mindview.utii.Print. / class Shape ( Shapemt i) ( print (Shape constructorJ / ) void dispose() ( print("Shape dispose"); )class Circle extends Shape { Circle (int i) { super(i); print("Drawing Circle)/ void dispose() ( print("Erasing Circle"); super.dispose(J;

7 Reutilizacion de clases 420 I )

} class Triangle extends Shape { Triangle<int i) { super(i) ; print("Drawing Triangle")/

) void dispose!) ( print("Erasing Triangle")/ super.dispose()/

) class Line extends Shape ( private int start, end; Line(int start, int end) { super(start);

7 Reutilizacion de clases 421 I this.start = start; this.end = end/ print ("Drawing Line; " * start * ", " 4 end)/

) void dispose() ( print ("Erasing Line: " * start * ", H end); super.di spose()/

) public class CADSystem extends Shape { private Circle C; private Triangle t; private Line[J lines = new Line[3l ; public CADSystem(int i) ( super li 1) ; for (int j = 0; j < lines .length; j-t-t) lines[j] = new Lineij, j*}); c * new Circled); t = new Triangle (Imprint i"Combined constructor");

7 Reutilizacion de clases 422 I public void dispose() { print("CADSystem.dispose()"); //El orden de limpieza es el inverso // al orden de inicializacin: t.dispose\); c.dispose(J; foriint i * lines, length - 1/ i >* 0; i) lines[i].dispose()/ super.dispose( ) ;

} public static void main(StringU args) {CADSystem x - new CADSystem(47)j try ( // Cdigo y tratamiento de excepciones... ) } finally { x.dsposeO j )

) / Output: Shape constructor Shape constructor Drawing Line: 0. 0 Shape constructor Drawing Lne: 1, 1 Shape constructor Drawir.g Line: 2, 4 Shape constructor Drawing Circle Shape constructor Drawing Triangie Combined constructor CADSystem.dispo se() Srasing Triangie Shape d.soose Erasing Circle Simpe

7 Reutilizacion de clases 423 I dlspose Krasmg Line: 2. 4 Shape dispose Erasing Line: 1. 1 Shape dispose Erasing Line: 0, 0 Shape dispoae Shape dispose ///i-

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 sustituye el mtodo dispose( ) de Shape, adems de invocar la versin de dicho mtodo de la clase base utilizando super Las clases Shape especficas, Circle. Triangie y Line 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( ) pora 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 main( ). hay do?, palabras clave que no habamos visto antes y que no van a explicarse en detalle hasta el Captulo 12, Tratamiento Je errores mediante excepciones: try y finall> La palabra clave try indica que el bloque situado a continuacin 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 situada a continuacin deesia regin protegida siempre se ejecuta, independientemente de cmo se salga de bloque try (con el tratamiento de excepciones, es posible salir de un bloque try de diversas formas distintas de la normal). Aqu, la clusula flnall\ dice: Llama siempre a disposei ) para \. independientemente de lo que suceda.

7 Reutilizacion de clases 424 I En el mtodo de limpieza, (dispose( ). en este caso), tambin 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 subobjeto dependa de otro. En general, hay que seguir la misma forma que imponen los compiladores de G++ para los destructores: primero hay que realizar toda la tarea de limpieza especifica de la clase, en orden inverso a su creacin (en general, esto requiere que los elenentos de la clase base sigan siendo viables) A continuacin, se invoca el mtodo de limpieza de la clase base, como se ilustra en el ejemplo. May 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 memoria no sirv e de gran ayuda en este aspecto. El depurador de memoria puede que no

7 Reutilizacin de clases 425 llegue nunca a ser invocado y. en caso de que lo sea, podra reclamar los objetos en el orden que quisiera. No podemos confiar 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 finaliz ).

Ejercicio 12: (3)Aadauna 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 clase derivada no ocultar ninguna de las versiones de la clase base (a diferencia de lo que sucede en C++). Por tanto, el mecanismo de sobrecarga funciona independientemente de si el mtodo ha sido definido en este nivel o en una clase base: / / : retsing/Hi de. j 3va . La sobrecarga de un nombre de mtodo de la clase base en una i clase derivada no oculta las versiones de la ciase base, import static net .mindview.utii .Print. .Tase Homer ( char doh(char el { print("doh(char)"); rsturn * d * ,* J float dohlfloat f) ( print(*doh(f loat)*/ return l.Ofj

7 Reutilizacin de clases 426 i class Mllhouse (] class Bart extends Homer ( veid dohi'Mllhouse m) { print "doh(Milhcusel") j

) i public class Hide ( public static void nainStringH args1 ( Bart b = new Bart f)/ b.doh(1); b.dnh('x')/ b.dohU .Of) i b.doh(new Milhcuse t);

| /* Oufcput: donlfloat) doh(char) dohlfloat) doh(Mllhouse)

///

7 Reutilizacin de clases 427 Podemos ver que todos los mtodos sobrecargados de Homer estn disponibles en Bar, aunque Bart introduce un nuevo mtodo sobrecargado (si se hiciera esto en G++ se ocultaran los mtodos de la clase base). Como veremos en el siguiente captulo, lo ms comn es sobrecargar los mtodos del mismo nombre, utilizando exactamente la misma signatura y el mismo upo de retorno de la clase de retorno 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 SE5 ha aadido al lenguaje la anotacin (q Ovcrride, 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 generara un mensaje de error si sobrecargamos accidentalmente el mtodo en lugar de sustituirlo //: reusing/Lisa.java // (CompileTimeError( (Won't compile) class Lisa extends Homer { Override void doh(Mllhouse m) { System.out.printInf"doh(Milhouse) * ) : ) } tu-.-

F.l maleador {CompeTimeErnir) excluye el archivo del proceso de consiruccin con Ant de este libro, pero si lo compila a mano podr ver el mensaje de error: method does not override a method from its superclass

La anotacin (a Override evitara, asi. que sobrecarguemos accidentalmente un mtodo cuando no es

7 Reutilizacin de clases 428 eso lo que queremos hacer.

Ejercicio 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 disponibles 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, mientras que en el caso de la herencia esto se hace de forma implcita). Puede que se est preguntando cul es la diferencia entre ambos mecanismos y cundo conviene elegir entre uno y otro.

Generalmente, la composicin se usa cuando se desea incorporar la funcionalidad de la clase existente dentro de la nueva clase pero no su interfaz En otras palabras, lo que hacemos es integrar un objeto para poderlo utilizar con el fin de poder implementar ciertas caractersticas en la nueva clase, pero el usuario de la nueva clase ver la interfaz que hayamos definido para la nueva clase en lugar de la

7 Reutilizacin de clases 429 interfaz del objeto incrustado. Para conseguir este efecto, lo que hacemos es integrar objetos prvate de clases existentes dentro de la nueva clase.

Algunas veces, tiene sentido permitir que el usuario de la elase 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 mplementaein por si mismos, asi que no existe ningn nesgo. Cuando el usuario sabe que estamos ensamblando un conjunto de elementos, normalmente, puede comprender mejor la interfaz que hayamos definido. IJn ejemplo seria un objeto car (coche): //: reusmg/Car.java // Composicin con objetos pblicos. class Engine ( public void start) () public void rev() () public void stop() {)

) class Wheei { public void nflate(int psa) {}

) class Window { public void rollupt) {} public void rolldownO ()

7 Reutilizacin de clases 430 } class Door { public Window window = new Window(); public void oper* () ()public void cise O (}

) public class Car { public Engine engme new Engine); public Wheel(] wheel new Wheel[4! ; public Door left = new DoorU, right = new DoorO? // 2-door public CarO { for(mt i 0; i < 4; i++) wheel[i] = new Wheel();

) public static void mam(StringU args> { Car car = new Car(); car.left.window.rollup); car.wheel[0].nflate 172);

i I ///:-

Puesto que en este caso la composicin de un coche forma parte del anlisis del problema (y no

7 Reutilizacin de clases 431 simplemente del diseo subyacente). hacer los miembros pblicos ayuda al programador de clientes a entender cmo utili/ar la clase, y disminuye tambin 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 al mecanismos de herencia, lo que hacemos es tomar una clase existente y definir una versin especial de la misma. Fn general, esto quiere decir que estaremos tomando una clase de propsito general y especializndola para una necesidad 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 ex un vehculo. La relacin es-un se expresa mediante la herencia, mientras que la relacin tiene-un se expresa mediante la composicin.

Ejercicio 14: (I) En Car. ja va aada un mtodo servicef) a Engine c 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 prvate resultara suficiente, pero en los proyectos reales, hay veces en las que queremos ocultar algo a ojos del mundo pero permitir que accedan a ese algo los miembros de las clases derivadas.

7 Reutilizacin de clases 432 La palabra clave protected es homenaje al pragmatismo, lo que dice es: liste elemento es privado en lo que respecta al usuario de la clase, pero est disponible para cualquiera que herede tic esta clase y para todo lo dems que se encuentre en el mismo paquete (protected tambin proporciona acceso de paquete)*.

Aunque es posible crear campos de tipo protected (protegidos), lo mejor es definir los campos como privados; esto nos permitir conservar siempre el derecho a modificar la implcmentacin subyacente. Entonces, podemos permitir que los herederos de nuestra clase dispongan de un mtodo controlado utilizando mtodos protected: //: reusing/Orc.}ava // La palabra clave protected. import static net.mindview.util.Print.; class Villain ( prvate String ame; protected void set (String nmj ( ame * nm; ) public VillainIString namei ( this.name = ame; } public String toStringO ( retum HI*m a Villain and my ame is M ame;

) public class Ore extends Villain ( prvate int orcNumber; public Ore(String ame, int orcNumber) ( Ruperinamel; this.orcNumber - orcNuinber; i

7 Reutilizacin de clases 433 public void chanaeiStrina ame, int orcNuraberi j Retname}; // Disponible porque est protegido. thla.orcNumber * orcNumber,-

) public String toStringO ( return "Ore " * orcNumber i public static void main (String [] argsi ( Ore ore = new Ore("Limburger", 12 )j print(ore; ere.change("Bo b", 19) ; print(ore)j super.toString (J ;

) ) /* Output: Ore 12: Xm a Villain and my ame is Limburger Ore 19; T'm a Villain and my ame is Bob *///:-

Puede ver que change( ) tiene a cccso a sct( ) porque es de tipo protccted. Observe tambin la forma

7 Reutilizacin de clases 434 en que se ha definido el mtodo toStrng( ) de Ore en trminos de toString( ) de la clase base.

Ejercicio 15: (2) Cree una clase dentro de un paquete. Esa clase debe estar dentro de un paquete. Esa clase debe conte

ner un mtodo protccted. Fuera del paquete, trate de invocar el mtodo protected y explique los resulta dos. Ahora defina otra clase que herede de la anterior c invoque el mtodo protected 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 entre la nueva clase y la clase base. Esta relacin puede resumirse diciendo que la nueva clase car un tipo de la clase existente.

Esta descripcin no es simplemente una forma elegante de explicar la herencia, sino que est soportada directamente por el lenguaje. Por ejemplo, considere una clase base denominada Instrument que represente instrumentos musicales y una clase derivada denominada YVind (instrumentos de viento) Puesto que la herencia garantiza que todos los mlodos de la clase base estn

7 Reutilizacin de clases 435 disponibles tambin en la clase derivada, cualquier mensaje que enviemos a la clase base puede enviarse tambin a la clase derivada. Si la clase Instrument tiene un mtodo play( ) (tocar el instrumento), tambin lo tendrn los instrumentos de la clase VVind. Esto significa que podemos decir con propiedad que un objeto Wind es tambin un objeto de tipo Instrument. El siguiente ejemplo ilustra cmo soporta el compilador esta idea. //: reusing/Wmd. java // Herencia y generalizacin. class Instrument ( public void playl) {) static void tune(Instrument i) ( // . . .
i.

playO ;

) // Los objetos instrumentos de viento // porque tienen la misma interfaz

7 Reutilizarin de clases 436 :public ciass Wind extends Instrument ( pubiic static void main[String U argsj ( Wind flute = new WindO; Instrument.tune(flutel; // Generalizacin

} ///:-

Lo ms interesante de este ejemplo es el mtodo tune ) (afinar), que acepta una referencia a un objeto Instrument. Sin embargo, en YVind muin() al mtodo tune( ) se le entrega una referencia a un objeto W ind Dado que Java es muy estricto en lo que respecta a las comprobaciones de tipos, parece extrao que un mtodo que acepta un determinado tipo pueda aceptar tambin otro tipo distinto, hasta que nos demos cuenta de que un objeto Wind tambin es un objeto Instrument y de que no existe ningn mtodo que tunc( ) pudiera invocar para un objeto Instrument V 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 iipeasting (generalizacin).

7 Reutilizarin de clases 437 Por qu generalizar?

El trmino est basado en la forma en que se vienen dibujando tradicionalmente los diagramas de herencia de clase. Con la raz en la parte superior de la pagina y las clases derivadas distribuyndose hacia abajo (por supuesto, podramos dibujar los diagramas de cualquier otra manera que nos resultara til). El diagrama de herencia para Wind.java sera entonces:

AI realizar una proyeccin de un tipo derivado al tipo base, nos movemos /tocia arriba en el diagrama de herencia, y esa es la razn de que en ingls se utilice el trmino upeasting (up = arriba, casi = proyeccin. El upeasting o generalizacin siempre resulta seguro, porque estamos pasando de un tipo ms especifico a otro ms general. Es decir, la clase derivada es un 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 generalizacin es que pierda mtodos, no que los gane, y sta es la razn por la que el compilador permite la generalizacin sin efectuar ningn tipo de proyeccin explcita y sin emplear ninguna notacin especial.

Iambin podemos realizar el inverso de l3 generalizacin, que se denomina Jowncasting (especializacin), pero esto lleva asociado un cierto dilema que examinaremos ms en detalle en el siguiente capitulo, y en el Captulo 14, Informacin de tipos.

7 Reutilizarin de clases 438 Nueva comparacin entre la composicin y la herencia

Ln la programacin orientada a objetos, la forma ms habitual de crear y utilizar cdigo consiste en empaquetar los dalos y mtodos en una clase y usar los objetos de dicha clase Tambin utilizamos otras clases existentes para construir nuevas clases 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 quiere 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 formas ms claras de determinar 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 mecanismo, entonces la herencia ser necesaria, pero si ese mecanismo no hace falta conviene meditar si verdaderamente hay que emplear la herencia. En el Capitulo 8. Polimorfismo se proporciona una de las razones ms importantes para utilizar la generalizacin, pero si se acuerda de preguntarse voy a necesitar generalizar en algn momento?" tendr una buena forma de optar entre la composicin y la herencia.

Ejercicio 16: (2)Creeunaclase defina una nueva clase denomina

denominada Amphihian (anfibio). A partir de sta,

da Frog (rana) que herede de la anterior. Incluya una serie de mtodos apropiados en la clase base. En

7 Reutilizarin de clases 439 niain().cree un objeto Frog y realice una generalizacin a Amphibian. demostrando que todos los mtodos siguen funcionando.

Ejercicio 17: (I) Modifique el Ejercicio I6 para que el objeto Ero sustituya las definiciones de mtodos de la clase

base (proporcione las nuevas definiciones utilizando las mismas signaturas de mtodos). Observe lo que sucede en main( ). La palabra clave final

La palabra cla\c de Java final tiene significados ligeramente diferentes dependiendo del contexto, pero en general quiere decir: Este elemento no puede modificarse". Puede haber dos razones para que no queramos permitir los cambios: diseo Y eficiencia Puesto que estas dos razones son muy diferentes entre s. resulta bastante posible utilizar lu palabra clave final de manera inadecuada.

En las siguientes secciones vamos a ver los tres lugares donde final puede utilizarse: para

7 Reutilizarin de clases 440 los datos, para los mtodos y para las clases. Datos final

Muchos lenguajes de programacin disponen de alguna forma de comunicarle al compilador que un elemento de datos es 'constante". Las constantes son tiles por dos razones:

Puede tratarse de una constante Je tiempo Je compilacin que nunca va a cambiar.


1.

Puede tratarse de un valor inicializado en tiempo de ejecucin que no queremos que cambie.
2.

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 asi ciertos 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 preciso definir un valor.

7 Reutilizarin de clases 441 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 primitiva. final hace que el valor sea constante, pero con una referencia a objeto lo que final hace constante es la referencia. Lina vez micializada la referencia a un objeto, nunca se la puede cambiar para que apunte a otro objeto. Sin embargo, el propio objeto s que puede ser modificado; Java no proporciona ninguna manera para hacer que el objeto arbitrario sea constante (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 aqu un ejemplo donde se ilustra el uso de los campos final Observe convenio, los campos que son a

que. por la ve*

static y final (es decir, constantes de tiempo de compilacin) se escriben maysculas, utilizando guiones bajos para sepa

en

rar las palabras.

7 Reutilizarin de clases 442 //: reusmg/FinalData. java // Efecto de final sobre los campos, import java.til.*; import static net.mindview.utii.Print.*; class Valu { int i; // Acceso de paquete public Valu(int i) { this.i = i; }

) public class FinalData { private static Random rand = new P.andomU7) ; prvate String id; public FinalData(String id) ( this.id = id; ) // Pueden ser constantes de tiempo de compilacin: private final int valueOne = 9; prvate static final int VALE__TWQ = 99; // Constante pblica tpica: public scatic final int VALUE_7HREE 1 9 : // No pueden ser constantes de tiempo de compilacin: prvate final int 14 = rand.nextlnt(20) ; static final int 1NT_5 = rand.nextlnt (20',* prvate Valu vi = new Valu(11l; prvate final Valu v2 * new Valu(22); prvate static final Valu VAL_3 = new Valu(33); // Matrices:

7 Reutilizarin de clases 443 prvate final int[] a * ( 1, 2, 3, 4, 5, 6}; public String toStringO ( recum. id " : " + "14 = n + i4 + H. INT_5 * M + INT_5 ;

) public static void main{StringH args) ( FinalData fdl = new FinalData(MfdlM); I I \ fdl.valueOne-M-; // Error: no se puede modificar el valor fdl.v2.i+*; // iEl objeto no es constante! fdl.vi = new Valu(9} j ( / OK -- no es final forlint i * 0; i < fdl.a.length; -m-) fdl.a[i]++; // jEl objeto no es constante! //! Edl.v2 = new Value(O); // Error: no se puede //I fdl.VAL_3 =* new Value(l); // cambiar la referencia //! fdl.a = new int 13j; print(fdl)a print("Creating new FinalData") ; FinalData fd2 = new FinalData("fd2") ; printlfdl); print fd2);

) } I * Output: fdl: 14 15, INT_5 = 10 Creating

7 Reutilizarin de clases 444 new FinalData fdl: 14 15. INT_5 10 fd2: 14 = 13, INT 5 = 10 *///:-

Dado que valueOnc y VALUE_TWO son primitivas final con valores definidos en tiempo de compilacin, ambas pueden usarse como constantes de tiempo de compilacin y no se diferencian en ningn aspecto importante. VAJLUETHREE es la forma ms tpica en que podr ver definidas dichas constantes: public que se pueden usar fuera del paquete, sialie para enfatizar que slo hay una y final para decir que se Irata de una constante. Observe que las primitivas final static con valores iniciales constantes (es decir, constantes en tiempo de compilacin) 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 convenio).

El que algo sea final no implica necesariamente el que su valor se conozca en tiempo de compilacin. El ejemplo ilustra esta inicializando 14 e INT_5 en tiempo de ejecucin, mediante nmeros generados aleatoriamente. Esta parte del ejemplo tambin genera la diferencia entre hacer un valor final esttico o no esttico. Esta diferencia slo se hace patente cuando los valores se inicializan en tiempo de ejecucin, yaque el compilador trata de la misma manera los valores de tiempo de compilacin (en muchas ocasiones, optimizando el cdigo paraeliminar esas constantes). La diferencia se muestra cuando se

7 Reutilizarin de clases 445 ejecuta el programa. Observe que los valores de 4 para fdl y fd2 son distintos, mientras que el valor para INT_5 no cambia porque creemos el segundo objeto FinalData. Esto se debe a que es esttico y se inicializa una sola vez durante la carga

> no

cada vez que se crea un nuevo objeto.

Las variables vi a VAL J ilustran el significado de una referencia final Como puede ver en main( ). el hecho de que v2 *<-*a final no quiere decir que se pueda modificar su valor. Puesto que es una referencia, final significa que no se puede asociar \ 2 con un nuevo objeto. Podemos \a que la afirmacin tambin es cierta para las matrices, que son otro de tipo de referencia (no hay ninguna forma que yo conozca de hacer que las referencias a una matriz sean final). Hacer las referencias final parece menos til que definir las primitivas como final.

Ejercicio 18: (2)Creeunaclasccon la diferencia entre los dos Valores final en blanco

un campo static final y un campo llnal y demuestre

7 Reutilizarin de clases 446 Java permite la creacin de valores finales en blanco, que son campos que se declaran como final pero que no se les proporciona un valor de inicializacin. Fn todos los casos, el valor final en blanco debe ser inicial izado antes de utilizarlo, y el compilador se encargar de hacer que esto sea asi. Sin embargo, los valores final en blanco proporcionan mucha ms flexibilidad en el uso de la palabra clave final ya que, por ejemplo, un campo final dentro de una clasc podr con esto ser diferente para cada objeto y mantener an asi su carcter de inmutable. He aqu un ejemplo: //: reusing/BlankFinal.java ! i Campos final en blanco*. class Poppet { private int i; Poppet(int ii) { i = ii; ) i public class BlankFinal { private final int i 0; // Valor final inicial izado private final int j; // Valor final en blanco prvate final Poppet p; // Referencia final en blanco // Los valores final en blanco DEBEN inicializarse en el constructor: public BlankFinal() ( j =1; // Inicializar valor final en blanco p = new Poooet(1); // Inicializar referencia final en blanco

) public BlankFinal(int x) { j x; // Inicializar valor final en blanco p = new Poppet (x) , // Inicializar referencia final en blanco

7 Reutilizarin de clases 447 ) public static void main(String[J args) ( new BlankFinal(J; new BlankFinal(47);

) ///:-

Estamos obligados a realizar asignaciones a los valores final utilizando una expresin en el punto de definicin del campo

o bien en cada constructor. De esa forma, se garantizar que el campo final est siempre inicializado antes de utilizarlo.

7 Reutilizarin de clases 448 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 estar inicializado antes de utilizarlo, y que no se puede modificar una vez inicializado. Argumentos final

Java permite 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: //: reusing/FinalArguments.java // Uso de "final" con argumentos de mtodos. clas9 Gizmo { public void spinO () ) public class FinalArguments ( void with(final Gizmo g) { //! g new Gizmo(); // Ilegal -- g es final

) _:a without teizms 3 {

7 Reutilizarin de clases 449 3 c new Sixmo \;0K - - g r.c es f i n a l 3,spin i 1 voi* f'final ir.t i i+t; ) Xq se puede carx-iar LES primitivas final slo pueden leerse, int 3 final int i> { return i I* | pibli* static void main(String[] args- j Fijiaiargumencs b new FinaiArgument5 'j; b i .without'nuil); L r with nuil ir

Lo* mtodos f( ) \ g( ) muestran lo que sucede cuando ios argumentos primitivos son final: se puede leer el argumento, pero no modificarlo. Esta caracterstica se utiliza principalmente para pasar datos a las clases internas annimas, lo cual es un rema del que hablaremos en el Captulo 10. Clase* memas Mtodos final

Ha> dos razones para utilizar mtodos final La primera es bloquear" el mtodo para impedir que cualquier clase que herede ie esta cambie su significado. Esto se hace por razones de diseo cuando queremos aseguramos de que se retenga el comportamiento de un mtodo durante la herencia y que ese mtodo pueda ser sustituido.

La segunda razn por la que se ha sugerido en el pasado la utilizacin de los mtodos

7 Reutilizarin de clases 450 final es la eficiencia. Fn las imple- men tac iones anteriores de Java, si definamos un mtodo como final, permitamos al compilador convertir todas las llamada' a e>e mtodo en llamadas en linea Cuando el compilador vea una llamada a un mtodo final, podia (a su discrecin) saltarse el modo nonna! de insertar el codigo correspondiente al mecanismos ile llamada al mtodo (insellar los argumentos en la pila, saltar al cdigo del mtodo y ejecutarlo, saltar hacia atrs v eliminar de la pila los argumentos y tratar el valor de retomo), paia sustituir en su lugar la llamada al mtodo por una copia del propio cdigo contenido en el cuerpo del mtodo Esto elimina el gasto adicional de recursos asociado a la llamada al mtodo. Por supuesto, si el mtodo es de gran tan.ao. el cdigo empezar a crecer enormemente y probablemente no detectemos ninguna mejora de velocidad por la utilizacin de mtodos en linea, ya que la mejora ser insignificante comparada con la cantidad de tiempo invertida dentro del metodo,

I it Lis versiones mas recientes de Java, la mquina virtual (en particular, la tecnologa h o t o p o f ) puede detectar estas situaciones y eliminar el paso adicional de imlireecion. por lo que ya no es necesario (de hecho, se desaconseja por regla genera! utilizar final para tratar de ayudar al optimizador Con Java SF5 6. lo que debemos hacer es dejar que el compilador y la J\ VI se encarguen de las cuestiones de eficiencia, y slo debemos definii un metodo como final s queremos impedir explcitamente la sustitucin del mtodo en las clases derivadas.1 final y prvate

l o> mtodos privados de una clase son implicitamente de tipo final (finales). Puesto que no se puede acceder a un metodo privado, es imposible sustituirlo en una clase derivada Podemos aadir el especificador final a un metodo private, pero 110 tendr ningn efecto adicional.

7 Reutilizarin de clases 451 tema puede causar algo du- confusin, porque si se trata de sustituir 1111 metodo private (que implcitamente es final), parece que el mecanismo funciona y el compilador no proporciona ningn mensaje de error.
1 -le

: reusng/FinalOverriding! Ilusin.java Tan slo parece que podamos sustituir / un mtodo privado o un mtodo privado final itrport static net.mindviftw.til.frmt ; \'o pierda el tiempo traame* de optimi/ar prematuramente Si el sistema funciona > es demasiado temo a*utia dudoyo que !i> pueda solventar con la palabra clave final htip Miiultu u wi Books contiene informacin acerca de !,u tcnicas de perfilado, que pueden servir dv ivud;-. a la hora !<- acelerar lo* programa* cass WithFinals {
II

Idntico ai uso de "prvate" sola:

private final void fCJ ( print \ "WithFinals.f(J" I ; ) // Tambin automaaticmente "final1'i private void g{) ( print("WithFinals.gOM); (

) class OverridingPrivate extends WithFinals ( private final void fO j \ print("OverridingPrivate.f{ } " ) ;

private void g() ( print COverridingPrivate.gO ">;

7 Reutilizarin de clases 452 >

) class OverrdingPrivate2 extends OverridingPrivate { public final void f() ( print ("OverridingPnvate2 . f () H) ;

} public void gO { print <"OverridingPrivate^.g()");

) public class FinalOverridinglllusio

7 Reutilizarin de clases 453 n { public static void main(String[] args) { OverridingPrivate2 op2 = new OverridingPrivate2(I ; op2.f(); op2.g () / // Se puede generalizar: OverridingPrivate op = op2; // Pero no se pueden invocar los mtodos: //! op.fO? //! op.gO-f
II

Lo mismo aqu:

WithFinals wf p2; J //! wf.fO // ! wf.g ();

) /* Output: OverridingPnvate2. f ) OverridingPrivate2.g) V//S-

La ''sustitucin de los mtodos slo puede lener lugar si el mtodo forma parte de la clase base. Ln otras palabras, es necesario poder generalizar un objeto a su tipo base y poder invocar al mismo mtodo (este tema resultar ms claro en el siguiente capitulo). Si un mtodo es privado* no forma parte de la interfaz de la clase base. Se Irata simplemente de un cierto cdigo que est oculto dentro de la clase y que sucede que tiene ese nombre, pero si creamos un mtodo public. protected 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 simplemente habremos creado oiro nuevo. Puesto que un mtodo private es inalcanzable y resulta invisible a efectos prcticos, a lo nico que afecta es a la organizacin del cdigo de la clase en la que haya sido definido.

7 Reutilizarin de clases 454 Ejercicio 20: (I) Demuestre que la anotacin Override resuelve el problema descrito en esta seccin

Ejercicio 21: (I) 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 455 .Clases final

Cuando decimos que iodo una clase es de lipo final (precediendo su definicin con la palabra clave final), lo que estamos diciendo es que no queremos heredar de esta clase ni permitir que nadie ms lo haga. En otras palabras, por alguna razn, el diseo de nuestra clasc es de tal naturaleza que nunca va 3 ser necesario efectuar ningn cambio, o bien no queremos que nadie defina clases derivadas por razones de seguridad. //: reuslng/Jurassic.java // Definicin de una clase completa como final. class SmallBrain (}

final class Dinosaur ( int 1 * 7 ; int j = 1; SmallBrain x * new SmallBrain () ; void f () {)

) //j class Further extends Dinosaur ()


II

error: no se puede heredar de la clase final 'Dinosaur'

public class Jurassic { public static void main(StringU aras) ( Dinosaur n new Dinosaur();

7 Reutilizacin de clases 456 n.f 0 ; n.i 40;

n. j-M-;

) ///:-

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 aplican las mismas reglas independientemente de si la clase esta definida como final. Sin embargo, como la clasc impide la herencia, todos los mtodos en una clase final son implcitamente final, ya que no existe ninguna forma de sustituirlos. Podemos aadir el especificador final a un mtodo de una clase final, pero no tiene ningn efecto adicional.

Ejercicio 22: (I) Cree una clase final y trate de definir otra clase que herede de ella

7 Reutilizacin de clases 457 Una advertencia sobre final

En ocasiones, uno puede verse tentado a definir un mtodo como final a la hora de definir una clasc. pensando que nadie podra querer sustituir ese mtodo. A veces, es cierto que las cosas pueden ser as.

Pero tenga cuidado con las suposiciones que realiza. En general, es difcil prever cmo se va a reutilizar una clasc. especialmente si se trata de una clase de propsito general. Si define un mtodo como final, puede que este impidiendo rcutilzar la clase a travs del mecanismo de herencia en algn otro proyecto de programacin, simplemente porque no haba llegado a imaginar que esa clase pudiera llegar a emplearse de esa forma.

La biblioteca estndar de Java es un buen ejemplo de esto. En particular. la clase Vector de Java 1.0 1.1 se utilizaba de forma bastante comn y todava pinla haber resultado mas 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 fundamental y tan til y sustituir sus mtodos, pero los diseadores decidieron por alguna razn que esto no era apropiado. Resulta bastante irnico que se tomara esa decisin por dos razones distintas. En pnmer 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 cualquier caso, se trata de un ejemplo en el que los propios diseadores de Jav a decidieron que una determinada clasc heredara de Vector Cuando crearon Stack de esta forma, 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 clementAt( ) estn sincronizados (synchronized). Como veremos en el Captulo 21, Concurrencia, esta caracterstica restringe significativ amente las prestaciones, lo que probablemente anula cualquier

7 Reutilizacin de clases 458 ganancia proporcionada por final. Fste ejemplo tiende a avalar la teora de que los programadores suelen equivocarse siempre a la hora de decidir donde hay que optimizar. Resulta un poco penoso que un diseo tan pobre terminara siendo incluido en la biblioteca estndar para que todo el mundo tuviera que sufrirlo (afortunadamente, la moderna biblioteca de contenedores Java sustituye Vector por ArrayList. que se comporta de una forma mucho ms civilizada; lamentablemente, hoy da se sigue escribiendo cdigo que utiliza la antigua biblioteca de contenedores).

Resulta tambin interesante observar que Hashtablc. otra clase importante de la biblioteca estandar 1.0/1.1 de Java no tiene ningn mtodo final Como va se ha mencionado, es patente que algunas de las clases fueran diseadas por personas distintas (tendr la ocasin de comprobar que los nombres de los mtodos en Hashtablc son mucho ms breves comparados con los de Vector, lo que constituye otra prueba de esta afirmacin). Esto es. precisamente, 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 necesario a los usuarios, lo cual es otro argumento en favor de las revisiones de diseo y de los programas (observe que la biblioteca actual de contenedores Java sustituye Hashtable 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 cuidadosamente para que el orden de inicializacin de los valores estticos no cause problemas. Por ejemplo, C++ tiene problemas si uno de los valores estticos espera que otro valor esttico sea vlido antes de que el segundo haya sido inicial izado.

Java no tiene este problema porque adopta una tcnica de carga completamente distinta. sta es una de las actividades que se facilitan enormemente porque en Java todo es un objeto. Recuerde que el cdigo compilado de cada clase est almacenado 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". Usualmentc, dicho lugar es cuando se construye el primer objeto de esa clase, aunque la carga tambin puede tener lugar cuando se acceda a un eampo esttico o a un mtodo esttico. 2

7 Reutilizacin de clases 459 El lugar del primer uso es tambin el lugar donde se produce la inicializacin de los elementos estticos Todos los elementos estticos y el bloque de cdigo static se inicializarn en orden textual (es decir, en el orden en el que estn escritos en la definicin de la clase), en el punto donde se produzca la carga. Por supuesto, los valores estticos slo *e micializan una

vez. inicializacin con herencia

Resulta Util 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.til .Print. fclass Insect ( private int i = 9; protected int j; Insectt) ( print ("i * " i + j * * * j) j * 39;

) private static int xl * printInit("static Insect.xl initialized"); static int printlnit(String si {

7 Reutilizacin de clases 460 : El constructor a tambin un mtodo esttico, an cuando la palabra cIhvc ststk no sea explcita. Por tanto, para ser preciso*, una clase se carga por poniera ve; cuando ve accede a cualquiera de su miembros estticos printis); retum 47;

) public class Beetie extends Insect ( private int k = printlnit("Beetle.k intill2edMJ; public Beetiei 1 ] print(Hk = * k) ; printl"J = B 4 j ) ; i prvate static int x2 = printlnit("static Beetie.x2 initialized"); public static void main(StringU args) { print("Beetie constructor"); Beetie b new Beetie W

} } / Output: static Insect.xl initialized static Beetie.x2 initialized Beetie constructor i * 9. j = 0 Beetle.k initialized k 47 j * 39 ///:-

Lo primero que sucede cuando se ejecuta Java con Beetie es que se traa de acceder a Beetle.niain() (un mtodo esttico), por lo que el cargador localiza el cdigo compilado correspondiente a la clase Beetie (en un archivo denominado Beetlc.class) Durante el proceso de carga, el cargador observa que tiene una clase base (eso es lo que dice la palabra clave eUends). por lo que procede a cargarlo. listo suceder independientemente de si se va a construir un objeto de dicha clase base (pruebe a desactivar con comntanos la creacin del objeto como demostracin)

7 Reutilizacin de clases 461 Si la clase base tiene a su vez otra clase base, esa segunda clase base se cargara, y asi sucesivamente. A continuacin, se realiza la inicializacin static en la clase base raz (en este caso. Insect). 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 asignan los valores predeterminados a todas las primitivas de este objeto y se configuran las referencias a objetos con el valor nuil (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- tmetor de la clasc base (como primera operacin dentro del constructor 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 complete 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 nene lugar una vez. Demuestre que la carga puede

ser provocada por la creacin de la primera instancia de esa clase o por el acceso i un miembro esttico de la misma.

Ejercicio 24: (2) En Beetle.java. defina una nueva clasc que represente un tipo especifico de la clase Beetie de la que de

7 Reutilizacin de clases 462 be heredar, siguiendo el mismo formato que las clases existentes. Trace y explique los resultados de salida. Resumen

Tanto la herencia como la composicin permiten crear nuevos tipos a partir de los tipos existentes. La composicin rcutt* li/a los tipos existentes como parte de la implementacin subyacente del nuevo tipo, mientras que la herencia reutiliza la interfaz.

Con la herencia, la clase derivada liene la interfaz de la clase base, asi que puede ser generalizada hacia la base, lo cual resulta critico para el polimorfismo, como veremos en el siguiente capitulo.

A pesar del gran nfasis que se pone en las cuestiones de herencia cuando hablamos de programacin orientada a objetos, al comenzar un diseo 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 aadido 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 modificar 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 especifico y no sea ni demasiado grande (que abarque lauta funcionalidad que sea difcil de reuttlizar) tu incmodamente pequea (de modo que no se pueda emplear por si misma o sin aadirla funcionalidad). Cuando los diseos comienzan a complicarse

7 Reutilizacin de clases 463 demasiado, suele resultar til aadir ms objetos descomponiendo los existentes en otros mas pequeos.

Cuando se proponga disear 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 diseo 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 construir 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 enconirnr las soluciones n los ejercicios seleccionados en el documento electrnico The Thinking in Java Annotated Solution Guide, disponible para la \ cilia cn 'MlMindVie^.wi.Polimorfismo

8 *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 (17911871)

Fl polimorfismo es la tercera de las caractersticas esenciales de un lenguaje de programacin orientado a objetos, despus de la abstraccin de datos y de la herencia.

Proporciona otra dimensin de separacin entre la interfaz y la implemcntacin. con el tin de desacoplar el qu con respecto al cmo. El polimorfismo permite mejorar la organizacin y la legibilidad de cdigo, as como crear programas amplia- bles que puedan hacerse crecer no slo durante el proceso original de desarrollo del proyecto, sino tambin cada vez que se desean adherir nuevas caractersticas.

El mecanismo de cncapsulacin crea nuevos tipos de datos combinando diversas caractersticas y comportamientos. La tcnica de la ocultacin de la implemcntacin separa la interfaz de la implementacin haciendo que los detalles sean de tipo privado. Este tipo de organizacin mecnica tiene bastante sentido para las personas que tengan experiencia con la programacin procedimental. Pero el polimorfismo trata la cuestin del acoplamiento en trminos de tipos. En el ltimo capitulo, hemos visto que la herencia permite tratar un objeto como si fuera de su propio tipo o como si fuera del tipo base. Esta capacidad resulta critica, porque permite tratar varios tipos (todos ellos derivados del mismo tipo base) como si fueran un nico tipo, pudindose utilizar un mismo fragmento de cdigo para procesar de la misma manera todos esos tipos diferentes La llamada a un mtodo polimrfico permite que cada tipo exprese su distincin con respecto a los otros

tipo> similares siempre y cuando ambos deriven del mismo tipo base. Esta distincin se expresa mediante diferencias en el comportamiento de los mtodos que se pueden invocar .1 travs de la clase base.

En este capitulo, vamos a estudiar el tema del polimorfismo (tambin denominado acoplamiento dinmico o acoplamiento tardo o acoplamiento en tiempo de ejecucin) comenzando por los conceptos ms bsicos y proporcionando ejemplos simples en los que nos fijremos tan slo en el comportamiento polimrfico de los programas Nuevas consideraciones sobre la generalizacin

En el ltimo captulo hemos visto cmo puede utilizarse un objeto como si fuera de su propio tipo o como si fuera un objeto del tipo base. El acto de tomar una referencia a un objeto y tratarla como si fuera una referencia a su tipo base se denomina generalizacin (upeasting) debido a la forma en que se dibujan los rboles de herencia, en los que la clase base se suele representar en la parte superior.

lambin vimos en el capitulo anterior cmo surgi el problema a este respecto, el cual se ilustra en el siguiente ejemplo sobre instrumentos musicales.

En pnmer lugar, puesto que en muchos de estos ejemplos los instrumentos hacen sonar notas (Note), vamos a crear una enumeracin separada Note, dentro de un paquete: //: polymorphism/music/Note.java // Notas para tocar en los instrumentos musicales.

package polymcrphiflm.music;

r public enum Note ( MIDDI.E_C. C_SHASP, S_FLAT; // Etc.

) ///=-

Los tipos en u 111 se han presentado en el Capitulo 5. Inicial ilacin i limpieza.

Aqu Wind es un tipo de Instrument; por tanto. ind hereda de Instrument: i ( t palyrnorphism/music/Inscniineat .java

package polymorphism.music; import static net.mindview.util.Print.; class Instrument { public void play(Note n) { print ("Instrument ,play{)M};

>

) tu-//; polymorphism/music/Wind.java package polymorphism.music/ // Los objetos Wind son instrumentos // porque tienen la misma interfaz; public class Wind extenas Instrument { // Redefinicin de un mtodo de la interfaz: public void play(Note n> {

System.out.printIn("Wind .play() + n);

} m-.I I \ poiymorphism/music/Music.java // Herencia y generalizacin, package polymorphism.music; public class Music { public static void tuneInstrument i) ( U ,,,

i.playlNote.MIDDLE_CI i

public static void main(String[] srgs> ( Wind flute * new Wind{); tunellute); // Generalizacin

! } /* Output: Wind.playO MIDDLE_C *///-

El mtodo Musc.tune< ) acepta una referencia a Instrument, pero tambin a cualquier cosa que se derive de Instrument. Podemos ver que esto sucede en main( ), donde se pasa una referencia W nd a tune( ). sin que sea necesario efectuar ninguna proyeccin. Esto resulta perfectamente lgico: la interfaz de Instrument debe existir en Wind. porque W ind hereda de Instrument. I.a generalizacin de W ind a Instrument puede estrechar" dicha interfaz, pero en ningn caso esa interfaz podr llegar a ser ms pequea que la interfaz completa de Instrument Por qu olvidar el tipo de un objeto

Music.java puede resultarle un poco extrao. Por qu alguien debera olvidar intencionadamente el tipo de un objeto? Esto es lo que sucede cuando efectuamos una generalizacin, y parece que seria mucho ms sencillo si tunet) simplemente tomar una referencia a Wind como argumento. Esto plantea un punto esencial %i hiciramos eso. necesitaramos escribir un

nuevo mtodo tune( ) para cada clase derivada de Instrument que incluyramos en nuestro sistema. Suponga que siguironla esta forma de razonar y aadiramos dos instrumentos Stringed (instrumentos de cuerda) > Brass (instrumentos de metal): polymorphism/music/Music2 . java Sobrecarga en lugar de generalizacin, package polymorphisnumusic; import static net.mindview.util.Print.; class Stringed extends Instrument { public voxd play(Note n) ( print ("Stringed. pl ay i) * + ni /

i j class Bras3 extends Instrument ( public void playNote n) { orine("Brass.playM " n); ) ) public class Music2 { public static void tune(Wmd i) ( i .play (Note. MIDDLE_C) ; 1 public static void tune(Stringed iJ ( i.play(Note.MIDLE_C); l public static void tune(Brass i) ( x.piay(Note.MIDDLE_C);

) public static void main(StringtJ args) ( Wind flute new Wind O ; Stringed vlolin = new Strlnaedl); Brass frenchHom = new Brass(),* tune(flute); // Sin generalizacin tune(violn);

tune f renchHo m 1 :

} ) / Output: Wind.play( } XIDDLE_C Stringed.playO MIDDLE_C Brass.play(J MIDDLE_C ///:-

Esta solucin funciona, pero presenta una desventaja 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 programacin, pero tambin quiere decir que si queremos aadir un nuevo mtodo como tune( ) 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 compilado- no nos dara ningn mensaje de error si nos olvidamos de sobrecargar alguno de los mtodos, todo el proceso de gestin de los tipos se vuelve inmanejable.

No sera mucho ms fcil, si pudiramos, limitamos a escribir un nico mtodo que tomara la clase base como argumento y no ninguna de las clases derivadas especificas' En otras palabras: no seria mucho ms adecuado si pudiramos olvidamos de que hay clases derivadas y escribir el cdigo de manera que slo se entendiera con la clase base?

Eso es exactamente lo que el polimorfismo nos permite hacer. Sin embargo, la mayora de los programadores que proceden del campo de los lenguajes de programacin proced menta Ies suelen tener problemas a la hora de entender cmo funciona el polimorfismo.

Ejercicio 1: (2) Cree una elase Cycle. con subclases Unicycle. Blcycle y Tricyde. Demuestre que se puede generali

zar una inslancia de cada tipo a Cycle mediante un mtodo ride< ) El secreto

La dificultad con Musfe.java puede verse ejecutando el programa. La salida es \Vnd.play( ). Se trata claramente de la salida deseada, pero no parece tener sentido que el programa funcione de esa forma. Examinemos el mtodo tune( ): pubile static void cune(Xr.scrument i) ( i.play(Note.MIBDLE_C); // ...

El mtodo recibe una referencia a Instruuunt De modo que cmo puede el compilador saber que esta referencia a Instruiiient apunta a un objeto Wind en este caso y no a un objeto Brass o Stringcd'.' El compilador 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 v el montador, si es que existe uno), el proceso se llama acopiamiento temprano (early binding). Puede que haya odo este trmino antes porque en los lenguajes proeedimen- tales, como por ejemplo C'. slo existe un tipo de llamadas a mtodos y ese tipo es precisamente, el acoplamiento temprano. 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 Instruinent.

La solucin es el acoplamiento tarx/io [late binding), que quiere decir que el acoplamiento tiene lugar en tiempo de ejecucin basndose en el tipo del objeto. El acoplamiento tardo tambin se denomina acoplamiento dinmica o acoplamiento en tiempo de ejecucin. Cuando un lenguaje implementa el mecanismo de acoplamiento tardo, debe haber alguna manera de determinar el tipo del objeto en tiempo de ejecucin, con el fui de llamar al mtodo apropiado. En otras palabras, el compilador sigue sin saber cul es el tipo del objeto, pero el mecanismo de invocacin del mtodo lo averigua y llama al cuerpo 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 informacin 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 prvale son implicitamente final). Esto quiere decir que, normalmente, no es necesario tomar ninguna decisin acerca de si debe producirse el acoplamiento tardo, ya que ste tendr lugar automticamente.

<fParu qu quemamos declarar un mtodo como final? Como hemos indicado en el capitulo anterior, esto evita que nadie pueda sustituir dicho mtodo en Las clases derivadas Adems, y todava ms importante, esta palabra clave desactiva en la practica 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 final. 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 polimrficamente a travs del acoplamiento tardo, podemos escribir el codigo de forma que se comunique con la clase base, a sabiendas de que lodos los casos donde estn involucradas las clases derivadas funcionarn correctamente con el mismo cdigo. O. 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 formas". Se suele utilizar comunmente porque resulta 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 cieno.

El ejemplo de las formas tiene una clase base denominada Shape (forma) y varios tipos derivados: Crcle (circulo), Squarc (cuadrado). Triangle (tringulo), etc. La ra/n por la que este ejemplo es tan adecuado es porque es fcil decir un circulo es un tipo de forma" y que el lector lo entienda. El diagrama de herencia muestra las relaciones: La generalizacin puede tener lugar en una instruccin tan simple como la siguiente. Shape a = new Circlet);

Aqu, se crea un objeto Circle, y la referencia resultante se asigna inmediatamente a un objeto Shape, (lo que podra parecer un error asignar un tipo a otro); sin embargo, es perfectamente correcto, porque un objeto Circle vs una forma (Shape) debido a la herencia. Por tanto, el compilador aceptar la instruccin v no generar ningn 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 como podra el compilador hacer cualquier otra cosa*7 Sin embargo, se invoca el mtodo apropiado Circlc.draw() debido al acoplamiento tardo (polimorfismo).

El siguiente ejemplo presenta las formas de una manera ligeramente distinta. En primer lugar, vamos a crear una biblioteca reutih/able de tipos Shape //: polymorphism/shape/Shape.java package polymorphism.shape; public class Shape { public void drawn () public void erase() {]

) ///:/ /: polymorphism/shape/Circie.3ava package polymorphism. shape import static net.mindview.util.Print.; public class Circle extends Shape ( public void drawf) ( print("Circle.draw()") ; } public void erase 0 ( print ("Circle, erase ()"l ; } ) m/ / : polymorphism/shape./Square . java package polymorphism.shape; import static net.mindview.util.Print . * ; public class Square extends Shape { public void drawO ( print ("Square, drawf)") j J

public void erase I)

printt"Square.erase(I M} / }

) ///'./(: polymorphism/shape/Triangle.java package polymorphism.shape; import static net .mmdview.util.Print * ; public class Triangle extends Shape ( public void drawO ( print ("Triangle. draw U ; ) public void erase () { print ("Triangle - erase ()"'); ) } m-.//: polymorphism/shape/RandomShapeGenerator.j ava // Una "fbrica" que genera formas aleatoriamente, package polymorphism.shape; lmport 3 ava,ut il.*; 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();

I i ///<//: polymorphism/Shapes.java // Polimorfismo en Java, import pclymorphi sm.shape.; public class Shapes ( private static RandomShapeGenerator gen new RandomShapeGenerator0; public static void main(String[] args) { Shaped s * new Shape f 9]; // Rellena la matriz con formas: for lint i = 0 ; i <r s.length; i*+) s(i) gen.next(); // Realiza llamadas a mtodos polimrficos: for(Shape shp : s) shp.draw();

) ) /* Output: Triangle.draw (J Trtangle.draw {) Square.draw() Triangle.draw() Square.draw() Triangle. drawO Square.draw() Triangle.draw() Circle.draw I)

///:

La clase base Shape establece la interfaz comn para cualquier otra clase que herede de Shape; en trminos conceptuales, representa a todas las formas que puedan dibujarse y borrarse. Cada clase derivada sustituye estas definiciones con el fin de proporcionar un comportamiento distintivo para cada tipo especfico de forma.

RandomMiapecnerator es una especie de fbrica'* que genera una referencia a un objeto Shape aleatoriamente seleccionado 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 C'ircle. Square o Triangle y la devuelve desde ne\t() con el tipo de retomo. Shape. Por tanto, cada vez que se invoca ne\t( ), nunca tenemos la oprotumdad de ver de que tipo especifico se trata, ya qje siempre obtenemos una referencia genrica a Shape

main) contiene una matriz de referencias Shape que se rellena mediante llamadas a RandomShapefcnerator.ne\t( ). En este punto, sabemos que tenemos objetos Shape. pero no podemos ser ms especficos (ni tampoco puede serlo el compilador). Sin embargo, cuando recorremos esta matriz e invocamos draw( ) para cada objeto, tiene lugar el comportamiento correspondiente a cada tipo especfico, 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 formas aleatoriamente es que asi puede percibirse mejor que el compilador no puede tener ningn conocimiento especial que le permite hacer las llamadas correctas en tiempo de compilacin. Todas las llamadas a dra\\() deben tener lugar mediante el mecanismo de acoplamiento dinmico

Ejercicio 2: (I) Aada la anotacin ( Override al ejemplo de procesamiento de formas.

Ejercicio 3: (I) 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, sustituyalo en una de las clases derivadas pero no en las otras y vea lo que sucede. Finalmente, sustituyalo en todas las clases derivadas.

Ejercicio 4: (2) Aada un nuevo tipo de objeto Shape a Shapes.java y verifique en main ) que el polimorfismo fun

ciona para el nuevo tipo al igual que para los tipos anteriores.

Ejercicio 5: (l) Partiendo del Ejercicio l, aada un mtodo wheels() a Cycle, que devuelva el nmero de ruedas.

Modifique ride( ) para invocar wheels() y ven fique que funciona el polimorfismo. Ampliabilidad

Volvamos ahora al ejemplo de los instrumentos musicales. Debido al polimorfismo, podemos aadir al sistema todos los nuevos tipos que deseemos sin modificar el mtodo iune( ). fcn un programa orientado a objetos bien diseado, la mayora de los mtodos lo todos ellos) seguirn el mtodo de tune( ) y slo se comunicarn con la interfaz de la clase base. Ese tipo

de programas es extensible aadir nueva funcionalidad datos a partir de la clase base manipulan la interfaz de la modificados para poder utilizar

[ampliable) porque puede heredando nuevos tipos de comn Los mtodos que clase base no necesitaran ser las nuevas clases.

Considere lo que sucede si tomamos el ejemplo de los instrumentos v aadimos ms mtodos a la clase base y una serie de clases nuevas. Puede ver el diagrama correspondiente al llnal de la pgina anterior.

Todas estas nuevas clases funcionan correctamente con el mtodo antiguo Uine( ). sin necesidad de modificarlo. Incluso si tune() se encontrara en un archivo separado y aadiramos nuevos mtodos a la interfaz de Instrument. tunc( ) seguira funcionando correctamente, sin necesidad de recompilarlo. He aqu la implemcntacin del diagrama: //: polymarphism/mu8c3/Music3 .java

// Un programa ampliable. package polymorphism.music3; import polymorphism.music.Note; import static net .mindview.util. Print. ; class Instrument ( voidplaytNote n) { print("Instrument.play() String what 0 ( return "Instrument*; ) void adjust{) ( print("Adjusting Instrument"); | l class Wind extends Instrument { void play(Note n) f print("Wind.play() " n)j ) String what 0 ( return "Wind"; ) void adjustO ( print("Adjusting Wind"); ) H n); )

) class Percussion extends Instrument ( voidplaylNote n) ( print I "Percussion.play U String what() ( return "Percussion"; ) void adjust () ( print "Adjusting Percussion'*); ) " * n); )

} class Stringed extends Instrument ( voidplayiNote n) \ {printfStringed.play() * 4 n);

String what() ( return "Stringed"; ) voidadjust() ( print I"Adjusting Stringed"); )

} class Brass extends Wind j voidplayiNote n) ( print<MBrass.play\) " + n); } voidadjust()| print("Adjusting Brass"/ }

) class Woodwind extends Wind ( voidplay(Note n) } (print("Woodwind.play{) " T n);

String whatO ( return "Woodwind"; )

) public class Music3 { // No importa el tipo, por lo que los nuevc3 // tipos aadidos al sistema funcionan bien: public static void tune(Instrument i) { u ...
i.

play Note.MIDDLE C);

i 8 Polimorfismo 483 public static void tuneAll(Instrument[J e) ( for(Instrument i : e)tune(i); public static void mainStringU args) ( f/ pcasting durante la adicin a la matriz: Instrument l] orchestra = ( new Wind{), new Percussiont) , new StringedO, new Brass(). new WoodwindO t uneAl 1 (orchest ra! ,*

} ) / Output: Wind.play0 MIDDLE_C Percussion.playO MIDDLE_C Stringed. playO MIDDLE_C Brass.play(J MIDDLE_C woodwind. play () MIDDLE_C ///:-

Los nuevos mtodos son what( ). que devuelve una referencia String con una descripcin de la clase y adjust( ), que proporciona alguna forma de ajustar cada instrumento.

bn main( ). cuando insertamos algo dentro de la matriz orchestra. se produce automticamente una generalizacin a Instrument.

Podemos ver que el mtodo tune< ) es completamente ignorante de todos los cambios de cdigo que han tenido lugar alrededor suyo, a pesar de lo cual sigue funcionando perfectamente. sta es. exactamente. la funcionalidad que se supone que el polimorfismo debe proporcionar. Los cambios en el cdigo no generan

i 8 Polimorfismo 484 ningn problema en aquellas partes del programa que no deban verse afectadas. Dicho de otra forma, el polimorfismo es una tcnica importante con la que el programador puede "separar las cosas que cambian de las cosas que permanecen.

Ejercicio 6: raiz

(1)Modifique Music3.java de modo que whaf() se convierta en el mtodo toString() del objeto

Object. Pruebe a imprimir los objetos Instrument utilizando System.out.println( ) (sin efectuar ninguna proyeccin de tipo).

Ejercicio 7: funciona

(2)Aadaunnuevo

tipo de objeto Instrument a Music3.java y verifique que el polimorfismo

para el nuevo tipo.

Ejercicio 8:

(2)Modifique!Muslc3.javapara que

genere aleatoriamente

objetos Instrument de la

i 8 Polimorfismo 485 misma forma que lo

hace Shapes.java.

Ejercicio 9: (3K ree una jerarquiaa de herencia Rodent .Mouse. Gerbil. Hmster, etc (roedor ratn, jerbe. hmster.

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 comportamientos dependiendo del tipo especifico de roedor. Cree una matriz de objetos Rodent. rellnela con diferentes tipos especficos de roedores e invoque los mtodos de la clase base para ver lo que sucede.

Ejercicio 10: (3) Cree una clase base con dos mtodos. Ln el primer mtodo, invoque el segundo mtodo. Defina una

i 8 Polimorfismo 486 clase que herede de la anterior y sustituya el segundo mtodo. Cree un objeto de la clase derivada, realice una generalizacin (u/Kasting) al tipo base y llame al primer mtodo. Explique lo que sucede. Error: sustitucin de mtodos private

He aqu un ejemplo de error de un programa que se puede cometer de manera inadvertida; //: polymorphism/Pn vateOverride.java // Intento de sustituir un mtodo privado. package polymorphism; import static net.mindview.util.Print.*; public class PrivateOverride ( prvate void f() ( print("prvate fO"); ) public static void main IString [] args) ( PrivateOverride po = new Derived O; po.fO;

) class Derived extends PrivateOverride { public void () { print^"public } } /+ Output: private f()

i 8 Polimorfismo 487 ///:-

Podra esperar, razonablemente, que la salida fuera public f( ). pero los mtodos privados son automticamente de tipo final, y estn tambin ocultos a ojos de la clase derivada. 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 incorrecto de sustituir mtodos de tipo prvate, ya que esos intentos no generan ninguna advertencia del compilador, sino que el sistema no har, seguramente, lo que se espera. Para evitar las confusiones, conviene utilizar en la clase derivada un nombre diferente al del mtodo private de la clase base. Error: campos y mtodos static

Una vez familiarizados con el tema del polimorfismo, podemos tender a pensar que todo ocurre polmdicamente. Sin embargo, las nicas llamadas que pueden ser polimorficas son las llamadas a mtodos normales. Por ejemplo, si accedemos a un campo directamente, ese acceso se resolver en tiempo de compilacin, como se ilustra en el siguiente ejemplo:18 //: polymorphism/FieldAccess.java // El acceso directo a un campo se determina en tiempo de compilacin. class Super { public int field * 0; public int getFieldO ( retum field; }

18

Gracia* a Ratuiy NichoU por planteat esla cuestin.

i 8 Polimorfismo 488 ) class Sub extends Super { public int field = l; public int getFieldO ( retum field; ) public int getSuperField() ( retum super.field; }

) public class FieldAccess { public static void main(Stringfj args) { Super sup new Sub () ; // Upcast System.out.println("sup.field * + sup.field + ", sup.getField() sup.getField()); Sub sub new Sub () ; System.out .println ("sub. field = " - sub.field ", sub.getField() = " + sub.getFieldO ** ", sub.getSuperFieldO * * sub.getSuperField 01;

> | / OUtpfUC: sup-field =* Q, sup.aetField( ) * 1 sub.field * 1, sub.aetFieldi) = i, sub.getSuperField() = 0 ///:-

Cuando un objeto Sub se generaliza a una referencia Super. los accesos a los campos son resueltos por el compilador, por

i 8 Polimorfismo 489 lo que no son polimrficos. En este ejemplo, hay asignado un espacio de almacenamiento distinto para Super.field y Sub.field Por tanto. Sub contiene realmente dos campos denominados field: el suyo propio y el que obtiene a partir de Super. Sin embargo, cuando se hace referencia al campo field de Super no se genera de forma predeterminada una referencia a la versin almacenada en Super; para poder acceder al campo field de Super es necesario escribir explcitamente super.field.

Aunque esto ltimo pueda parecer algo confuso, en la prctica no llega a plantearse casi nunca, por una razn: por regla general, se definen todos los campos como prvate, 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 mismo nombre de la clase buse a un campo de la clase derivada, ya que eso resultara muy confuso.

Si un mtodo es de tipo static. no se compona de forma poliinrfca //: polymorphism/StaticPolymorphism. java Los mtodos estticos no son polimrficos. class StaticSuper ( public static String staticGetO ( return Base staticGetOHM;

) public String dynamicGet0 { return "Base dynamicGet 0M ;

i 8 Polimorfismo 490 )

} class StaticSub extends StaticSuper { public static String staticGetO { return "Derived staticGetO";

) public String dynamicGet() ( return "Derived dynamicGet (),*

} i public class StaticPolymorphism { public static void main (String [] args) ( StaticSuper sup = new StaticSubO; // Generalizacin System.out .println(sup.staticGet ()); System.out .println(sup.dynamicGet {} ) ;

} ( / * Output:

i 8 Polimorfismo 491 Base staticGetO Derived dynamicGet()

///:-

Los mtodos estticos estn asociados con la clase y no con los objetos individuales. Constructores y polimorfismo

Como suele suceder, los constructores difieren de los otros tipos de mtodos, tambin en lo que respecta al polimorfismo. Aunque los constructores no son polimrficos (se trata realmente de mtodos estticos, pero la declaracin static es implcita). tiene gran importancia comprender cul es la forma en que funcionan los constructores dentro de las jerarquas complejas y en presencia de polimorfismo. Esta compresin de los fundamentos nos ayudar a evitar errores desagradables Orden de las llamadas a los constructores

Hemos hablado brevemente del orden de las llamadas a Jos constructores en el Capitulo 5. niaalbadn v limpieza, y tambin el Captulo 7. Reutilizacin Je clases, pero eso fue antes de introducir el concepto de polimorfismo.

i 8 Polimorfismo 492 El constructor de la clase base siempre se invoca durante el proceso de construccin correspondiente a una clase derivada. Esta llamada provoca un desplazamiento automtico hacia arriba en la jerarqua de herencia, invocndose un constructor para todas las clases base. Esto tiene bastante sentido, porque el constructor tiene asignada una tarea especial: garantizar que el objeto se construye apropiadamente Una clase derivada slo tiene acceso a sus propios miembros y no a los de la clase base (aquellos miembros tpicamente de tipo prvate). 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 constructores, en caso contrario, no podra construirse el mtodo completo. Esta es la razn por la que el compilador impone que se realice una llamada al constructor para cada pane de una clase deriv ada. Si no especificamos explcitamente una llamada a un constructor de la clase base dentro del cuerpo de la clase derivada, el compilador invocar de manera automtica el constructor predeterminado. Si no hay ningn constructor predeterminado, el compilador generar un error (en aquellos casos en que una determinada clase no tenga ningn constructor, el compilador sintetizar automticamente un constructor predeterminado).

Veamos un ejemplo que muestra los efectos de la composicin, de la herencia y del polimorfismo sobre el orden de construccin: //: polymorphism/Sandwich.java / / Orden de las llamadas a los constructores. package polymcrphism; import static net.mindview.til.Print.; class Meal ( Meal O { print {"Meal O MJ ; ) class Bread ( Bread!) ( print ("Eread() w) ,* )

) class Cheese ( Cheese0 ( print < "Cheese O") ; }

i 8 Polimorfismo 493 ) class Lettuce ( LettuceO [ print tLettuce O nl ; ) i class Lunch extends Meal ( Lunch() [ print!"Lunch0 ") ; )

} class PortableLunch extends Lunch ( PortableLunch0 ( print"PortableLunchO");)

) public class Sandwich extends PortableLunch { private Bread b = new Breado prvate Cheese c = new Cheese t); private Lettuce 1 = new LettuceO ; public Sandwich{) { print("Sandwich O H > ; } public static void mam(Stringf] args) { new Sandwich < ) ) ) /* Output: Meal( )

Lunch () P o r tableLunchO BreadO Cheeae() LettuceO Sandwich(i *///:-

i 8 Polimorfismo 494 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 constructores para un objeto complejo es el siguiente:

1.

Se invoca al constructor de la clase base Este paso se repite de forma recursiva de modo que la raz de la jerarqua se construye en primer lugar, seguida de la siguiente clase derivada, etc., hasta alcanzar la clase situada en el nivel ms profundo de la jerarqua.

2.

Los inicializadores de los miembros se invocan segn el orden de declaracin.

3.

Se invoca el cuerpo del constructor 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 puhlic 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 derivada. En un mtodo normal, 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 umea forma de garantizar esto es invocando primero al constructor de la clase base. Entonces, cuando nos encontremos dentro del constructor de la clase derivada, todos los miembros de la clase base a ios que queremos acceder ya habrn sido inicializados. Saber que todos los miembros son vlidos dentro del constructor es tambin la razn de que. siempre que sea posible, se deban micializar todos los

i 8 Polimorfismo 495 objetos miembro (los objetos incluidos en la clase mediante los mecanismos de composicin) en su punto de definicin dentro de la clase (por ejemplo,

c y I en el ejemplo anterior). Si se ajusta a esta practica 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 inicial izados. I amentablemenie, este sistema no nos permite gestionar todos los casos, como veremos en la siguiente seccin.
b.

Ejercicio 11: (l) Aada una clase Pickle a Sandwch.java. Herencia y limpieza

Cuando se utilizan los mecanismos 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 normalmente dejarse para que los procese eJ depurador de memoria. Sin embargo, si hay algn problema relativo a la limpieza, es necesario actuar con diligencia y crear un mtodo dispnse( ) (ste es el nombre que yo he seleccionado, pero usted puede utilizar cualquier otro que indique que estamos deshacindonos del objeto) en la nueva clase. Y, con la herencia, es necesario sustituir disposc< ) en la clase derivada si necesitamos realizar alguna tarea de limpieza especial que tenga que tener lugar como piule 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: //: polymorphi sm/Frog.j ava // Limpieza y herencia, package polymorphism; import static net.mindview.util.Print. * ; class Characteristic ( prvate String s?

496 Piensa en Java Characteristic(String si | thia.s = s;print(Creating Characteristic * * s) ; protected void dispose(> { print<"disoosina Characteristic * s ) ; }

) class Description { private String s; Description(String s) { this.s s; print ("Creating Description " + s) ;

] protected void dispose() ( print ('disposing Description " * s); I

} class LivingCreature ( private Characteristic p = new Characteristic("is alive"); private Description t =

8 Polimorfismo 497 new Description!"Basic LivingCreature() ( print("LivingCreature()"); Living Creature");

) protected void dispose() ( print("LivingCreature dispose" ) ; t.dispose(}; p.dispose 0 :

> class Animal extends LivingCreature ( private Characteristic p = new Characteristic("has heart"); private Description t * new Description<"Animal not VegetableH); Animal() { print 1"Animal()"); ) protected void dispose() ( print["Animal dispose) ; t.dispose(); p.dispose(); super.dispose() ;

498 Piensa en Java i

) class Amphibian extends Animal { private Characteristic p = new characteristic("can Description t = print("Amphibian{)"); live in water"); private

new Description!"Both water and land"); Amphibian() {

} protected void dispose() { print("Amphibian dispose") ; t.dispose() ;p.dispose ) r super.di spose();

8 Polimorfismo 499 ) public class Frcg extends Amphibian ( private Characteristic p * new Characteristic("Croaks"I; private Description t = new Description("Eats Bugs*'J; public Frog() ( print("Frog 0 *>; } protected void dispose() ( print I"Frog dispose"); t.dispose(); p.dispose 0 ; super.dispose(i; i public static void mainiString[] args? { Frog frog * new FrogO; print ("Bye! " ) ; frog.dispose();

) ) /* Output: Creating Characteristic is alive Creating Description Basic Living Creature LvingCreatureI) Creating Characteristic has heart Creating Description Animal not Vegetable Animal() Creating Characteristic can live in water Creating Description Both water and land Amphibian{) Creating Characteristi c Croaks Creating Description

500 Piensa en Java Eats Frog( ) Bye! Frog dispose disposing Description Eats Bugs disposing Characteristi c 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 LivmgCreature dispose disposing Description Basic Living Creature disposing Characteristic is alive Bugs

*///:-

Cada clase de la jerarqua tambin contiene objetos miembro de los tipos Characteristic y

8 Polimorfismo 501 Description, que tambin habr que borrar. Fl 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 inicializan en el orden de declaracin) Para las clases base (siguiendo la norma utilizada en C++ para los destructores), debemos realizar primero las tareas de limpieza 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, asi 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 llevan a cabo es preciso hacerlo con un gran cuidado y una gran atencin

Ejercicio 12: (3) Modifique e) Ejercicio 9 para que se muestre el orden de inicializaeiu de las clases base y de las cla

ses derivadas. Ahora aada objetos miembro a las clases base y derivadas, y muestre el orden en que se lleva a cabo la micializacion durante el proceso de construccin.

502 Piensa en Java Observ e tambin en el ejemplo anterior que un objeto Frog posee sus objetos miembro: crea esos objetos miembro y sabe durante cunto tiempo tienen que existir (tanto como dure el objeto Frog). de modo que sabe cundo invocar el mtodo disposi ) para borrar los objetos miembro. Sin embargo, si uno de estos objetos miembro es compartido con otros objetos, el problema se vuelve ms complejo y no podemos simplemente asumir que basta con invocar dispose(). En estos casos, puede ser necesario un recuento de referencias para llevar la cuenta del nmero de objetos que siguen pudiendo acceder a un objeto compartido. He aqu un ejemplo: //: polymorphism/ReferenceCountlng. java // Limpieza de objetos miembro compartidos, import static net.mindview.util.Print.*j class Shared ( private int refcount * 0 ; private static long counter = 0 ; private final long id * counter 4 -+; public Shared(I { print("Creating " + this I;

) public void addRefd f ref count ; ) protected void dispose(I { if(--refcount == 0 ) print(Disposing M + this);

} public String toStringO ( return "Shared H + id; )

8 Polimorfismo 503 ) class Composing { private Shared shared; private static long counter = 0 ; private final long id counter+-r; public Composing(Shared shared) ( print ("Creating M this)? this.shared = shared; this. shared. addRef {) ,*

! protected void dispose 0 ( print"d isposing + this)/ shared.dis DOse();

) public String toStrina() ( return "Composing " * id; } J public class ReferenceCounting ( public static void main(String[] arqs) ( Shared shared = new Shared()/ Composing[J composing = ( new Composing(shared), new Composing(shared), new Composing(shared), new Composing(shared), new

504 Piensa en Java Composing(shared) for(Composing composing) c.dispose(); c ); :

) ) / Output: Creating Shared 0 Creating Composing 0 Creating Composing 1 Creating Campos ing 2 Creati ng Compos ing 3 Creati ng Compos ing 4 dispos ing Compos ing 0 dispos ing Compos ing 1 di sposin g Compos ing 2 ispos ing Compos ing 3 dispos ing Compos ing 4 Dispcs ina Shared

8 Polimorfismo 505 0 ///i

El contador stalic long counter lleva la cuenta del numero de instancias de Shared que son creadas y tambin crea un valor para id El tipo de counter 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 ejemplos de este 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( ) llevar 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 lleve a cabo una determinada tarea de limpieza, no son muchas las opciones que tenemos.

Ejercicio 13: (3) Aada un mtodo rmalize( ) a ReferenceCounting.java para verificar la condicin de terminacin (vase el Capitulo 5, Inicial ilacin y limpieza).

506 Piensa en Java Ejercicio 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 constructores de los mtodos polimrficos dentro de los

La jerarqua de llamada a constructores plantea un dilema interesante. Qu sucede si estamos dentro de un constructor e invocamos un mtodo con acoplamiento dinmico del objeto que est siendo construido?

Dentro de un mtodo normal, 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 misma.

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

8 Polimorfismo 507 se encuentra en la clase actual). Sin embargo, el efecto de esta llamada puede ser inesperado, porque el mtodo sustituido ser invocado antes de que el objeto haya sido completamente construido. Esto puede hacer que queden ocultos algunos errores realmente difciles de detectar.

Conceptual mente, la tarea del constructor es hacer que el objeto comience a existir (lo que no es una tarea trivial). Dentro de cualquier constructor, puede que el objeto completo slo est formado parcialmente, ya que de lo nico que podemos estar seguros es de que los objetos de la clase base han sido nicializados Si el constructor es slo uno de los pasos a la hora de construir un objeto de una clase que haya sido derivada de la clase correspondiente a dicho constructor, las partes derivadas no habrn sido todava inicializadas en el momento en que se invoque al constructor actual. Sin embargo, una llamada 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 micializados. lo cual constituye una receta segura para que se produzca un desastre

Podemos ver el problema en el siguiente ejemplo: //: polymorphism/PolyConstructo rs.java // Los constructores en presencia de polimorfismo // pueden no producir los resultados esperados, imporc static net.mindview.til.Print.*; class Glyph ( void draw() | prmt ("Glyph.draw()n); } GlyphO ( prrnt ("Glyph () before draw 1") ;

508 Piensa en Java draw(); print("Glyphi) after draw(>">; i

} class RoundGlyph extends Glyph { prvate int radlus 1 ; RoundGlyph(Int r) ( radius * r; print ("RoundGlyph. radius); ) void draw<) ( print("RoundGlyph.drawO, radius * H + radius); RoundGlyph O . radius = ** +

) public class PolyConstructors (

8 Polimorfismo 509 public static void main(Stringti args) { new RoundGlyph(5 >; J } /* Output: Glyph() before drawi) RoundGlyph.dra w()f radius = 0 GlyphO after draw() RoundGlyph.RoundG lyph<), radius = 5 *///=-

Glyph.dra>v( ) est diseado para ser sustituido, lo que se produce en RoundGlyph Pero el constructor de Glyph invoca este mtodo y la llamada termina en RoundGlyph.dra\v( ). que parece que fuera la intencin original. Pero si examinamos la salida, podemos ver que cuando el constructor de Glvph invoca dravv( ). el valor de radius no es ni siquiera el valor inicial predeterminado de 1. sino que es 0. 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 ahi es donde radica lu clave para resolver el misterio. El proceso real de inicializacin es:

510 Piensa en Java El almacenamiento asignado al objeto se inicializa con ceros binarios antes de que suceda ninguna otra cosa.
1.

Los constructores de las clases base se invocan tal y como hemos descrito anteriormente. En este punto se invoca el mtodo sustituido draw( ) (si, se invoca antes de que llame al constructor de RoundGlyph) y ste descubre que el valor de radius es cero, debido al Paso 1.
2.

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 nuil Por tanto, si nos olvidamos de inicializar esa referencia, se generar una excepcin en tiempo de ejecucin. Todo lo dems lomar el valor cero, lo que usualmente nos sirve como pista a la hora de examinar la salida.

8 Polimorfismo 511 Por otro lado, es posible que el programador se quede horrorizado al ver la salida de este programa: hemos hecho algo perfectamente lgico, a pesar de lo cual el comportamiento es misteriosamente errneo, sin que el compilador se haya quejado (C+-* 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 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, damos este Upo de sorpresas. Puede que no siempre seamos capaces de seguir esta direc- jn2. pero al menos debemos tratar de cumplirla.

Ejercicio 15: (2) Aada una clase KectanuiilarGIyph a PolvConstructors.java e ilustre el problema descrito en esta

seccin.

512 Piensa en Java Tipos de retorno covariantes

Java SE5 aade los denominados tipos de retorno covariantes, lo que quiere decir que un mtodo sustituido en una clase derivada puede devolver un tipo derivado del tipo devuelto por el mtodo de la clase base: //: polymorphism/Covariant Retum .java clas Grain { public String toStringU { retum "Grain"; ) j class Wheat extends Grain ( public String toStringU ( retum "Wheat"; }

) class Mili { Grain process() { retum new GrainO; ] l class WneatMill extends Mili { Wheat orocess) j retum new Wheat (); }

) public class CovanantRetum { public static void main(StringIJ args) {

8 Polimorfismo 513 Mili m - new Mili(); Grain g = m.processi ) ; System .out.p rintln (g); m = new WheatM ill{); g m.proc ess{ ) SyGtem.out .println(g);

) ] / Output! Grain Wheat

*///:-

La diferencia clave entre Java SE5 y las versiones anteriores es que en stas se obligara a que la versin sustituida de proct*ss( ) devolviera Grain, en lugar de Wheat, a pesar de que Wheat deriva de Grain y signe siendo, por tanto, un tipo de retomo legtimo. Los tipos de retorno covariantes permiten utilizar el tipo de retomo Wheat ms especifico.

514 Piensa en Java 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 polimorfismo es una herramienta tan inteligente. Pero la realidad es que esto puede complicar nuestros diseos innecesariamente, 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 herencia. Pero, asimismo, la composicin es ms flexible, porque permite seleccionar dinmicamente un tipo (y por tanto un compor- (amiento), mientras que la herencia exige que se conozca un tipo exacto en tiempo de compilacin. El siguiente ejemplo ilustra esto: //: polymorphism/Transmogrify.java // Modificacin dinmica del comportamiento de un objeto // mediante la composicin (el patrn de diseo basado en estados]. import static net.mindview.til.Print; class Actor ( public void actO ()

8 Polimorfismo 515 class HappyActor extends Actor { public void act<> { print("HapoyActor"); }

} class SadActor extends Actor ( public void act(l ( print <"SadActor") ,* )

i class Stage { prvate Actor actor = r.ew HappyActor () public void changeO ( actor = new SadActor(J; ) public void performPlayO { actor.actO; }

) public class Transmogrify { public static void main(String(l args) { Stage stage = new Stage O; stage.perf ormPlay( ) ;

516 Piensa en Java stage.chan ge(); stage.perf ormPlay();

} } / Output: HappyActor SadActor

V//:-

Un objeto Stage contiene una referencia a un objeto Actor, que se inicializa para que apunte a un objeto HappyActor. Esto significa que performP!ay( ) produce un comportamiento concreto. Pero, como una referencia puede redingirse a un objeto distinto en tiempo de ejecucin, podramos almacenar una referencia a un objeto SadActor en actor, y entonces el comportamiento producido por performPlav ) variara. Por tanto, obtenemos una mayor flexibilidad dinmica en tiempo de ejecucin (esto se denomina tambin patrn ci diseo basado en estados, consulte Thinking in Patterns (w'tth Java) en wwu. Mindtfew.net). Por contraste, no podemos decidir realizar la herencia de forma diferente en tiempo de ejecucin, el mecanismo de herencia debe estar perfectamente determinado en tiempo de compilacin.

8 Polimorfismo 517 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 mecanismos: definimos mediante herencia dos clases distintas para expresar la diferencia en el mtodo act( ) y Stage utiliza la composicin para permitir que su estado sea modificado. Dicho cambio de estado, en este caso, produce un cambio de comportamiento.

Ejercicio 16: (3) Siguiendo el ejemplo de Traiismoirify.java, cree una clase Starship que contenga una referencia

AlertStatus que pueda indicar tres estados distintos. Incluya mtodos para verificar los estados. Sustitucin y extensin Podra parecer que la forma ms limpia de crear una jerarqua de herencia seria 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

:Shape

draw() erase ()Triang le draw() erase{ )

Circle draw() erase()

Square draw() erase()

Esio

podra decirse que es una relacin de tipo *es-un porque la interfaz de una clase establece lo que dicha clase es La herencia garantiza que cualquier clase derivada tendr la interfaz de la clase base y nada ms. Si seguimos este diagrama, las clases derivadas no tendrn nuda ms que lo que la interfaz de la clase base ofrezca.

Esto podra considera

rse como una sustituci n pura, porque podemos sustituir perfecta mente un objeto de la clase base o un objeto de una clase derivada y no nos hace falta conocer ninguna informaci n adicional acerca de las subclases a la hora de utilizarlas : Habla con Shape Circle Square. Line o un nuevo tipo de Shape Mensaj e R el a ci n e su n "

En otras palabras, la clase base puede recibir cualquier mensaje que enviemos a la clase derivada, porque las des tienen exactame nte la misma interfaz. Debido a esto lo que tenemos que hacer es generaliz ar a partir de la clase derivada, sin tener que preocupa mos de ver cul es el tipo exacto del objeto con el que estemos tratando. Todo se maneja mediante el polimorfi smo. Cuando vemos las cosas de esta forma,

debe parecer que las relaciones puras de tipo esun son la forma ms lgica de implemen tar las cosas, y que cualquier otro tipo de diseo resulta confuso por comparac in. Pero esta forma de pensar es un error. Ian pronto comence mos a pensar de esta forma, miraremo sa nuestro alrededor y descubrir emos que ampliar la interfaz (mediante la palabra clave extends) es la perfecta solucin

para un problema concreto. Este tipo de solucin podra denomina rse relacin de tipo "es-comoun", porque la clase derivada es como la clase base: tiene la misma interfaz elemental y tiene, adems, otras caracterst icas que requieren mtodos adicionale s para implemen tarla sUsefu l void f() void g()

Su

ponga que esto y represe nta una interfaz complej a

Es -comoun MoreU seful void f() void g() voi d u() voi d v() voi d w()

Amplia cin de la interfaz

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 disponible en la clase base, por lo que. una vez que efectuemos una generalizacin no podremos invocar los nuevos mtodos: Mensaje Habla con el objeto Useful parte de Useful Si no estamos haciendo generalizaciones, no debe haber ningn problema, pero a menudo nos encontraremos en situaciones 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 (upeast, que consiste en moverse hacia arriba por la jerarqua de herencia), tiene bastante sentido que para extraer la informacin de tipos; es decir, para volver a descender por la jerarqua de herencia, utilicemos un proceso de especializacin (downcast). 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 forma, 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 forma correcta. 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.

F.n algunos lenguajes (como C+-*-) es necesario realizar una operacin especial para poder llevar a cabo una especializacin de tipos de forma correcta, pero en Java todas las proyecciones de tipos se comprueban. Por tanto, aunque parezca que estemos utilizando simplemente una proyeccin de tipos normal, 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 tiempo de ejecucin (RTTI, runtime type Information). El siguiente ejemplo ilustra el comportamiento de RTTl: //: polymorphism/RTTI,]ava // Especializacin en informacin de tipos en tiempo de ejecucin (RTTIJ. // ThrowsException) clase seful { puble void f() () public void g() {)

) class MoreUseful extends Useful {

} public class RTTI ( public static void main(StringH args) [ Useful [] x ^ ( new Useful(), new MoreUsefulf)

>;

x10] . t i ) s xlj .gO i / / Tiempo de compilacin: mtodo no encontrado en Useful: //i xllj.ul); ! (MoreUseful)x[II .u{)j // Especial! racin/RTTI (MoreUseful)x1011 .uO; // Excepcin generada

l i ///.-

Cuino en el diagrama anterior, MoreUseful ampla la interfaz de Useful. Pero, como se trata de una clase heredada tambin puede generalizarse a Useful. Podemos ver esta generalizacin en accin durante la inicializacin de la matriz \ en main( ) Puesto que ambos objetos de la matriz son de clase Useful. podemos enviar los mtodos f( ) y g( ) a ambos, mientras que si tratamos de invocar u( | (que slo existe en MoreUseful), obtendremos un mensaje de error en tiempo de compilacin.

Si queremos acceder a la interfaz ampliada de un objeto Morel'seful. 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 ningn codigo especial para esta excepcin, ya que indica un error del programador que puede producirse en cualquier lugar del programa. La etiqueta de comentario {ThrowsException} le dice al sistema de construccin de los ejemplos de este libro que cabe esperar que este programa genere una excepcin al ejecutarse.

L! mecanismo RTT1 es ms complejo de lo que este ejemplo de proyeccin simple permite intuir. Por ejemplo, existe una forma de ver cul es el tipo con el que estamos tratando untes de efectuar la especializacin. El Captulo 14, Informacin Je tipos est dedicado al estudio de los diferentes aspectos de la informacin de tipos en tiempo de ejecucin en Java

Ejercicio 17: (2) Utilizando la jerarqua Cvcle del Ejercicio I. aada un mtodo balancc( ) a l'nicycle y Bicycle, pero

no a Tricycle. Cree instancias de los tres tipos y generalcelas para formar una matriz de objetos Cycle Trate de invocar balance( ) en cada elemento de la matriz y observe los resultados. Realice una especia- lizacin e invoque balance( ) y observe lo que sucede. Resumen

Polimorfismo significa diferentes formas. En la programacin orientada a objetos, tenemos una misma interfaz definida en la clase base y diferentes formas que utilizan dicha interfaz: las diferentes versiones de los mtodos dinmicamente acopiados.

Hemos visto en este capitulo que resulta imposible comprender, o incluso crear, un ejemplo de polimorfismo sin utilizar la abstraccin de datos y la herencia. El polimorfismo es una caracterstica que no puede analizarse de manera aislada (a diferencia, por ejemplo, del anlisis de la instruccin switcli), sino que funciona de manera concertada, como parte del esquema global de relaciones de clases.

Para usar el polimorfismo, y por tanto las tcnicas de orientacin a objetos, de manera efectiva en los programas, es necesario ampliar nuestra visin del concepto de programacin, para incluir no slo los

miembros de una clase individual, sino tambin los aspectos comunes de las distintas clases y las relaciones que lienen entre si. 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 organizacin del cdigo y la posibilidad de disponer de programas atnpliablcs. y un mantenimiento del cdigo ms eficiente.

Pucc enconirar l;in Mtluuone*. a lo ejercicio* seleccionado*; en el documento electrnico thi' Thinkhtg in Java AnnotuituSolutfan Guitk. li*>punibte pan la venta cu hwv.Mimillnx net.Interfaces

Las interfaces y las clases abstractas proporcionan una fomia ms estructurada de separar la interfaz de la implementacin.

Dichos mecanismos no son tan comunes en los lenguajes de programacin. C++. por ejemplo, slo tiene soporte indirecto para estos conceptos Fl 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 trmino medio entrc una clase normal y una interfaz. Aunque nuestro primer impulso pudiera ser crear una interfaz, la clase abstracta constituye una herramienta importante y necesaria para construir clases que tengan algunos mtodos no implementados. No siempre podemos utilizar una interfaz pura.

Clases abstractas y mtodos abstractos

En todos los ejemplos de instrumentos musicales del capitulo anterior, los mtodos de la clase base Instrument eran siempre "ficticios". Si estos mtodos llegan a ser invocados, es que hemos hecho algo mal. La razn es que Instrument no tiene otro sentido que crear una interfaz comn para todas las clases derivadas de ella.

En dichos ejemplos, la nica razn para establecer esta interfaz comn es pan poder expresarla de manera diferente para cada uno de los distintos subtipos. Esa interfaz establece una forma bsica, de modo que podemos expresar todo aquello que es comn para todas las clases derivadas. Otra forma de decir esto seria decir que Instrument es una case bas abstracta, o simplemente una clase abstracta.

Si tenemos una clasc 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 Instrument consiste simplemente en expresar la interfaz y no en una implementacin concreta, por lo que no tiene sentido crear un objeto Instrument y probablemente convenga impedir que el usuario pueda hacerlo Podemos impedirlo haciendo que todos los mtodos de Instrument generen errores, pero eso retarda la informacin 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 compilacin

Java proporciona un mecanismo para hacer esto denominado mtodo abstracto 19 Se trata de un mtodo que es incompleto: slo tiene una declaracin, y no dispone de un cuerpo. He aqui la sintaxis para la declaracin de un mtodo abstracto: abstract void fI )

Una clase que contenga mtodos abstractos se denomina clase abstracta. Si una clase contiene uno o ms mtodos abstractos. la propia clase debe Calificarse como abstrae!, (en caso contrario, el compilador generar un mensaje de error).

Si una clase abstracta est incompleta, qu es lo que se supone que el compilador debe hacer cuando alguien trate de ins- tanciar un objeto de esa clasc? El compilador no puede crear de manera segura un objeto de una clase abstracta, por lo que

19

Para lu prcgpwitadatt de O-. tnua del anlogo a I fim'innej virtuales puna de C

generar un mensaje de error. De esta forma, el compilador garantiza la pureza de la clase abstracta y no es necesario preocuparse de si se la va a utilizar correctamente.

Si definimos una clase heredada de una clase abstracta y queremos construir objetos del nuevo tipo, deberemos proporcionar definiciones de mtodos para todos los mtodos abstractos de la clase base. Si no lo hacemos (y podemos decidir no hacerlo), entonces la clase derivada sera tambin abstracta, y el compilador nos obligara a calificar ew clase con la palabra clave abstraer

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 capitulo anterior puede transformarse fcilmente en una clase abstracta. Slo algunos de los mtodos sern abstractos, ya que definir una dase como abstracta no obliga a que todos los mtodos sean abstractos. He aqui el ejemplo modificado He aqui el ejemplo de la utilizar clases y mtodos //; orquesta modificado para abstractos:

interfaces/musi*/Music4.java // Clases y mtodos abstractos, package interfaces.music4 ; import polymorphism.music.Note; import static net.mindview.util.Print. / abstract class Instrument { private int i; // Storage allocated for each public abstract void playlNote n) ; public String what() ( return "Instrument; ) public abstract void adjust();

> class Wind extends Instrument { public void playlNote n) (print i"Wind.play I) " n) ; public String what IJ ( r e t u r n "Wind"; ) public void adjust I) {]

) ciass Percussion extends Instrument { public void play(Mote n] ( print("Percussion.play 0 " nJ ;

public public

String whacO ( return " Percuss ion*; void adjust 0 ()

) class Stringed extends Instrument ( public void play(Note n> ( print {"Stringed .olay I) " n) ; i public public String what 0( return "Stringed": void adjust O i) }

) class Brass extends Wind [ public void play(Noce n) { printi"Brass.play() + n)7 i j public void adjust<) { orint("Brass.adjust 0"); )

class Woodwind extends Wind f public void play(Note n) ( print("Woodwind.play() " * nj; ) public String whati) { return "Woodwind"; )

) public class Music4 (

// Wo me preocupa el tipo, por lo que los nuevos tipos // aadidos al sistema seguirn funcionando: static void tune(Instrument i) (

// ... 1.piay(Note.MIDDLE_C);

} static void LuneAll(Instrument I] e) { for(Instrument 1 : e) tune(i) ; j public static void main(StringU args) ( // Generalizacin durante la insercin en la matriz: Instrument t3 orchestra * ( new Wind O . new Percussion(I, new Stringed(), new Brass(), new Woodwind(]

). tuneAll Iorchestra); }) / Output: Wind.playl) MIDDLE_C Percussion.play() MIDDLE_C Stringed.playO MXDDLE_C Erass.play{I MIDDLE_C

Woodwind.play O MIDDLE_C *///:-

Podemos ver que no se ha efectuado 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 informan 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: (l) Modifique el Ejercicio 9 del capitulo anterior de modo que Rodent sea una clase abstracta. Defina los

mtodos de Rodent como abstractos siempre que sea posible.

Ejercicio 2: (l) Cree una clase abstracta sin incluir ningn mtodo abstracto y verifique que no pueden crearse instan

cias de esa clase.

Ejercicio 3: (2) Cree una clase base con un mtodo print( ) abstracto que se sustituye en una clase derivada. 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 variable, proporcione un valor distinto de cero. En el constructor de la clase base, llame a este mtodo. En main( ). cree un objeto del tipo derivado y luego invoque su mtodo print( ) Explique los resultados.

Ejercicio 4: (3) Cree una clase abstracta sin mtodos Defina una clase derivada y adale 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 mnin( ), demuestre que este mecanismo funciona. Ahora, incluya la declaracin abstracta del mtodo en la clase base, eliminando asi la necesidad de la especializacin Interfaces

La palabra clave interface lleva el concepto de abstraccin un paso ms all. La palabra clave abstract permite crear uno o ms mtodos no definidos dentm 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 interface produce una clase completamente abstracta, que no proporciona ninguna implementacin en absoluto. Las interfaces permiten al creador determinar los nombres de los mtodos, las listas de argumentos y los tipos de retomo, pero sin especi ficar ningn cuerpo de ningn mtodo. Una interfaz proporciona simplemente una forma, sin ninguna implementacin.

Lo que las interfaces hacen es decir: Todas las clases que implementen esta interfaz concret tendrn este aspecto Por tanto, cualquier cdigo que utilice una interfaz concreta sabr qu mtodos pueden invocarse para dicha interfaz y eso es todo. Por tanto, la interfaz se utiliza para establecer un protocolo entre las clases (algunos lenguajes de programacin orientados a objeto* disponen de una palabra clave denominada protocol para hacer lo mismo).

Sin embargo, una interfaz es algo ms que simplemente una clase abstracta llevada hasta el extremo, ya que permite realizar una variante del mecanismo de herencia mltiple creando una clase que pueda generalizarse a ms de un tipo base.

Para crear una intcraz. utilice la palabra clave interface en lugar de class. Al igual que con una clase, puede aadir la palabra clave public antes de interface (pero slo si dicha interfaz est definida en un archivo del mismo nombre). Si no incluimos la palabra clave public. obtendremos un acceso de tipo paquete, porque la interfaz slo sera utilizable dentro del mismo paquete. Una interfaz tambin puede contener campos, pero esos campos sern implcitamente de tipo static y final

Para definir una clase que se adapte a una interfaz concreta (o a un grupo de interlaces concretas), utilice la palabra clave implements que quiere decir La interfaz especifica cul es el aspecto, pero ahora vamos a decir cmo funciona. Por lo dems, la definicin de la clase derivada se asemeja al mecanismo normal de herencia. El diagrama para el ejemplo de los instrumentos musicales seria el siguiente: Podemos ver en las clases una vez que hemos la implementaein pasa a puede ampliarse de la Woodwind y Brass que implementado la interfaz, ser una clase normal que forma usual

Podemos declarar de una interfaz como sern pblicos an cuando tanto, cuando interfaz, los mtodos de esa definidos como pblicos. revenirla de forma de tipo paquete, con lo que accesibilidad de los herencia, cosa que el compilador de Java no permite.

explcitamente los mtodos public. pero esos mtodos no lo especifiquemos. Por implementemos una interfaz deben estar En caso contrario, se predeterminada ai acceso estaramos reduciendo la mtodos durante la

Podemos ver esto en la versin modificada del ejemplo Instrument. Observe que todos los mtodos de la interfaz son estrictamente una declaracin, que es lo nico que el compilador permite. Adems,

ninguno de los mtodos de Instrument se declara como public. pero de lodos modos son pblicos de manera automtica: //: interfaces/music5/Music5.java // Interfaces. paclcage interfaces .music5; mport polymorphism.music.Note; import static net . mindview.util .Print. ; interface Instrument { // Constante de tiempo de compilacin: int VALUE = 5; // static & final // Ko puede tener definiciones de mtodos: void play(Note n); // Automticamente pblico vola adjustf);

) class Wind implements Instrument { public void play(Note n) { ) printlthis * H M.play{) " n) ;

public Strng toStringl) ( retum "Wind"; ) public void adjust() { printlthis ' .adjust () ") ; } class Percu9 sion mpiementa Instrument ( public void playiNote ni { printlthis -* ".playO " -r n>; i public String toStringO { return "Percussion"; ) public void adjustO ( print (this - ".adjust (J *) : ) I class Stringed implements Instrument { public void playiNote n) { print (this -f M.play() + n) ;

) public String toStringO { return "Stringed"; ) public void adjust0 { print(this .adjust0 "); }

} class Brass extends Wind { oublic String toStringO ( return "Brass"; | j class Woodwind extends Wind ( oublic String toStringO { return "Woodwind"; ) I public class MusicS { // No le preocupa el tipo, por lo que los nuevos tipos // que se aaden al sistema seguirn funcionando: static void tune(Instrument il { // . . . i.play(Note.MIDDLE_C);

) static void tuneAll(Instrument[] e) { for(Instrument i : e) tune(i);

} public static void mainIString[] args) (

// Generalizacin durante la insercin en la matriz: Instrument[) orchestra = ( new Wind(), new PercussionO, new Stringed O. new Brass f) , new Woodwind0 tuneAll(orchestra)

) } / Output; Wind.playf) MIDDLE C Percussion.play() MIDDLE_C Stringed. playO MIDDLE_C Brass.playO MIDDLE_C Woodwind.play O MIDDLE_C *///:-

En esta versin del ejemplo liemos hecho otro cambio; el mtodo what( ) ha sido cambiado a toStrlng ), dado que esa era la forma en que se estaba utilizando el mtodo. Puesto que loStrngt ) forma parte de la clase raz Ohject, no necesita aparecer en la interfaz. El resto del cdigo funciona de la misma manera. Observe que no importa si estamos generalizando a una clase normal* denominada Instrument. a una clase abstracta llamada Instrument. o a una interfaz denominada Instrument. El compor

9 Interfaces 545 *taimente es siempre el mismo. De hecho, podemos ver en el mtodo tunet ) que no existe ninguna evidencia acerca de si

Instrument es una clase normal**, una clase abstracta o una interfaz.

Ejercicio 5: (2) Cree una interfaz que contenga tres mtodo* en su propio paquete. Implemento la interfaz en un paque

te diferente.

Ejercicio 6: (2) Demuestre que todos los mtodos de una interfaz son automticamente pblicos.

Ejercicio 7: (1) Modifique el Ejercicio 9 del Capitulo 8. Polimorfismo, para que Rodent sea una interfaz

9 Interfaces 546 Ejercicio 8: (2) En polymorphism.Sandwich.javu. cree una interfaz denominada FastFood (con los mtodos apro

piados) y cambie Sandwich de modo que tambin implemente FastFood.

Ejercicio 9: (3) Redisee Music5.java mov iendo los mtodos comunes de W'ind. Percussion y Stringeda una clase

abstracta.

Ejercicio 10: (3) Modifique Music5.java aadiendo una interfaz Playahle. Mueva la declaracin de p!ay( ) de

9 Interfaces 547 Instrument a Playahle. Aada Playahle a las clases derivadas incluyndola en la lista implements Modifique tune( ) de modo que acepte un objeto Playahle en lugar de un objeto Instrument. Desacoplamiento completo

Cuando un mtodo funciona con una clase en lugar de con una interfaz, estamos limitados a utilizar dicha clase o sus subclases. Si quisiramos aplicar ese mtodo a una clase que no se encontrara en esa jerarqua, no podramos. Las interfaces relajan esta restriccin considerablemente. Como resultado, permiten escribir cdigo ms reutili/able.

Por ejemplo, suponga que disponemos de una clase Proeessor que tiene sendos mtodos ame ( ) y process( ) que toman una cierta entrada, la modifican v generan una salida. La clase base se puede ampliar para crear diferentes tipos de objetos Proeessor. En este caso, los subtipos de Proeessor modifican objetos de tipo String (observe que los tipos de retomo pueden ser cov ariantes, pero no los tipos de argumentos): //: mnerfaces/classprocessor/Appiy.java package interfaces.classprocessor; impart java.til. *, mport static nec .mindview. ucil, Prini. *; class Proeessor { public String ame( ( retom getCiass11.getSimpleName();

9 Interfaces 548 ) Objecc process(Object input) | retum input; }

) class Upcase extends Proeessor { String process (Object inputI ( // Retomo covariante retum ((String) input).toUppervasel)

) } class Downcase extends Processor { String process(Object input) ( return {(Strinci) input) . toLowerCase() ;

9 Interfaces 549 class Splicter extends Processor { String process(Object inputi ( I I El m^Lodo split 0 divide una cadena er* fraamentos: retum Arrays.toString(((String)input).split(* "));

) public class Apply { public scatic void process(Processor p. Obnect s) ( print IUsing Processor M 4 p.name O J; print Ip.process(s>);

) public static String s = wDisagreement with beliefs is by definition incorrect"; public static void main(String(J args< { process(new UpcaseO4 s); process(new Downcase(), s) ; processnew Splitter O, s) ;

) ) / Output: Using Processor Upcase DISAGREEMENT WITH BELIEFS IS BY DEFINITION

9 Interfaces 550 INCORRECT Using Processor Downcase disagreement with beliefs is by definition incorrect Using Processor Splitter [Disagreement, with, beliefs, is, by, definition, incorrect]

///:-

El mctodo Apply.process( ) toma cualquier Upo de objeto Processor y lo aplica a un objeto Object, imprimiendo despus los resultados La creacin de un mtodo que se comporte de forma diferente dependiendo del objeto argumento que se le pase es lo que se denomina el patrn de diseo basado en estrategias. El mtodo contiene la parte fija del algoritmo que hay que impJcmentar. mientras que la estrategia contiene la parte que varia. La estrategia es el objeto que pasamos, y que contiene el cdigo que hay que ejecutar. Aqu, el objeto Processor es la estrategia y en man( ) podemos ver como se uplican tres estrategias diferentes a la cadena de caracteres v

El mtodo splitC ) es parte de la clase String; toma el objeto String y lo divide utilizando el argumento como frontera, v devolviendo una matriz String|| Se utiliza 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 Appl\.process( )

9 Interfaces 551 //: mterfaces/filters/Waveform.java package interfaces.filters; public class Waveform ( private static long counter; private final long id = counter--*; public String toStringO f return "Waveform " id; )

) ///:I t : interfaces/filters/Filter.Java package interfaces.filters; public class Filter ( public String name 1) { return getClass f).getSimpleName I)j

} public Waveform process(Waveform input) { return input; } } m-.~ //: interfaces/filters/LowPass.java package interfaces.filters; public class LowPase extends Filter { double cutoff; public LowPass(double cutoff) { this.cutoff = cutoff; ) public Waveform process(Waveform input > ( return input; // Dummy processing

9 Interfaces 552 )

} ///://: interfaces/filners/HighPass.jav a package interfaces.fiIters; public class HighPaas extends Filter { double cutoff; public cutoff; public { return input; HighPass(double cutoff { } Waveform process(Waveform ) this.cutoff input)

) ///:/; interfaces/fliters/BandPass.jav a package interfaces.filters; public class BandPass extends Filter { double lowCutoff. highCutoff; public BandPass(double lowCut, double highCut) { lowCutoff lowCut; highCutoff = highCut; i public ( retum input; Waveform process(Waveform ) input)

9 Interfaces 553 | ///!-

Killer licne los mismos elementos de interfaz que Processor, pero puesto que no hereda de Proeessor (puesto que el creador de la clase Filler no tena ni idea de que podramos querer usar esos objetos como objetos Processor), no podemos utilizar un objeto Filter con el mtodo Apply.process( ). a pesar de que funcionara. Bsicamente, el acoplamiento entre Apply.process( ) y Proeessor es ms fuerte de lo necesario y esto impide que el cdigo de Apply.process() pueda sutilizarse en lugares que seria til. Observe tambin que las entradas v salidas son en ambos casos de tipo Waveform.

Sin embargo, si Processor es una interfaz, las restricciones se relajan lo suficiente como para poder reutilizar un mtodo Apply.proccss( ) que acepte dicha interfaz. He aqui las versiones modificadas de Proeessor v Apply: : interfaces/mterfaceprocessor/Processor . java package interfaces.interfaceprocessor; public interface Processor ( String name Oj Object process{Object input); ) U h: : interfaces/mterfaceprocesscr/Apply . java package interfaces.interfaceprocessor; import static net.mindview.til.Print.; public class Apply { public static void processiProeessor p, Object s} ( print ("Using Processor " * p.namel)); print(p.orocess(si I;

9 Interfaces 554 ) I ///:-

La primera forma en que podemos reutilizar el cdigo es si los programadores de clientes pueden escribir sus clases para que se adapten a la interfaz, como por ejemplo: i /: interfaces/interfaceprocessor/StringProces sor.java package interfaces.nterfaceprocessor; import java.util.*; public abstract class StringProcessor implements Processor) public String rame0 ( i return getClass(J .getSimpleName l J;

public abstract String process(Object input); public static String s =

555 Piensa en Java "If she weighs the same as 3 duck, she's made of wood";public static void main(String(] argsl { Apply, process (new Upcase(), s) j Apply.process (new Downcase (), s) Apply .process (new Splitterd, s) j ) I class Upcase extends StringProcessor { public String process (Object input) { // Recomo covariante return ( (String)input).toUpperCase<)/

>

1 class Downcase extends StringProcessor ( public String process(Object input) ( return {(String)input).t oLowerCase(); class Splitter extends StringProcessor ( public String process(Object input! ( return Arrays. toString ( ( i String) lr.DUt) .split (" " M ; I

556 Piensa en Java I ) / Output: Using Processor Upcase IF SHE WEIGHS THE SAME AS A DUCK, SHE'S MADE OF WOOD Using Processor Downcase if she weighs the same as a duck, shes made of wood Using Processor Splitter [If, she, weighs, the, same, as, a. duck., she's, made. of. wood]

///:-

Sin embargo, a menudo nos encontramos en una situacin en la que no podemos modificar las clases que queremos usar. En el caso de los filtros electrnicos, por ejemplo, la correspondiente biblioteca la liemos descubierto, en lugar de desarrollarla. En cslos casos, podemos utilizar el patrn de diseo adaptador. Con dicho patrn de diseo, lo que hacemos es escribir cdigo para tomar la interfaz de la que disponemos y producir la que necesitamos, como por ejemplo: //: interfaces/interfaceprocessor /FilterProcessor.java package interfaces.interfaceprocessor ; import interfaces.filters.*; class FilterAdapter implements Processor ( Filter filter; public FilterAdapter(Fil ter filter) ( this.filter * filter; I

557 Piensa en Java ) public String name () ( return filter, name (I ,* J public Waveform process(Object input) ( return filter.process((Waveform) input);

) 1 public class FilterProcessor { public static void main(StringU args) { Waveform w * new Waveform!); Apply.process(new FilterAdapter(new LowPass(1.0)), w); Apply.process(new FilterAdapterInew HighPass(2 .01), w); Apply.process( new FilterAdapter(new Bandpass(3.0, 4.0)), w) ;} ( Output Using Processor LowPass Waveform 0 Using Processor HighPass Waveform 0 sing Processor BandPass Waveform 0 ///;-

558 Piensa en Java En esta aplicacin, el patrn de diseo, de adaptacin, el constructor FilterAdaptcr. toma la interfaz que tenemos (Fiiter) v produce un objeto que tiene la interfaz Processor que necesitamos. Observe tambin la utilizacin del mecanismo de delegacin en la clase FilterAdaptcr

Desacoplar la interfaz de la implementacin permite 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 pan que funcione con interfaceproccssor.Apply.process( ).

559 Piensa en Java 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 varas interfaces. Esto resulta muy til en ocasiones, como por ejemplo cuando queremos implementar el concepto una \ es una a y una b y una c" En C++, este acto de combinar mltiples interfaces de clase se denomina herencia mltiple. y puede llegar 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 C-t-f no aparecen en Java cuando se combinan mltiples interfaces: En una derivada, estamos a lencr base que abstracta concreta no tenga clase no obligados una clase sea o (una que 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 mplements y separarlos mediante comas. Podemos incluir tantas interfaces como queramos y podemos realizar generalizaciones [upenst) a cada interfaz, porque cada una de esas interfaces representa un tipo independiente. El siguiente ejemplo muestra una clase concreta que se combina con vanas interfaces para producir una nuev a clase: f /: interfaces/Adventure-java // Interfaces mltiples. interface CanFight ( void fightl);

560 Piensa en Java } interface CanSwim ( void swim0 /

} interface CanFly { void fly(); i class ActionC haracte r ( publi c void fightt) ()

) class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly ( public void svrimO (} public void fly O ()

561 Piensa en Java ) public class Adventur ( publicstaticvoid { x..fightt) ) publicstaticvoid ( x.swim()j) publicstaticvoid x.fly(); ) publicstaticvoid args) { t(h);//Treatitas uh);//Treatitas v(h);//Treatitas w(h);//Treatitas t(CanFight ulCanSwim vfCanFly x) x) x) {

publicstaticvoid w(ActionCharacter xi { x.fight(); ) raain(String[]

Hero h * new Hero(); a CanFight a CanSwim a CanFly an ActionCharacter

) ///:-

562 Piensa en Java Puede ver que Mero combina la clase concreta ActionCharacter con las interfaces CanFight. CanSwim y CanFly. C uando se combina una dase concreta con interfaces 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 fighf( ) es igual en la interfaz CanFight y en la clase AetionC'haracter Asimismo, a fight ) no se le proporciona una definicin en Hero. Podemos ampliar una interfaz, pero lo que obtenemos entonces ser otra interfaz. Cuando queramos crear un objeto, todas las definiciones debern haber sido ya proporcionadas Aunque Hero 110 proporciona explcitamente una definicin para figlt( ). dicha definicin est incluida en ActionCharacter: por tanto, es posible crear objetos Hero

En la clase Adventure. podemos ver que hay cuatro mtodos que toman argumentos de las distintas 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 siendo generalizado en cada caso a cada una de las interfaces. Debido a la forma en que se disean las interfaces en Java, este mecanismo funciona sin que el programador tenga que preocuparse de nada.

Recuerde que una de las principales razones para utilizar mierfaces es la que se ilustra en el ejemplo anienor: para realizar generalizaciones a ms de un tipo base (y poder disfrutar de la flexibilidad que esto proporciona). Sin embiirgo. una segunda razn para utilizur interfaces coincide con la razn por la que utilizamos clases base abstractas: para impedir que el programador de clientes cree un objeto de esta clase y para establecer que slo se trata de una interfaz. I

563 Piensa en Java Esto hace que surja una cuestin: debemos utilizar una interfaz o una clase abstracta? Si resulta posible crear nuestra clase base sin ninguna definicin de mtodo y sin ninguna variable 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 conveniente transformarla en interfaz {hablaremos ms sobre este tema en el resumen del captulo).

Ejercicio 12: (2) EnAdventure.java.aada una siguiendo el patrn de las otras interfa

interfaz

llamada

CanClimb.

ces.

Ejercicio 13: (2)Creeunainterfaz yherede de interfaces. Defina, mediante herencia mltiple, una

ella

otras

dos

nuevas

tercera interfaz a partir de estas otras dos.: I

564 Piensa en Java - Este ejemplo muestra cOuno tas interfaces e\itan el denominado "problema del rombo que ve pre-vnita en el mecanismo de herencia mlliplc de O* Ampliacin de la interfaz mediante herencia

podemos aadir fcilmente nuevas declaraciones de mtodos a una interfaz utilizando los mecanismos de herencia, y tambin podemos combinar varias interfaces mediante herencia para crear una nueva interfaz. En ambos casos, obtendremos una interfaz nueva, como se ve en el siguiente ejemplo: //: interfacea/HorrorShow.java // Ampliacin de una Interfaz mediante herencia. interface Monster { void menace Mj

) interface DangerousMonster extends Monster { void destroy(); i interface Lethal { void killO; i class DragonZilla implements DangerousMonster ( public void menace{) {} public void destroy0 {) i interface Vampire extends DanaerousMonster, Lethal { void drinkBlood () ,* I

565 Piensa en Java ) class VeryBadVampire implements Vampire { public void menace() I) public void destroy 0 () public void kill 0 {) public void drinkBlood() {) I public class HorrorShow ( static void u(Monster b) ( b.menaced; J static void v(DangerousMonster d) { d.menace(); I
d.

destroy 0 ;

static void w(Lethal 1) { l.killO; ) public static void main(Stringf] argsJ ( DangerousMonster barney = new DragonZilla{); u(barney); v(barney); Vampire vlad * new VeryBadVampire () ; U ( v l a d ) ; v ( v l a d ) , * w i v I

566 Piensa en Java l a d } ;

) ) ///:-

DangerousMonster es una extensin simple de Monster que produce una nueva interfaz Esta se implementa en DragonZilla

La sintaxis empleada en Vampiro slo funciono cuando se heredan interfaces. Normalmente, slo podemos utilizar e\tends con una nica clase, pero extends puede hacer referencia a mltiples interfaces base a la hora de construir una nueva interfaz 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 I

567 Piensa en Java mediante herencia una nueva interfaz

que combine las tres, aadiendo un nuevo mtodo. Cree una clase implementando la nueva interfaz y que tambin herede de una clase concreta. A continuacin, escriba cuatro mtodos, cada uno de los cuales tome una de las cuatro interfaces como argumento. En main ). cree un objeto de esa clase y pselo a cada uno de los mtodos.

Ejercicio 15: (2)Modifiqueelejercicioanterior haciendo que la clase derivada herede de

creando

una

clase

abstracta

ella. Colisiones de nombres al combinar interfaces

Podemos encontramos con un pequeo problema a la hora de implementar mltiples I

568 Piensa en Java interfaces En el ejemplo anterior, tanto CaiiFight como ActionC'haracter tienen sendos mtodos idnticos void fight( ). El que haya dos mtodos idnticos no resulta problemtico, pero t>qu sucede si los mtodos difieren en cuanto a signatura o en cuanto a tipo de retomo? He aqui un ejemplo: //: interfaces/Interfac eCollision.java package interfaces; class C ( public int f() ( return 1; } ) class C2 implements II, 1 2 ( public void f() {) public nt f<int i) ( sobrecargado return 1; } //

) class C3 extends C iraplements 12 ( public int flint i) ( sobrecargado return 1? ) //

) class C4 extends C implements 13 { // Idntico. No hay problema: public int

569 Piensa en Java fi) ( return 1; )

) // Los mtodos slo difieren en el tipo de retorno: / / i class C5 extends C implements II {) / / i interface 14 extends II. 13 {} ///:-

La dificultad surge porque los mecanismos de anulacin, de implementacin y de sobrecarga se entremezclan de forma compleja. Asimismo, los mtodos sobrecargados no pueden diferir slo en cuanto al tipo de retomo Si quitamos la marca de comentario de las dos ltimas lineas, los mensajes de error nos informan del problema InterfaceCoUision.java: 23; ft ) in C cannot implementff I in II. altempting to use incompatible return type found: int requtred; void interfaceColtision. java:2<f; Interfaces 3 and II are incompatible; lu>tli define f' ). but wiih different return type

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 evitar la utilizacin de nombres de mtodo idnticos.

570 Piensa en Java Adaptacin a una interfaz

Una de las razones mas importantes para utilizar interfaces consiste en que con ellas podemos disponer de mltiples imple- mentaciones para una misma interfaz, tn los casos ms simples, esto se lleva a la prctica empleando un mtodo que acepta una interfaz, lo que nos deja total libertad v 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 >a hemos hablado: escribimos un mtodo que realice ciertas operaciones y dicho mtodo toma como argumento una interfaz que especifiquemos. Bsicamente, lo que estamos diciendo es: Puedes utilizar mi mtodo con cualquier objeto que quieras, siempre que este se adapte a mi interfaz". Esto hace que el mtodo sea ms flexible, general y reutilizable.

Por ejemplo, el constructor para la clase Scanner de Java SE5 (de la que hablaremos ms en detalle en el Capitulo 13. Cadenas de caracU'tvs) admite una interfaz Readable. Como veremos. Readable no e> un argumento de ningn otro mtodo de la biblioteca 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: //: interfaces/RanaomWords.java I

571 Piensa en Java // Implementation 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 chart] capitals = "ABCDEFGHIJKLMNOPQRSTU VWXYZ- . toCharArray() private static final chart] lowers = 'abcdetghijkimnopqrst uvwxys''. toCharArray ( ) ; private static final chart] vowels = "aeiou".toCharArray(); private lnt count; public RandomWords(int count) ( this.count * count; J public int readiCharBuffer cb) ( if(count-- == 0) return -1; // Indic3 el final de la entrada cb.append I capitalsIrand.nextInt(capit als.length) ] ) ; for(int i * O; i < 4; i*-) ( cb.append(vowels[rand.nextI nt(vowels.length)]); cb.append(lower8 [rand.nextI nt[lowers.length)]); I cb.append (M ); i return 10 ? // NQmero de caracteres aadidos

public static void main(String[J args> { Scanner s = new Scanner(new RandomWords1 10)); while(s.hasNext()) System.out.printIn(s.next())/

572 Piensa en Java ) } /* Output: Y a z e r u y a c P o w e n u c o r G e e a z i m o m R a e u u a c i o N u o a d e s i w H I

573 Piensa en Java a g e a i k u x R u q i c i b u i S u m a s e t i h Kuuuuozoa Waqizeyoy

*///:-

574 Piensa en Java La interfaz Readable slo requiero que se implemento un mtodo read( Dentro de read( la informacin al ).

argumento CharBufTcr (hay varias formas de hacer esto, consulte la documentacin deCharBuffer). devolvemos -\

cuando ya no haya ms datos de entrada.

Supongamos que disponemos de una clase base que an no implementa 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. //: interfaces/Ran doraDoubles.ja va import java.util.*; public clase RandomDoubles { private static Random rand = new Randomi47); public double next() { return rand.nextDouble0 ; ) public static void main(String[] args) I

575 Piensa en Java ( RandoraDoubles rd = new RandomDoubles( I ; for(int i 0; i < 7; i ++) i


0. 0.

System.out.print(rd.next{ ) t " " ) /

) /* Output: 7271157860730044 0.5309454508634242 0.16020656493302599 0.18847866977771732 5166020801268457 0.2613610344283964 0 .2678662084200585

*///:-

De nuevo, podemos utilizar el patrn de diseo 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 clave interface, produciremos una nueva clase que sera a la v ez RandomDoubles y Readable: //: interfaces/AdaptedRandomDoubles .java // Creacin de un adaptador mediante herencia, import java.nio.*/ import java. uni 1 . * public class AdaptedRandomDoubieo extends RandoraDoubles implements Readable ( private int count; I

576 Piensa en Java public AdapteoRandomDoubies(int count} ( this.count > count; i public int read{CharBuffer cb) ( if(count-- 0 ) return -1 ; String result = Double. toString (next 0 J - " "; cb.append(result); return result.length();

) public static void main(StringU args> { Scanner s = new Scanner(new AdaptedflandomDoubles(7) ) while(s.hasNextDouble()) System.out .print Is .nextDouble ( I " *);

) ) / Output: 7271157860730044 0.5309454508634242 0.16020656493302599 0.18847866977771732


0. 0.

5166020801268457 0.2678662084200585 0.2613610344283964 ///*-

577 Piensa en Java Puesto que podemos aadir de esta forma una interfaz a cualquier clase existente, podemos deducir que un mtodo que lome como argumento una interfaz nos permitir adaptar cualquier clase para que funcione con dicho mtodo. Aqui radica la verdadera potencia de utilizar interfaces en lugar de clases

Ejercicio 16: (3)Creeunaclasequegenereunasecuenciade para que pueda utilizarse

caracteres. Adapte esta clase

como entrada a un objeto Scanner Campos en las interfaces

Puesto que cualquier campo que incluyamos en una interfaz sera automticamente de tipo static y final, la interfaz constituye una herramienta conveniente para crear grupos de valores constantes. Antes de Java SE5, sta era la nica forma de producir el mismo efecto que con la palabra clave enum en C o C++. Por tanto, resulta habitual encontrarse con cdigo anterior a la versin Java SE5 que presenta el aspecto siguiente: f / \ interfaces/MonLhs.java // Uso de interfaces para crear grupos de constantes. package interfaces; public interface Months ( int I

578 Piensa en Java JANUARY = 1, FEBRUARY = 2 , MAP.CH * 3, APRIL = 4, MAY 5. JUNE * 6 . JULY * 7, AUGUST = 8 , SEPTEMBEP. 9, OCTOBER = 10, NOVEMBER = 11, DECEMBER 12;

} ///r-

Observe la utilizacin del estilo Java, en el que todas las letras estn en maysculas (con guiones bajos para separar las distintas palabras que formen un determinado identificador) en los casos de valores estticos finales con inicializadores constantes. Los campos de una interfaz son automticamente pblicos, asi que el atributo public no se especifica explcitamente.

Con Java SE5. ahora disponemos de la palabra clave enum. mucho ms potente y flexible, por lo que rara vez tendr sentido que utilicemos interfaces para definir constantes. Sin embarco, quiz se encuentre en muchas ocasiones con sta tcnica antigua a la hora de leer cdigo heredado (los suplementos de este libro disponibles en MindVtew.net proporcionan una descripcin completa de la tcnica previa a Java SE5 para producir tipos enumerados utilizando interfaces). Puede encontrar ms detalles sobre el uso de la palabra clave enum en el Captulo 19. Tipo* enumerados

579 Piensa en Java Ejercicio 17: (2) Demuestre que los campos de una interfaz son implcitamente de tipo static y final Inicializacin de campos en las interfaces

Los campos definidos en las interfaces no pueden ser valores finales en blanco", pero pueden miciatizarse con expresiones no constantes. Por ejemplo: //J interfaces/RandVals.java // Inicializacin de campos de interfaz con // inicializadores no constantes, lmport java.util.*; public interface RandVais ( Random RAND new Random(47); int RANDOM__INT = RAND.nextlnt<10) ; long RANDOM_LONG = RAND,nextLcng() 10; float RANDOM_FLOAT * RAND.nextLong{) 10; double RANDOMJDOUBLE = 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: //: interfaces/TestRandVals.java mport static net.mindview.util.Print.*; I

580 Piensa en Java public clase TestRandVals ( public static void main(Strin g 11 args) { print RandVals.R AND0M_INT) ; print(Rand Vals.RANDQ M_LONG); print(Rand Vals.RAND0 M_FL0A7); print Rand Vals.RANDO M DOUBLE/ j i ) / Output:

B 3 2 0 3 2 2 4 7 0 1 6 5 5 9 9 5 4 9

581 Piensa en Java . 5 9 3 9 2 9 1 E 1 8 5 . 7 7 9 9 7 6 1 2 7 8 1 5 0 4 9 '/ // :-

Los campos, por supuesto, no forman parte de la interfaz. Los valores se almacenan en el rea de almacenamiento esttico correspondiente a dicha interfaz.

582 Piensa en Java Anidamiento de interfaces

Las interfaces pueden anidarse dentro de clases y dentro de otras interfaces.3 Esto nos revela una serie lie caractersticas interesantes: //: interfaces/nesting/Ne stinglnterfaces.java package interfaces.nesting; class A { interface B ( void ft);

) public class BImp implements B f public void f() (}

) I

583 Piensa en Java private class BImp2 implements B { public void f() {)

} public interfa ce C { void f (); i class CImp impleme nts C ( public void f() (}

) private class CImp2 implements C ( oublic void fO ()

584 Piensa en Java ) private interfa ce D { void f();

} private class DImp implements D ( public void f() ()

) public class Dlmp2 implements D { public void f() ()

] public D getDO { return new DImp2(); } I

585 Piensa en Java private D dRef; public void receiveD{D d) { dRef = d; dRef.f()

9 Interfaces 586 ;}

) interface E ( interfac e G ( void f I)i J f ( public" redundante: public interface K { void f|; l void g(); // No puede ser private dentro de una interfaz: //i private interface I ()

) public class Nestinglnterfaces ( public class BImp implements A.B { public void ft) {)

) class CImp implements A.C ( oublic void f() {}

9 Interfaces 587 } // No se puede implementar una interfaz privada excepto f t dentro de la clase defintoria de dicha interfaz: //J class DImp implements A.D { //! public void ft) {( f/l I class EImp implements E ( public void gO {)

class EGImp implements E.G ( public void ft) ()

} class EImp2 implements E { public void gO {) class EG implements E.G ( public void f() (}

) public static void main(Strlng[] argsj ( k = - new A () ; t f No se puede acceder a A.D: //l A.D ad * a.getDO; // Slo puede devolver a A.D: f t I A .DImp 2 di2 a.getDO;

9 Interfaces 588 f t No se puede acceder a un miembro de la interfaz: //i a.getDO.f O; f t Slo otra A puede utilizar getDO: A a2 = new At) ; a2 .receiveD(a.getD());

>

} ///:-

Ui sintaxis para anidar una interfaz dentro de una clase es razonablemente amplia. Al igual que las interfaces no anidadas, las anidadas pueden tener visibilidad pblica o con acceso de paquete.

Como caracterstica adicional, las interfaces tambin pueden ser privadas, como podemos ver en A.D (se necesita la misma siniaxis de cualificacin para las interfaces anidadas que para las clases anidadas). Para qu sirve una interfaz anidada privada? Podemos suponer que slo puede implementarse como clase interna privada, como en DImp. pero A.DImp2 mus- ira que tambin puede implementarse como clase pblica. Sin embargo. A.DImp2 slo puede utilizarse como ella misma. No se nos permite mencionar el hecho de que implementa la interfaz privada D. por lo que implementor interfaces privadas es una forma de forzar la definicin de los mtodos de dicha interfaz sin aadir ninguna informacin de tipos (es decir, sin permitir ninguna generalizacin).

9 Interfaces 589 ti mtodo getD( ) nos revela un dato adicional acerca de las interfaces privadas: se trata de un mtodo pblico que devuelve una referencia a un interfaz privada. Que podemos hacer con el valor de retomo de este mtodo? En main( ). podemos ver varios intentos de utilizar el valor de retomo, todos los cuales fallan. La nica cosa que funciona es entregar el valor de retomo a un objeto que tenga permiso para usarlo, que en este caso es otro objeto A. a travs del mtodo receiveD( ).

La interfaz E muestra que podemos anidar unas interfaces dentro de otras. Sin embargo, las reglas acerca de las interfaces, en particular, que todos 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.

Nostinglnterfaces muestra las diversas formas en que pueden implementarse las interfaces anidadas. En particular, observe que. cuando iniplementamos una interfaz, no estamos obligados a implcmentar ninguna de las interfaces anidadas dentro de ella. Asimismo, las interfaces privadas no pueden implementarse fuera de sus clases definitorias.

Inicialmente, pudiera parecer que estas caractersticas slo se hubieran aadido para garantizar la coherencia sintctica, pero mi experiencia es que una vez que se conoce una caracterstica siempre se descubren ocasiones cu las que puede resultar til

9 Interfaces 590 Interfaces y factoras

El objeto principal de una interfaz es permitir la existencia de mltiples implcmentaciones. y una forma tpica de producir objetos que encajen con una interfaz, es el denominado patrn de diseo de mtodo factora. En lugar le llamar a un constructor directamente, invocamos un mtodo de creacin en un objeto factora que produce una implementacin de la interfaz: de esta forma, en teora, nuestro cdigo estar completamente aislado de la implementacin de la interfaz, haciendo asi posible intercambiar de manera transparente una implementacin por otra. He aqu un ejemplo que muestra la estructura del mtodo factora: //: interf aces/Factories .-java import static net .mindview.til. Print ,* interface Service ( void methodl( ) j void method2 (); i interface ServiceFactory ( Service getService); i class Implementation.1 implements Service { Implementationl() {| // Package access public void methodli) (printi"Implementationl methodl"!;} public void method2<J {print < "Implementationl method2"J;}

) class mplementationlFactory implements ServiceFactory ( public Service getServce ) { return new ImplementationlO; )

9 Interfaces 591 ) class Implementationl implements Service { Implementation2 O {) //Acceso de paquete public void methodlI) (print("Implementation2 methodl);} public void method2 il (printi"Implementations method2 ");|

) class Implementation2Factory implements ServiceFactory ( public Service getService{) { return new Implementation2();

) i public class Factories { public static void serviceConsumer*ServiceFactory tact) ( Service s tact.getService(); a .methodl0 ) s.method2 ( ) ;

public static void main(String[J args) ( serviceConsumer(new ImplementationlFactory{)); // Las implementaciones son completamente intercambiables: serviceConsumer(new Implementation2Factory{)),

9 Interfaces 592 ) ) / * Output: Implementationl methodl Implementation! method2 Implementation methodl Implementation2 method2 *///:-

Sin cl mtodo factora, nuestro cdigo tendra que especificar en algn lugar el tipo exacto de objeto Service que se estuviera creando, para poder invocar el constructor apropiado.

Para qu sirve aadir este nivel adicional de indireccin? Una razn comn es para crear un marco de trabajo para el desarrollo. Suponga que estamos creando un sistema para juegos que permita, por ejemplo, jugar tanto al ajedrez como a las damas en un mismo tablero. //; interfaces/Games.java /'/ Un marco de trabajo para juegos utilizando mtodos factora, import static net .mindview.util.Print. *; interface Game ( boolean moveO; } inr^rfarp Gam<*Frt:nry { Gam* getGama(}* ) class Checkers implements Game ( private Int moves = 0 ; private static final int MOVES = 3; public boolean move(J ( print(Checkers move " moves); return -*~*-raoves 1= MOVES; 1 class Checkers Factory implements GaineFactory | public Game getGamei ) ( return new Checkers(); )

9 Interfaces 593 ) class Chess implements Game { private int moves = 0 ; private static final me MOVES ~ 4; public boolean moveU ( i print"Chess move " + moves-; return ^moves MOVES;

class ChessFactory Implements GameFactory ( public Game getGamed ( return new ChessO; )

) public class Games { public static void playGamelGameFactory Eactory) ( Game s factory.getGame0; while(s.move()) i

public static void main(Strmg(l args} ( playGame(new CheckersFactory()); playGame(new ChessFactory U);

) ) /* Output: Checkers move 0 Checkers move 1 Checkers move 2 Chess move 0 Chess move l Chess move 2 Chess move 3

9 Interfaces 594 Si la clase Games representa un fragmento 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 desarrollar este diseo.

En el siguiente captulo, veremos una forma ms elegante de implemcntar las factoras utilizando clases internas annimas.

Ejercicio 18: (2) Cree una interfaz Cycle, con implemen tac iones Unleycle. Blcycle y Tricycle. Cree factoras 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 permita simular las operaciones de lanzar

9 Interfaces 595 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 concretas. Por supuesto, casi siempre que creemos una clase, podemos crear en su lugar una interfaz y una factora.

Mucha gente ha cado 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 necesario para optimizar el cdigo, en lugar de incluir ese nivel adicional de indirecein en todas partes. ya que ello hace que aumente la complejidad. Esa complejidad adicional es significativa, y hacer 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 sospechara, con motivo, de todos los diseos que realicemos.

Una directriz apropiada es la que seala que las clases resultan preferibles a las interfaces. Comience con clases y, si esta claro que las interfaces son necesarias, redisee el cdigo. Las interfaces son una

9 Interfaces 596 herramienta muy conveniente, pero est bastante generalizada la tendencia a utilizarlas en demasa

Puede encontrar las solucione* a los ejercicios >eleccionadm en el documento electrnico Tlir Thttking InJaui AnrmtatedSolulUui Cmule, disponible para la venta en k-hu.Mim/View net.Clases internas

10

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 permite agrupar clases relacionadas y controlar la visibilidad mutua de esas clases. Sin embargo, es importante comprender que las clases internas son algo totalmente distinto al mecanismo de composicin del que ya hemos hablado.

A primera vista, las clases internas 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 detalles 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)

Imcialmenie. las clases internas pueden parecer extraas y se requiere cierto tiempo para llegar a sentirse cmodo al utilizarlas en los diseos La necesidad de las clases internas no siempre resulta obvia, pero despus de describir la sintaxis bsica 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 sintaxis de las clases internas. Estas caractersticas se proporcionan con el tin de cubnr 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 capitulo 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 cabria suponer: la definicin de la clase se incluye dentro de otra clase contenedora: //: innerclasses/Parcel1 .j ava // Creacin de clases Internas. public class Parcel1 { class Contenes ( prvate iot i 11 ; public int valuIJ ( return i; |

) class Destination ( private String label;

Destination(String whereTo) ( label * whereTo;

) I String readLabelt) ( return label; )

// La utilizacin de clases internas se asemeja // a la de cualquier otra clase, dentro de Parcell:

public void ship(String dest) { Contenta c * new Contents(l; Destination d * new Destination(dest); System.out.println(d.readLabei());

) public static vod main(String[] args) { Parcell p * new Parcel1(); p.ship("Tasmania4");

) /* Output: Tasmania ///--

Las clases internas utilizadas dentro do ship( ) parecen clases normales. Aqui. la nica diferencia prctica es que los nombres estn anidados dentro de Parcell. Pronto veremos que esta diferencia no es la nica.

Lo ms normal 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( ): //; innerclasses/Parcel2 .java // Devolucin de una referencia a una clase interna. public clase Parcei2 ( class Contents { prvate int i * 11 ; public int valu() { return i; }

i class Destination { private String label; Destination(String whereTo) { label = whereToj i String readLabei(> ( return label; ) J public Destination to(String s) { return new Destination(oI ;

) public Contents contentsO ( return new Contents();

) public void ship(String dest) { Contents c => contents(); Destination d to(dest); System.out.printIn(d.readLabeiO];

) public static void main(StringI1 args) ( Parcel2 p = new Parcel2 () , p.ship("Tasmania"); Parcel2 q = new Parcel2 i);

// Definicin de referencias a clases internas: Parcel2.Contents c = q.contentsO; Parcel2.Destination d q.to("Borneo");

) ) / Output: Tasmania

*///:-

Si queremos construir un objeto de la clase interna en cualquier lugar que no sea dentro de un mtodo no esttico de la clase extema, debemos especificar el upo de dicho objeto como NombreClaseExicnia.NotnbreClaschUcnta. como puede verse en tnain().

Ejercicio 1: (l) Escriba una clase denominada uter que contenga una clase interna llamada Inner. Aada un mto

do a Outer que devuelva un objeto de tipo Inner. F.n main ). cree e inicialice una referencia a un objeto taer. El enlace con la clase externa

Hasta ahora, parece que las clases internas son simplemente un esquema de organizacin de cdigo y de ocultacin de nombres. lo cual resulta til pero no especialmente necesario. Sin embargo, las cosas son ms complejas de lo que parecen, cuando se crea una clase interna, cada objeto de esa clase interna dispone de un enluce al objeto contenedor que o ha emulo. por lo cual puede acceder a los miembros de dicho objeto contenedor sin utilizar ninguna cualificacin especial. Adems, las clases internas tienen derechos de acceso a todos los elementos de la clase contenedora.20 Fl siguiente ejemplo ilustra esta caracterstica*. //: innerclasses/Sequence. ]ava / Almacena una secuencia de objetos. interface Selector ( fcoclean endl)? Object current()j void next();

) public class Sequence { prvate Object[J items; prvate int next = 0; public Sequence(int size) if(next < items.length) F.sto difiere significanvnmcnic del diseo de claves <imlaJa\ en 0+-*-. que simplemente se irnta de un mecanismo de ocultacin de nombres N:o hay ningn enlace ul objeto contenedor ni ningn tipo ile permisos implcitos en C-++.
20

items a new Object[aisej; }

public void ada{Object x,- {

tems[next++] = X;

} prvate class SequenceSelector implements Selector ( prvate int i. Q; public boclean endtl { return i == J.rem3 .length; ) public Object current(J ( return items[i]; ) J public void next O ( if(i< items.length) i*-*-; )

pablic Selector selecto*O ( return new SequenceSelector\); i public static void main(String11 args) ( Sequence 3 equence * new Sequence(10); ornt 1 = Oj i < i0; i-*-} sequence.add(Integer.toString(i)); Selector selector = sequence.selector) ; while(!selector.endO) { System.out.print (selector.current () + " *); selector.next();

) / Output: 0 1 2 3 4 5 6 7 9 9

*///:-

La secuencia Sequen ce es simplemente una matriz de tamao fijo de objetos Object con una clase envoltorio. Invocamos add( ) para aadir un nuevo objeto al t nal de la secuencia (si queda sitio). Para extraer cada uno de los objetos de la secuencia, hay una interfaz denominada Selector. ste es un ejemplo del patrn de diseo iterador del que hablaremos ms en detalle posteriormente en el libro. Un Selector permite ver si nos encontramos al final de la secuencia [end( )]. acceder al objeto actual (currenti )] y desplazarse al objeto siguiente (ne\t< )] de la secuencia. Como Selectores una interfaz, otras ciases pueden implementar la interfaz a su manera y otros mtodos pueden tomar la interfaz como argumento, para crear cdigo de propsito ms general.

Aqu, SequenceSeleetor es una clase privada que proporciona la funcionalidad Selector. En main(). podemos ver la creacin 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 SequenceSeleetor se asemeja a la de cualquier otra clase interna. Pero examinemos el ejemplo en ms detalle. Observe que cada uno de los mtodos [end( ). current( ) y neit( )] hace referencia a Items, que es una referencia que no forma parte de SequenceSeleetor. sino que se encuentra en un campo privado dentro de la clase contenedora. Sin embargo, la clase interna puede acceder a los mtodos y campos de la clase contenedora como si fueran de su propiedad. lista caracterstica resulta muy cmoda, como puede verse en el ejemplo anterior.

Asi pues, una clase interna tiene acceso automtico a los miembros de la elase contenedora. Cmo

puede suceder esto? La clase interna captura en secreto una referencia al objete concreto de la clase contenedora que sea responsable de su creacin Entonces, cuando hacemos referencia a un miembro de la clase contenedora, dicha referencia se utiliza para seleccionar 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 referencia al objeto de la clase contenedora y el compilador 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: (I) Cree una clase que almacene un objeto Strng y que disponga de un mtodo toStrinf( ) que muestre

esa cadena de caracteres. Aada varias instancias de la nuev a clase a un objeto Sequence y luego visualcelas.

Ejercicio 3: (l) Modifique el Ejercicio I para que Outer tenga un campo prvate String (inicializado por el construc

tor) e Inner tenga un mtodo toStrmgt ) que muestre este campo. Cree un objeto de tipo Inner y visualcelo

Utilizacin de .this y .new

Si necesita generar la referencia al objeto de la clase externa, basta con indicar el nombre de la clase extema seguido de mi punto y de la palabra clave tliis. La referencia resultante lendr automticamente el tipo correcto, que se conoce y se comprueba en tiempo de compilacin, por lo que no hay ningn gasto adicional en liempo de procesamiento. He aqu un ejemplo que muestra como utilizar .tliis: //: innerclasses/DotThis.java // Cualificacin del acceso al objeto de la clase externa. public class DotThis { void f() ( System.out.println("DotThis.f()*); ) public class Inner ( public DotThis outer() retum DotThis.this; // Un "this" hara referencia al "this" de Inner {

) public Inner inner() { return new Inner(); ) public static void main(String(] args) { DotThis dt = new DotThis():

i 10 Clases internas 608 DotThis. Inner dti = dt. inner 0:dti.cter O - f l) i } / OutpUt: DctThis.f <)

*///:-

Algunas veces, necesitamos decir a un objeto que cree otro objeto de una de sus clases internas. Para hacer esto es necesario proporcionar una referencia al objeto de la clase externa en la expresin new. utilizando la sintaxis .new. como en el siguiente ejemplo: //: innerclasses/DotNew.lava Creacin de una clase interna directamente utilizando la sintaxis .new. public clasa DotNew ( public class Inner () public static void main(Stxing[1 aras) { DotNew dn = new DotNew O; DotNew. Inner dni = dn.new Inner O;

i 10 Clases internas 609 } ///:-

Para crear un objeto de la clase interna directamente, no se utiliza esta misma forma haciendo referencia al nombre de la clase externa DotNew como cabria esperar, sino que en su lugar es necesario utilizar 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 escribiramos (porque, de hecho, no se puede) dn.new DotNew.Inner( ).

No es posible crear un objeto de la clase interna a menos que ya se disponga 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 embargo. si definimos una clase anidada, (una clase interna esttica), entonces no ser necesaria la referencia al objeto de la clase externa.

A continuacin puede ver cmo se aplicara .new al ciemplo Parcel**: //: nnerclasses/Parcel3.java // Utilizacin de .new para crear instancias de clases internas. public class Parcel3 ( class Contents ( prvate int i * 11 ; i public int valu O { return i; } class Destination ( prvate Strlng label; Destination(Strlng whereTo) { label = whereTo; ) String readLabel() { return label; }

i 10 Clases internas 610 ) public static void tnam String [] args) { Parcel3 p = new Parcel3(); // Hay que usar una instancia de la clase externa // para crear una instancia de la clase interna: Parcel3.Contents c = p.new ContentsO; Parcel3.Destination d = p.new Destination("Tasmama");

) ///:-

Ejercicio 4: (2)Aadaun mtodo a la clase Sequence.SequenceSelector que genere la referencia a la clase externa Sequence.

Ejercicio 5: (I) Cree una clase con una clase interna. En otra clase separada, cree una instancia de la clase interna

i 10 Clases internas 611 Clases internas y generalizacin

Las clases internas muestra su utilidad real cuando comenzamos a generalizar a una clase base y. en particular, a una interfaz. (L! efecto de generar una referencia a una interfaz a partir de un objeto que la implemente es prcticamente el mismo que el de realizar una generalizacin a una clase base). La razn es que entonces la clase interna (la implementacin de la interfaz) puede ser no visible y estar no disponible, lo cual resulta muy til para ocultar la implementacin. Lo nico que se obtiene es una referencia a la elase base o a la interfaz

Podemos crear interfaces para los ejemplos anteriores: //: nnerclasses/Destination.java public mterface Destination { String readLabelt); l ///://: nnerciasses/'Contents.java public interface Contents ( int valu()

} ///:-

i 10 Clases internas 612 Ahora Contents y Destination representan interfaces disponibles para el programador de clientes. Recuerde que una interfaz hace que todos sus miembros sean automticamente pblicos.

Cuando obtenemos una 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: //: innerclasses/TestParcel.java class Parcel4 { privare class PContents implements Contents { private int i = II; oubllc int valu(i { return i; }

) protected class FDestmation implements Destination ( private String label; private PDestinafcion(String whereTo ( label = whereTo;

) public String readLabelU { return label; }

i 10 Clases internas 613 ) public Destination destination(String s) ( return new PDestinationis); i public Contents contentsO { return new PContents();

} public class TestParcel ( public static void main(String[] args) { Parcel4 p = new Parcel4(); Contents c = p.contentsO; Destination d * p.destination("Tasmania"); // Ilegal -- no se puede acceder a la clase privada: l //! Parcel4 .PContents pe = p.new PContentsO;

) ///;-

i 10 Clases internas 614 En Parcel-* hemos aadido algo nuevo. La clase interna PContents es private, as que slo puede acceder a ella ParceM 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 Parcel*!, las clases contenidas en el mismo paquete (>' que protected tambin proporciona acceso de paquete) y las clases que hereden de ParccM. Lsto quiere decir que el pro- gramador de clientes tiene un conocimiento de estos miembros y un acceso a los mismos restringido. De hecho, nu podemos ni siquiera realizar una esperializacin 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 \er en ela&s XestParcel. Por tanto, las clases internas privadas proporcionan una forma para que los diseadores de clases eviten completamente las dependencias de la codificacin de tipos y oculten totalmente Ion detalles relativos a la implementacin. Adems, la extensin de una interfaz resulta intil desde la perspectiva del programador de clientes, ya que este no puede acceder a ningn mtodo adicional que no forme parte de la interfaz pblica. Este* tambin proporciona una oportunidad para que el compilador de Java genere codigo ms eficiente.

Ejercicio 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 implcmcntc la interfaz. En un tercer paquete, defina una clase que herede de la anterior y. dentro de un mtodo, des uelva un objeto de la clase interna protegida. efectuando una generalizacin a la interfaz durante el retomo.

Ejercicio 7: (2) Cree una clase con un campo privado y un mtodo privado. Cree una clase interna con un mtodo que

i 10 Clases internas 615 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.

Ejercicio 8: (2)Determine interna.

si una clase externa tiene acceso a los elementos privados de su clase

Clases internas en los mtodos y mbitos

Lo que hemos visto hasta ahora son los usos tpicos 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 comprender. Sin embargo, las sintaxis de las clases internas abarca varias otras tcnicas ms complejas. Las clases internas pueden crearse dentro de un mtodo o incluso dentro de un mbito arbitrario. Lxisten dos razones para hacer esto:

1.

Como hemos visto anteriormente, 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 encontrar la solucin, pero sin que la clase est pblicamente disponible.

i 10 Clases internas 616 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.

lina clase annima que implemento 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

i 10 Clases internas 617


6.

Una clase annima que lleve a cabo la construccin utilizando el mecanismo de inicializacin de instancia (las clases internas annimas no pueden tener constructores).

El primer ejemplo muestra la creacin de una clase completa dentro del mbito de un mtodo (en lugar de dentro del mbito de otra clase), fisto se denomina clase interna /acal / f : innercla9 ses/Pareel5.j ava / / Anidamiento de una clase dentro de un mtodo. public claas Parcel5 ( public Destination destination(String s) { clase PDestination mplements Destination { prvate String label; private PDestinacion(Scring whereTo) ( label whereTo; ) public Strino readLabeliJ ( return label; }

) } return new PDestination(s);

public static void main (String [J args) { ParceLS p = new Pareis()/ Destination d * p.destlnation(TasmanlaM,,) ; 1 ) U Hr

i 10 Clases internas 618 a clase PDestination es parte de destina(inn( ) en lugar de ser parte de Parcel? 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 destnation( ) no quiere decir que PDestination no sea un objeto vlido una vez que destination( ) termina.
1.

Podemos utilizar el identificador de clase PDestination para nombrar cada clase interna dentro de un mismo subdirectoro sin que se produzcan colisiones de clases.

1:1 siguiente ejemplo muestra cmo podemos anidar clases dentro de un mbito arbitrario. //: nnerclasses/Parcele.java // Anidamiento de una clase dentro de un mbito. public claBS Parcel ( prvate void intemalTrackinglbooiean b) ( if(b) { class TrackingSlip { private String id; TrackingSlip(String s) { id ** S;

} String getSlipO { return id; )

i 10 Clases internas 619 ) TrackingSlip ts = new TrackingSlip("slip") ; String s = ts.getSlipO ; i // |No se puede usar aqu! Fuera de mbito: //1 TrackingSlip ts = new TrackingSliD i."x" I ;

) public void trackO | intemalTracking( true) ; ) public static void mainStTingn argsl ( Parcel p = new ParcelGO; p.track(};

} ///*-

La clase TrackingSlip est anidada dentro del mbito de una instruccin if. Esto no quiere decir que la case se cree condicionalmente; esa clase se compila con todo el resto del cdigo. Sin embargo, la clase

i 10 Clases internas 620 no est disponible fuera del mbito en que est definida. Por lo dems, se asemeja a una clase normal.

Ejercicio 9: (I) Cree una interfaz con al menos un mtodo e implemente dicha interfaz definiendo una clase interna

dentro de un mtodo que devuelva una referencia a la interfaz.

Ejercicio 10: (l) 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 devuelva

i 10 Clases internas 621 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 especializacin sobre la misma. Clases internas annimas

El siguiente ejemplo puede parecer un tanto extrao: //: innerclassea/Parcel7.java // Devolucin de una instancia de una ciase interna annima. public class Parcel? ( public Contenta contenta O { retum new Contentad { // Inserte una definicin de clase prvate int = 11 ,* public int valu O { retum i; } ); // En este caso hace falta el punto y coma

} public static void main{String() aras) { Parcel7 p = new Parcel7(); Contente c = p.contentaJ ; ) } ///-.-

El mtodo contents() combina la creacin del valor de retomo con la definicin de la clase que

i 10 Clases internas 622 representa dicho valor de retomo. Adems, la clase es annima, es decir, no tiene nombre. Para complicar an ms las cosas, parece como si estuviramos empezando a crear un objeto Contents, y que entonces, antes de llegar al punto y coma, dijramos Un momento: voy a introducir una definicin de clase".

Lo que esta extraa sintaxis significa es: "Crea un objeto de una clase annima que herede de Contents" La referencia devuelta por la expresin new se generalizar automticamente a una referencia de tipo Contents. La sintaxis de la clase interna annima es una abreviatura de: //: innerclasses/Parcel7b.java // Versin expandida de Parcel7.java public class ParceI7b { class MyContents implements Contents 1 private int i * XI ublic nt valu0 { return i; )

) public Contents contents() { retum new MyContentsO; ) public static void main(Stringfl args1 { ParceI7b p = new Parcei7b(); Contents c = p.contental) ;

i 10 Clases internas 623 1 ///:-

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: /./; mnerclasses/Pareis . java // Invocacin del constructor de la clase base. public ciass Parcela ( public Wrapping wrapping(int x) ( // Llamada al constructor de la clase base: retum new Wrapping(x) ( constructor, public int valu(I ( retum super.valu() 47; //Pasarargumento del

) ) t // Punto y coma necesario

i 10 Clases internas 624 i

625 Piensa en Java public static void malntStrlngll aras) (Parcela p = new Parcele(); Wrapping w = p.wrapping(10);

) J

Es decir, simplemente pasamos c! argumento apropiado aJ constructor de la clase base, como sucede aqui con la x que se pasa en new \N rapping(x). Aunque se trata de una clase normal con una implementacin. Wrapping se est usando tum. bien como interfaz" con sus clases derivadas: //: innerciasses/Wrappina.java public class Wrapping { private int i? public Wrapping(int x) ( i = x; } public int valu() ( return i; )

) ///:-

626 Piensa en Java Como puede observar. W rapping tiene un constructor que requiere un argumento, para que las cosas sear un poco ms interesantes.

El punto y coma situado al final de la clase interna annima no marca el final del cuerpo de la clase, sino el final de la expresin que contenga a la clase annima. Por tanto, es una utilizacin idntica al uso del punto y coma en cualquier otro lugar

Tambin se puede realizar la tnicializacin cuando se definen los campos en una clase annima: //: nnerclasses/Parcel9.java // Una clase interna annima que realiza la // inicialzacin. Versin ms breve de Pareis.java. public class Parcel9 ( // El argumento debe ser final para poder utilizarlo // dentro de la clase interna annima: public Destination destination(final String dest) ( return new Destination(J { private String label * dest; public String readLabelO ( return label; )

627 Piensa en Java };

) public static void main(StringLl args) { Parcel9 p * new Farcel9(); i Destination d * p.destination("Tasmania");

} ///:-

Si estamos definiendo una clase interna annima y queremos usar un objeto que est definido fuera de la clase interna annima. el compilador requiere que la referencia al argumento sea final, como puede verse en el argumento de dcstination( ) Si nos olvidamos de hacer esto, obtendremos un mensaje de error en tiempo de compilacin.

Mientras que estemos simplemente realizando una asignacin a un campo, la tcnica i

628 Piensa en Java empleada en este ejemplo resulta adecuada. Pero que sucede si necesitamos realizar algn tipo de actividad similar a la de los constructores? No podemos disponer de un constructor nominado dentro de una clase annima (ya que la clase no tiene ningn nombre), pero con el mecanismo de micializacin Je instancia, podemos, en la practica, crear un constructor para una clase interna annima, de la forma siguiente: f f : Innerclasses/AnonymousConstructor.java // Creacin de un constructor para una clase interna annima, import static net .mindvew.til. Print. abstract class Base { public Base(int i) { printC'Base constructor, i = * -* i)

) public abstract void fO ;public class AnonymousConstructor ( public static Base getBase(int i) ( retum new Base (i) ( ( print {Inside mstance initializer"); } public void () {

printCIn anonymous E O " >;

I ;

629 Piensa en Java ) public static void main(String(] args) { Base base = gesBase( 47); base.f( ) i

) J / Output: Base constructor, i 47 iTiside ir.stanee initalizer Xa anonymous f (I ///:-

En es le caso, la variable i no tenia porqu haber sido final Aunque se pasa i al constructor base de la clase annima, nunca se utili/a esa variable dentro de la clase annima.

He aqu un ejemplo con inicializacin de instancia. Observe que los argumentos de destinationt ) deben ser de tipo final, puesto que se los usa dentro de la clase annima: i //: mnerclasses/ParcellO. java

630 Piensa en Java // Uso de Minicializacn de instancia" para realizar // la construccin de una clase interna annima. public class ParcellO ( public Destination destinacin(final String dest, final float pricej { return new Destination!) { prvate int cost;
II

Inicializacin de instancia para cada objeto:

{ cost a Math.round (price); if(cost > 100 ) I Systen;.out. println(MOver budget tM) ;

prvate String label - dest: public String readLabelO { retum label; )

) public static void mair I String [] args) ( ParcellO p * new ParcellOO; Destination 101.395F); i d ^ p. destination ("Tasmania'*,

631 Piensa en Java > } / Output: Over budgetI ///:-

Dentro del micializador de instancia, podemos ver cdigo que no podra ejecutarse como parte de un micializador de campo (es decir, la instruccin if). Por tanto, en la prctica, un micializador de instancia es el constructor de una clase interna annima. Por supuesto, esta solucin est limitada: no se pueden sobrecargar los micializadores de instancia, asi que slo podemos disponer de uno de estos constructores. Las clases internas annimas estn en cierta medida limitadas si las comparamos con el mecanismo normal de herencia, porque tienen que extender una clase o implementai una interfaz, pero no pueden hacer ambas cosas al mismo tiempo. Y. si implementamos una interfaz, slo podemos implcmentar una

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 632 Piensa en Java .Ejercicio 13: (!) Repita el Ejercicio 9 utilizando una clase interna annima

Ejercicio 14: interfaces/HorrorShow.ja\ a para implementar utilizando

d) Modifique Dmngerous.Monster

Varapire

clases annimas.

Ejercicio 15: predeterminado (uno

t2) Cree unaclase con un constructor no que tengaargumentos) y sin ningncons

tructor predeterminado (es decir, un constructor sin argumentos). Cree una segunda clase que tenga un mtodo que devuelva una referencia a un objeto de la primera clase. Cree el objeto que hay que devolver definiendo una clase interna annima que herede de la primera clase.

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 633 Piensa en Java Un nuevo anlisis del mtodo factora

Observe que el ejemplo interfaees/factories.java es mucho ms atractivo cuando se utilizan clases internos annimas //: innerclasses/Factories.java tnport stat;c net . mmdview. til .Print. * ; interface Service { void methodl O / void method( i ; ) nterface ServiceFactory ( Service qetService< ) \ class Implementationl implements Service { prvate Implementationl() {) public void methodl() {printf"Implementationl methodl"! i) public void method2C' {print("Implementationl method2") ,) public static ServiceFactory factory = new ServiceFactory() ( public Service getServiceO ( return new Implementationl () ;

class Implementation2 implements Service { private Implementation2() {} public void methodlf {print("Implementation methodl";) public void method2() (print^Implementations methods");) public static ServiceFactory factory = new ServiceFactory() ( public Service getServiceO ( return new Implementations(;

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 634 Piensa en Java )

} public class Factories { public static void serviceConsumeriServiceFactory fact) ( Service s fact.getServica0; 3 .methodl ()/ s.method2 0;

) public static void main(StringM args1 ( serviceConsumer(Implementationl.factor y); // Las mplementaciones son completamente intercambiables: servceConsumer(Implementation2.factory)j

) / Output: Implementationl methodl Implementation! metho2 Implementation^ methodl

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 635 Piensa en Java jmplementatlon2 method2 ///:-

Ahora, los constructores de Implementation I e Implementation! pueden ser privados y no hay ninguna necesidad tie crear una clase nominada como factora. Adems, a menudo slo hace falta un nico objeto factora, de modo que aqu lo hemos creado como un campo esttico en la implementacin de Service. Asimismo, la sintaxis resultante es ms clara. Tambin podemos mejorar el ejemplo Interfaces/Gam ei.ja va utilizando clases internas annimas: //: nnerclasses/Games.java Utilizacin de clases internas annimas con el marco de trabajo Game, import static net.mindview.util.Print interface Game ( boolean move(); } interface GameFactory | Game getGameO; ) class Checkers implements Game ( private Checkers() () private int moves * 0 ; private static final int MOVES 3; public boolean movep ( print ("Checkers move " 4 - moves); return amoves 1= MOVES;

) public static GameFactory factory * new GameFactory() ) public Game getGameO ( return new Checkers(); ) I class Che35 implements Game { private Chess 0 () private int moves = 0 ; private static final int MOVES * 4; public boolean move() (

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 636 Piensa en Java print "Chess move " -f moves) ; return moves 1 * MOVES;

public static GameFactory factory = new GameFactory() ( public Game getGameO I return new ChessO; )

!;

) public class Games ( public static void playGame(GameFactory factory) ( Game s = factory .getGame(l; while(s.move <)) J

public static void mainiString(J args) ( playGame'Checkers.factory !; playGameiChess.factory);

) ) / Output: Checkers move Checkers move Checkers 0 I

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 637 Piensa en Java move 2 Chess move 0 Chess move 1 Cheas move 2 Chess move 3 *///:-

Recuerde el consejo que hemos dado al final del ltimo captulo: Utilice las clases con preferencia a las interfaces. Si su diseo necesita una interfaz, ya se dar cuenta de ello. En caso contrario, no emplee una interfaz a menos que se vea obligado.

Ejercicio 16: (l) Modifique la solucin del Ejercicio 18 del Capitulo 9. Interfaces para utilizar clases internas annimas

Ejercicio 17: (1) Modifique la solucin del Ejercicio 19 del Captulo 9. Interfaces para utilizar clases internas annimas

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 638 Piensa en Java Clases anidadas

Si no es necesario disponer de una conexin entre el objeto de la clase interna y el objeto de la clase externa, podemos definir la clase interna como esttica. Esto es lo que comnmente se denomina una clase anulada.21 Para comprender el significado de la palabra clave static cuando se la aplica a las clases internas, hay que recordar que el objeto de una clase interna 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 como esttica. Por tanto, una clase anulada significa:

1.

Que no es necesario un objeto de la clase externa para crear un objeto de la clase anidada.

Que no se puede acceder a un objeto no esttico de la clase externa desde un objeto de una clase anidada.
2.

Las clases anidadas difieren de las clases internas ordinarias tambin en otro aspecto. Los campos y los mtodos en las clases internas normales slo pueden encontrarse en el nivel extemo de una clase, por lo que las clases internas normales no pueden tener datos estticos, campos estticos o clases anidadas. Sin embargo, las clases anidadas pueden tener cualquiera de estos elementos. //: innerclasses/Parcelll. java // Clases anidadas (clases internas estticas). < manta cierto purccido con las clases anidadas de Cn salvo porque dichas clases no permiten acceder a miembros privado a diferencia de lo que succ de en Java.
21

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 639 Piensa en Java public class Parceill ( prvate static class PaxcelContents lmplements Contents ( privare iut i = 11 / public int valu(J { retum i; }

) protected static clasB ParcelDestinatiem implements Destination ( prvate Stnng labei; private ParcelDestinationiString whereTo) { labei - whereTo,*

> public String readLabelf) ( return labei; ) // Las clases anidadas pueden contener otros elementos estticos: public static void f() (} static int x = 10 ; static class AnotherLevel { public static void f() () static int x = 10 ; i

} public static Destination destination(String s) ( retum new ParcelDestination(s) ; i public static Contents contents/) ( retum new ParcelContents{)j

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 640 Piensa en Java 1 public static void main<Stringl] args; { Contents c = contents); Destination d * destination("Tasmania" ) ;

) J ///:-

En main( I. no es necesario ningn objeto de Parcel 11. en su lugar, utilizamos la sintaxis normal para seleccionar un miembro esttico con el que invocar los mtodos que devuelven referencias a Contents y Destination

Como hemos visto anteriormente en el capitulo, en una clase interna normal (no esttica), el vinculo con el objeto de clase externa se utiliza empleando una referencia this especial. Una clase anidada no tiene referencia this especial, lo que hace que sea anloga a un mtodo esttico

Ejercicio 18: (I) Cree una clase que contenga una clase anidada. En main( ). cree una instancia de la clase anidada.

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 641 Piensa en Java Ejercicio 19: (2) Cree uua clase que contenga una clase interna que a su vez contenga otra 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, ni' podemos incluir cualquier cdigo dentro de lina interfaz, pero una clase anidada puede ser parte de una 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 forma siguiente, como por ejemplo en: //: mnerciasses/ClassInlnterface .java // (mairi: Classlnlnterface$Test) public interface Classlnlnterface ( void howdy (); ciass Test impiemente Classlnlnterface { ptiblic void howrly I) { System.out .prmtln ("Howdy 1 " I;

) public static void mam (String U args) { new Test().howdy();

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 642 Piensa en Java ) i } *Output: Howd yl ///:-

Resulta bastante til anidar una clase dentro de una interfaz cuando queremos crear un cdigo comn que haya que emplear con todas las diferentes mplementaciones de dicha interfaz.

Anteriormente en el libro ya sugeramos incluir un mtodo main( ) en todas las clases, con el fin de utilizarlo como mecanismo 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: //: mnerclasseB/TestEed.java // Inclusin del cdigo de prueba en una clase anidada. // {tttain: TestBedSTester} public ciass TeatBed ( public void f() { Sy8 tein.ot.prntlnl "f i i " l ; ) public static ciass Tester {

public static void maltt(String J args | TestBed c new TescBedli/ t . f O f

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 643 Piensa en Java )

) ) / Output: f O *///*-

Esto genera una clase separada denominada TestBedSTettcr (para ejecutar c! programa, escribiramos java TestBedSTester). Puede utilizar esta clase para las pniebas. pero no necesitara incluirla en el producto final, bastar con borrar TcxtBedSTestcr.class antes de crear el producto definitivo.

Ejercicio 20: (1| Cree una interfaz que contenga una clase anidada. Implemento 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

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 644 Piensa en Java 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:* //: Innerclasses/MultiNestingAccess. java // Las clases anidadas pueden acceder a todas los miembros de // todos los niveles de las clases en las que est anidada. ciass MNA { private void f() {} ciass A j private void gO () public ciass B { void hl) (

g t ) ; f t *

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 645 Piensa en Java )

) public ciass MultiNestingAccess ( public static void main(Stringl args) ( MNA mna * new MNA () j MNA. A tnnaa = mna. new A () ; ) ) ///:MNA.A.B mnaab = mnaa.new B(); mnaab.h( > ;

Puede ser que en MNA.A.B. los mtodos j() y f() son invocables sin necesidad de ninguna cualificacin (a pesar del hecho de que son privados) Este ejemplo tambin ilustra 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 constructor.

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 646 Piensa en Java iracia* iic mievu u Martin Dannci Para qu se usan las clases internas?

Hasta este momento, hemos analizado buena parte de los detalles sintcticos y semnticos que describen la forma de funcionar de las clases internas, pero esto no responde a la pregunta de para qu sirven las clases internas. Por qu los diseadores de Java se tomaron tantas molestias para aadir esta caracterstica fundamental al lenguaje?

Normalmente, la clase interna hereda de otra clase o implemento una interfaz, y el cdigo incluido en la clase interna manipula el objeto de la clase externa dentro del cual hubiera sido creado. Asi pues, podramos decir que una clase interna proporciona una especie de ventana hacia la clase externa.

Una de las cuestiones fundamentales acerca de las clases internas es la siguiente: si simplemente necesitamos una referencia a una interfaz, por qu no hacemos simplemente que la clase extema 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 unplementa 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 implcmenta- ciones Por tanto, la razn ms evidente para utilizar clases internas es la siguiente: Cada dase interna puede heredar de una imp/ementaein de manera independiente Por tanto, la dase interna no est limitada por d hecho de si la dase externa ya est heredando de una implementarin

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 647 Piensa en Java Sin la capacidad de las clases internas para heredar, en la prctica, de ms de una clase concreta o abstracta, algunos problemas 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 mplementaciones** ln otras palabras, las clases internas nos permiten en la prctica heredar de vanos 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.

//: mnerclasses/Multilnterfaces.java

// Dos formas de impiemintar mltiples interfaces con una clase, package lnnerclasses; interface A {) lterface B (} clase X implementa A. B (} class Y Lmplements A {

B raakeB\J (

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 648 Piensa en Java // Clase interna annima: retum new B <) {}; ii

public ciass Multilntertaces ( static void takesAlA a) {) 3tatic void takesB{B b) {} public stacic void main{String[] argsj j X x * new X i) Y y = new () ; takesAx) ; takesA y>; takesB *x); takesB(y.makeB i));

) )//I-

Por supuesto, esto presupone que la estructura del cdigo tenga sentido en ambos casos desde el pumo de vista lgico. Sin embargo, normalmente dispondremos de algn tipo de directriz, extrada de la propia naturaleza del problema, que nos indicar M debemos utilizar una nica clase o una clase interna, pero en ausencia de cualquier otra restriccin, la tcnica utilizada en el ejemplo anterior no presenta muchas diferencias desde el punto de vista de la implementacin. Ambas soluciones funcionan adecuadamente.

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 649 Piensa en Java Sin embargo, si tenemos clases abstractas o concretas en lugar de interfaces, nos veremos obligados a utilizar clases internas si nuestra clase debe implemeniar de alguna forma las otras clases de las que se quiere heredar: //: lnnerclasses/Multilmplementation.java // Con clases abstractas o concretas, las clases // internas son la nica forma de producir el efecto // de la "herencia de mltiples impiementaciones* packaae innerclasses; clasB D () abstract ciass E () ciass Z excends D { E makeEil ( return new E{) j); } 1 public ciass Multilmpleraentation ( static void takesDD d) () 3tatic void takesE( e) () public static void maintStringII argsl ( Z 2 new Z (J f takesD<z); takesE\2.make E()) j

) J ///*-

Si no necesitramos resolver el problema de la herencia de mltiples iinplemcntaciones". podramos escribir el resto del programa sin necesidad de utilizar clases internas. Pero con las clases internas tenemos, adems, las siguientes caractersticas adicionales:

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 650 Piensa en Java
1.

La clase interna puede tener mltiples instancias, cada una con su propia informacin de estado que es independiente de la informacin contenida en el objeto de la clase externa.

2.

En una nica clase externa, podemos tener vanas clases internas, cada una de las cuales implementa la misma interfaz o hereda de la misma clase en una forma diferente En breve mostraremos un ejemplo de este caso.

El punto de creacin del objeto de lu clase interna no est ligado a la creacin del objeto de la clase externa
3.

4.

No existe ninguna relacin de tipo es-un* potencialmente confusa con la clase interna, se trata de una entidad separada.

Por ejemplo, si Sequen ce. ja va no utilizara clases internas, estaramos obligados a decir que un objeto Scquence es un objeto Selector, y slo podra existir un objeto Selector para cada objeto Sequence concreto. Sin embargo, podramos fcilmente pensar en definir un segundo mtodo. re\erseSelector( ). que produjera un objeto Selector que se desplazara en sentido inverso a travos de la secuencia. Este tipo de flexibilidad slo est disponible con las clases internas. Ejercicio 22: (2) Implemento reverseSelector( ) en Sequence. ja va.

Ejercicio 12:< I )Repita elEjercicio 7 utilizando una clase interna annima. 651 Piensa en Java Ejercicio 23: (4) Cree una interfaz l con tres mtodos. Cree una clase A con un mtodo que genero una referencia a l

10 Clases Internas 652 definiendo una clase interna annima. Cree una segunda clase B que contenga una matriz de V. B debo tener un mtodo que acepte y almacene una referencia a U en la matriz, un segundo mtodo que configure una referencia en la matriz (especificada mediante el argumento del mtodo) con el valor nuil, y un tercer mtodo que se desplace a travs de la matriz e invoque los mtodos de l. 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 BCierres y retrollamada

Un cierre (closure) es un objeto invocable que retiene informacin acerca del mbito en que fue creado. Teniendo en cuenta esta definicin, podemos ver que una clase interna es un cierre orientado a objetos, porque no contiene simplemente cada elemento de informacin del objeto de la clase externa **el mbito en que fue creado), sino que almacena de manera automtica una referencia que apunta al propio objeto de la clase externa, en el cual tiene permiso para manipular todos los miembros, incluso aunque sea privados.

Uno de los argumentos mas slidos que se proporcionaron para incluir algn mecanismo de punteros en Java era el de permitir las retrollamudasi (caflbacks), Con una retrollamada. se proporciona a algn otro objeto un elemento de informacin que le permite llamar al objeto original en un momento posterior. Se irata 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 lenguaje Java tiende a ser bastante ms precavido, por lo que no se han incluido punteros en el lenguaje.

10 Clases Internas 653 El cierre proporcionado por la clase interna es una buena solucin, bastante ms flexible y segura que la basada en punteros. Veamos un ejemplo: //; innerclasses/CalIbacks.java / / Utilizacin de clases internas para las retrollamadas package innerclasses/ import static net.mindview.til.Print; nterface Incrementable ( void increment(J;

) // Muy eimple para limitarse a implementar la interfaz: ciass Calieel implements Incrementable ( private int i * 0 ; public void increment() { i-H- print(i);

i i ciass Mylncrement ( public void incremento { printl1MOther operatlon"); ) scatic void f(Mylncrement mi) { mi. increment; O ; }

10 Clases Internas 654 I // Si nuestra clase debe implementar incremento de // alguna otra forma, es necsario utilizar una clase interna: ciass Callee2 extends Mylncrement j private int i = 0 ; public void increment <) ( super.increment t} ; 1-M-; printII); i private ciass Closure implements Incrementable { public void increment( ) ( // Especifique un mtodo de la clase externa; en caso // contrario, se producirla una recursin infinita: i Callee2.this.increment();

} Incrementable aetCallbacfcReferenceO { return new Closure( )

) clas9 Caller ( prvate Incrementable callbackHeference; CallerIncrementable cbhl i

10 Clases Internas 655 ( callbackReference * cbh,- ] void ao() { calIbackP.eference.mcrement O ; ]

) public class Callbacks { public 3 tatic void main(StringC] args) { Callee! el new CalleelO; Callee2 c2 = new Callee2(); Mylttcrement.f(c2 ) 7 Caller callerl * new Caller(cll; Caller caller2 * new Caller(c2.getCallbackReferenceO); callerl.go ); callerl.go(); caller2 .go< J; caller2 .go(); i ) / Output t Other operation 1

12 Other operation 2 Other operation 3

10 Clases Internas 656 *///:-

Esto muestra tambin una distincin adiciona! entre el hecho de implementar una interfaz en una clase externa y el hecho de hacerlo en una clase interna. CalleeI es claramente la solucin ms simple en trminos de cdigo. C allee2 hereda de Mylncrement. que ya dispone de un mtodo lncrement( ) diferente que lleva a cabo alguna tarea que no est relacionada con la que la interfaz Incremcntahle espera. Cuando se hereda M\ Increment en Callee2. incrcment( ) no puede ser sustituido para que lo utilice Incrementable, por lo que estamos obligados 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 getCalIbackRecrcnce ). Para permitir algn tipo de conexin con el mundo exterior, la interfaz Incremcntable resulta esencia!. Con este ejemplo podemos ver que las interfaces permiten una completa separacin entre la interfaz y la implementacin.

La clase interna Closure implementa Incremcntable para proporcionar un engarce con Callee2, pero un engarce que sea

10 Clases Internas 657 lo suficientemente seguro. Cualquiera 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 permitira hacer cualquier

cosa).

Caller loma una referencia . Incrementable en su constructor (aunque la captura de la referencia de rctrollamada podra tener lugar en cualquier instante) y luego en algn momento posterior utiliza la referencia para efectuar una rctrollamada a la clase Callee

El valor de las retrollamadas radica en su flexibilidad; podemos decidir de manera dinmica qu mtodos van a ser invocado en tiempo de ejecucin Las ventajas de esta manera de proceder resultarn evidentes en el Capitulo 22, Interfaces grficas Je usuario, en el que emplearemos de manera intensiva las retrollamadas para implementar la funcionalidad GUI {Graphtcal User Inter/ace).

10 Clases Internas 658 Clases internas y marcos de control

Un ejemplo ms concreto del uso de clases internas es el que puede encontrarse en lo que denominamos matxu Je conttvl

Un mareo de trabajo de una aplicacin es una clase o un conjunto de clases diseado para resolver un tipo concreto de problema. Para aplicar un marco de trabajo de una aplicacin, lo que normalmente se hace es heredar de una o ms clases y sustituir algunos de los mtodos, ti 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 diserto basado en el mtodo de plantillas {vase Thinking in Patterns twith Ja\a) en www.\tindllew.nci\ ti mtodo basado en plantillas contiene la estructura bsica del algoritmo, e invoca uno o ms mtodos 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 si que sufren modificacin y en este caso el mtodo basado en plantillas es la parte que permanece 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 aplicacin, que est dominado por la necesidad de responder a cierto suceso Los sistemas que se dedican principalmente a responder a sucesos se denominan sistemas dirigidos por sucesos. 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 Capitulo 22. Interfaces grficas de usuario, la biblioteca Swing de Java es un marco de i

10 Clases Internas 659 control que resuelve de manera elegante el problema de las interfaces GUI y que utiliza de manera intensiva las clases internas.

Para ver la forma en que las clases internas permiten crear y utilizar 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 lu hora actual. El ejemplo que sigue es un marco de control que no contiene ninguna informacin especifica acerca de qu es aquello que est controlando. Dicha informacin se suministra mediante el mecanismo de herencia, cuando se implementa la parte del algoritmo correspondiente al mtodo actionf ).

En primer lugar, he aqu la interfaz que describe los sucesos de control. Se trata de una clase abstracta, en lugar de una verdadera 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: I I j innerclasses/controller/Bvent.java Los mtodos comunes para cualquier suceso de control, package innerclasses.controller;
II

public abstract ciass Event { private long eventTime; protected final long deiayTime; public Event(long deiayTime) ( this.deiayTime = deiayTime; atart t)/

10 Clases Internas 660 ) public void start) ( // Permite la reinicializacin eventTime System.nanoTlme(l deiayTime; public boolean readyO ( rettirn System.nanoTimeO eventTime;

) public abstract void acticnO;

) ///:-

El constructor captura el tiempo (medido desde el instante de creacin del objeto) cuando se quiere ejecutar el objeto Event. v 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 F.n lugar de incluirlo en el constructor. sturt( ) es un mtodo independiente De esta forma, se puede rcinicializarel temponzador despus de que el suceso haya caducado, de manera que el objeto Event puede reutilizarse. Por ejemplo, si queremos un suceso repetitivo, podemos invocar simplemente start() dentro del mtodo action()

10 Clases Internas 661 n?ady() nos dice cundo es el momento de ejecutar el mtodo action( ) Por supuesto. rcady( ) puede ser sustituido 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 almacenan dentro de un objeto contenedor de tipo Uit< Event > (una lista de sucesos), que es un tipo de objeto que analizaremos en ms detalle en el Capitulo 11. Almacenamiento de objetos. Pero ahora lo nico que necesitamos saber es que udd|) aade un objeto Event al final de la lista List, que si/.e( ) devuelve el nmero de elementos de List, que la sintaxis foreach permite extraer objetos Event sucesivos de List, y que mnove ) elimina el objeto Event especificado de List //: innerclasses/controller/Controller.java // El marco de trabaja reutilizable para sistemas de control, package innerclasses.controller; import java.til.22; public class Controller { // Una clase de ^ava.util para almacenar los objetos Event: private List<Event> eventList = new ArrayList<Event> ( ) ; public void addEvent{Event c> ( eventList.addte); ) public void rund ( while (eventList . size i ) > 0) // Hacer una copia para no modificar la lista // mientras se estn seleccionando sus elementos: for(Event e : new ArrayList<Event>(eventList))
i f(e.ready[ ) )

System. out.pri Por alguna rozn. este problema siempre me ha resultado bastante grato de resolver, proviene de mi anterior libro O+Insufe & Out. pero Java permite obtener unu solucin mas elefante. i
22

10 Clases Internas 662 ntln(e) ; e.actio n t); eventLi st.rera ove(e);

] ///:-

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 informacin utilizando el mtodo toString( ) del objeto, invoca el mtodo action( ) y luego elimina el objeto Event de la lista. i

10 Clases Internas 663 Observe que en este diseo, hasta ahora, no sabemos nada acerca de <tt es exactamente lo que un objeto Event hace. Y este 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 trmino 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 enjuego 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 toda* aquellas caractersticas distintivas de dicha implementacin. Las clases internas se usan para expresar los mltiples tipos distintos de acciones (action()J 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 podria llegar a ser tan complejo que terminaramos tratando de buscar una alternativa.

10 Clases Internas 664 Considere una implementacin concreta del marco de control diseado para regular las funciones de un invernadero. Cada accin es totalmente distinta: encender y apagar las luces, iniciar y detener el riego, apagar y encender los termostatos, hacer sonar alarmas y reimciali/ar el sistema. Pero el marco de control esta diseado de tal manera que se aslan fcilmente esuis 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 V escribimos el cdigo de control en la implementacin de action( ).

Como puede suponer por los marcos de trabajo para aplicaciones, la clase CreenhouseControls hereda de Controller //j innerclasses/GreenhouseControls.java // Genera una aplicacin especfica del sistem a// de control, dentro de una nica clase. Las clases // internas permiten encapsular diferente funcionalidad // para cada tipo de suceso, import innerclasses.controller.; public class GreenhouseControlo extends Controller { private boolean light * false; public class LightOn extends Event ( public LightOnHong deiayTime! ( 9 uper(deiayTime)/ } public void act ion) ( i // Poner cdigo de control del hardware aqui I f para encender fsicamente las luces, light = true;

public String toStringO ( return Light is on41*; }

} public class LightOff extends Event { public LightOff1 long deiayTime) i

10 Clases Internas 665 ( super(deiayTime); ) public void actioni) ( // Poner cdigo de control del hardware aqui i f para apagar fsicamente las luce3. light = false;

) )* oublic String toStringO ( return "Light is off"; |

private boolean water - false; public class WaterOn extends Event ( public WaterOn(long deiayTime) ( super(deiayTime); } public void act ion( ) ( // Poner el cdigo de control del hardware aqui. water = true;

) public String toStringO ( return Greenhouse water is on";

10 Clases Internas 666 ) public class WaterOff extends Event { public WaterOff(long deiayTime) { super(deiayTime); } public void action() { f t Poner el cdigo de control del hardware aqui. water = false;

) public String toStringO ( return Greenhouse water is off";

} private String thermostat * "Day"; public class ThermostatNight extends Event { public ThermostatNight(long deiayTime) { super(deiayTime) t ) public void act ionO ( f t Poner el cdigo de control del hardware aqu, thermostat * "Night"; i

10 Clases Internas 667 > public String toStringO { return "Thermostat on night setting;

) )public clase ThermostatDay extends Event { public ThermostatDay(long delayTime) ( super IdeldyTime) ,

) public void actionO ( // Poner el cdigo de control del hardware aqu, thermostat = "Day";

public String toString( ) ( retum "Thermostat on day setting";

) i // Un ejemplo de actianO que inserta un i

10 Clases Internas 668 // nuevo ejemplar de ai misma en la lnea de sucesos: public class Bell extends Event ( public Belliong delayTime) i euper(delayTime) : ) public void actionO ( i addEvent(new Bell(delayTime));

public String toStringl) ( return "Bina!"; )

) public class Restart extends Event ( private Event[] eventList; public Restart{long delayTime, Event[] eventList) ( super(delayTimeI; this.eventList - eventList; for(Event e : eventList) addEvent(e >;

) public void actionO { forEvent e ; eventList) ( e.startO; // Re-ejecutar cada suceso. addEvent(e);

10 Clases Internas 669 ) startOj // Re-ejecutar cada suceso addEvent(this);

) public String toString<) { returr. "Restartina system"/

) i public static class Termnate extends Event ( public Termnate ilong delayTime) { superi delayTime)j ) public void actionO ( System.exit 10) ; ) public String toStringO ( return MTerminatinq"; )

10 Clases Internas 670 } ///:-

Observe que light, water y thermostat pertenecen a la clase externa GreenhouseControK a pesar de lo cual las clases internas pueden acceder a dichos campos sin ninguna cualificacin y sin ningn permiso especial. Asimismo, los mtodos actinn() suelen requerir algn tipo de control del hardware.

La mayora de las ciases Event parecen similares, pero Bell y Restart son especiales. Bell hace sonar una alarma y luego aade un nuevo objeto Bell a la lista de sucesos, para que vuelva a sonar posteriormente. Observe cmo las clases internas casi parecen un verdadero mecanismo de herencia mltiple. Bell y Restart tienen todos los mtodos de Event y tambin parecen tener todos los mtodos de la clase externa GreenhouxeControls.

A Restart se le proporciona una matriz 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 Restart.action( ) para que el sistema se reinicialtcc a si mismo de manera peridica.

10 Clases Internas 671 La siguiente clase configura el sistema creando un objeto GreenhouseControls y aadiendo diversos tipos de objetos Event. Esto es un ejemplo del patrn de diseo Comniand: cada objeto de cventIJst es una solicitud encapsulada en forma de objeto: //: innerclasses/GreenhouseController.java // Configurar y ejecutar el sistema de control de invernadero. / / {Args: 5000} inn>ort innerclasBes.controller; public class GreenhouseController { public static void mam (String[J args) ( GreenhouseControls ge = new GreenhouseControls (I // En lugar de fijar los valores, podramos analizar // informacin de configuracin incluida t i e n un archivo de texto: ge. addEver.t (ge. new Bell (900)) ; EventU ventList = { ge.ne w Therm ostat Night (0), ge.ne w Light 0n(20 0), ge.ne w Light Off(4 00J, ge.ne w Water On(60 0), ge.ne w Water Off(8 00). i

10 Clases Internas 672 ge.ne w Therm osLat Day(1 400)

ge.addEvent(ge.new Restart2Q00, eventList)); if(args.length == 1J ge.addEvent( new GreenhouseControls. Termnate( new Integerargs[0 J)J) ; ge.run()r ) / Cutput: angl Thermostat on night setting Light is or* Light is off Greenhouse water B on Greenhouse water i uCC Thermostat on day setting Restarting svstem Terminating *///:-

siu clase iniciali/a el sistema, para que aada todos los sucesos apropiados. El suceso Kcstart se ejecuta repetidamente y carga cada vez la lista eventList en el objeto GreenhouseControls. Si proporcionamos un argumento de lnea de comandos que indique los mil segundos. Restart terminar 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 archivo en lugar de leerlos en el i

10 Clases Internas 673 cdigo. Uno de los ejercicios del Capitulo 18, F./S, pide, precisamente, que modifiquemos este ejemplo para hacer eso.

Este ejemplo debera permitir 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, Interfaces grficas de usuario. veremos de qu forma tan elegante se utilizan las clases internas para definir las acciones de una interfaz grfica de usuario Al terminar ese captulo, espero haberle convencido de la utilidad de ese tipo de clases.

Ejercicio 24: (2) In GreenhouseControls.java. aada una serie de clases internas Event que permitan encender y apa

gar una serie de ventiladores. Configure GrcenhouseController.java para utilizar estos nuevos objetos Event.

Ejercicio 25: (3) Herede de GreenhouscControls en GreenhouseControb.java para aadir clases niemas Event i

10 Clases Internas 674 que permitan encender y apagar una serie de vaporizadores. Escriba una nueva versin de GreenhouseController.java para utili/ar 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 contenedora, las cosas se complican ligeramente cuando tratamos de heredar de una clase interna. Fl problema es que la referencia secreta al objeto de la clase contenedora Jebe in atizarse. a pesar de lo cual en la clase derivada no hay ningn objeto predeterminado con el que asociarse lis necesario utili/ar una sintaxis especial para que dicha asociacin se haga de forma explcita: //: mnerclasses/Inheritlnner. jav3 // Heredando de una clase interna. class Withlnner { class Inner {)

} public class Inheritlnner extends Withlnner.Inner { l / \ Inheritlnner() () //No se compilar Inheritlnner(Withlnner wi) ( wi.superI)/

10 Clases Internas 675 } public static void main(String(] argsl { Withlnner wi new Withlnner(); Inheritlnner ii new InheritInner(wi);

) ) f/j'.-

Puede ver que Inheritlnner slo amplia la clase interna, no la externa. Pero cuando llega el momento de crear un constructor. el predeterminado no sirve y no podemos limitamos a pasar una referencia a un objeto contenedor. Adems, es necesario utilizar la sintaxis: enclosingClassReference.super();

dentro del constructor. Esto proporciona la referencia necesaria y el programa podr asi compilarse.

10 Clases Internas 676 Ejercicio 26: (2) Cree una clase con una clase interna que tenca un constructor no predeterminado (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 rede fin irnos 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 extema no tiene, en realidad, ningn efecto //: innerclasses/BigEgg.java // No se puede sustituir una clase interna como si fuera un mtodo, import static net .mindview. til. Pnnt. # ; class Egg { private Volk y; protected class Yolk { public YolkO ( print ("Egg. Yolk ()"); }

10 Clases Internas 677 ) public EggO ( print(MNew Egg(J M J; y t* new Volk ()

i 10 Clases internas 678 i ; public class 9iggg extends Egg [ public class Yolk { public YolkO { print<"BigEgg.YolkO"); )

} public static void mainStringl] args' ( new BiaEga();

) ] /* Output: New Egg() Egg.Yolk{)

///*-

El compilador sintetiza automticamente el constructor predeterminado, y ste invoca al constructor predeterminado de la clase base. Podramos pensar que puesto que se est creando un objeto BigF.gg,

i 10 Clases internas 679 i se utilizar la versin sustituida'1 ce Nolk. pero esto no es asi, como podemos ver analizando la salida.

Este ejemplo muestra que no hay ningn mecanismo mgico adicional relacionado con las clases internas que entre en accin al heredar de la clase externa. I^as dos clases internas son entidades completamente separadas, cada una con su propio espacio de nombres. Sin embargo, lo que sigue siendo posible es heredar explcitamente de la clase interna: //: innerclasses/BigEgg2.java ! ( Herencia correcta de una clase interna. mport static net .mindview.util .Print class Egg2 { protected class Yolk ( public Yclk() { print("Egg2.Yolk()"); ) public void f() ( print(nEgg2 .Yolk. f C) ") ;)

) private Yolk y = new YolkO; public Egg2() ( print"New Egg2)n); } public void insertYclk(Yolk yy) ( y yy; } public void g() ( y.fl); }

i 10 Clases internas 680 i ) public class BigEgg2 extends Egg2 ( public class Yolk extends Egg2.Yolk ( public YolkO ( print("BiaEgg2 YolkO"J; ) public void fO ( print I "BigEgg2.Yolk.() ") / }

) public BigEgg2(i { inaertYolk(new Yolkl)); > public static void main(StringlJ args) { Egg2 e2 new BigEgg2 (); e2 .g ) i

) ) / Output: Egg2.Yolkt) New Egg2() Egg2.Yolk() BigEgg2.Yolk ) BiqEgg2.Yolk.f()

i 10 Clases internas 681 i *///:-

Ahora. BlgEgg2.Yolk amplia explicttamente extends Egg2.Yolk y sustituye sus mtodos El mtodo fnsertYo!k( ) permite que BigEgg2 generalice uno de sus propios objetos Yolk a la referencia y en Egg2. por lo que g( ) invoca y.f(). se utiliza 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( ) se utiliza la versin sustituida de f(). Clases internas locales

Como hemos indicado anteriormente, tambin pueden crearse clases internas dentro de bloques de cdigo, normalmente dentro del cuerpo de un mtodo. Una clase interna local no puede tener un especificador de acceso, porque no forma pane de la clase externa, pero s 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: //: innerciasses/LocailnnerCla as.]ava // Contiene una secuencia de objetes, import static net.mindview.util.Print.; interface Counter ( nt next( ) ;

i 10 Clases internas 682 i ) public class LocalInnerClass { private int count = 0; Counter getCounter(final String ame) ( // Una clase interna local: class LocalCounter implements Counter ( public LocalCountert) ( // La clase interna local puede tener un constructor orint (''LocalCounter {) ") ;

] public int next () ( i printnb(ame); // Acceso a variable local final retura count++;

} return new LocalCounter() ;

i 10 Clases internas 683 i // Lo mismo con una clase interna annima: Counter getCounter21 final String ame) { return new Counter() { // La clase interna annima no puede tener un constructor // nominado, sino slo un inicial izador de instancia:

{ Drint("Counter()n);

) public int next() ( printnb trame) ; // Acceso a una variable local final return count--+;

) public static void main(StringU args) { LocallnnerClass lie new LocalInnerClass(); Counter cl = 1ic.getCounter("Local inner "), c2 = lie.getCounter2i"Anonymous inner "); for tint i = 0; i < 5? i++) print(cl.next I));

i 10 Clases internas 684 i i for(int i = 0; i < 5; i+*) print(c2 .next{))/

) /* Output: LocalCounter() Counter() Local inner 0 Local inner 1 Local inner 2 Local inner 3 Local inner 4 Anonymous inner 5 Anonymous 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 ambaslos mismoscomportamientos y capacidades. Puesto que el nombre dela clase interna local no es

accesible fuera del mtodo, la nica justificacin parautilizaruna clase interna local de una claseinterna annima

en lugar

es que necesitemos un constructor nominado y/o un constructor sobrecargado, ya que una clase

i 10 Clases internas 685 i interna annima slo puede utili/ar 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 .class que almacena toda la informacin relativa a cmo crear objetos de dicho tipo (esta informacin genera una metaclase*1 denominada objeto Class), podemos imaginar fcilmente que las clases internas tambin debern producir archivos .class para almacenar la informacin de sus objetos Class. Los nombres de estos archivos /classes responden a una frmula estricta: el nombre de la clase contenedora, seguido de un signo S*. seguido del nombre de la clase interna. Por ejemplo, los archivos .class creados por LocalinncrClass.java incluyen: Counter.class LocalInnerClassSl.class LocalInnerClass$lLocalCou nter.class LocalInnerClass.class

Si las clases internas son annimas, el compilador simplemente genera una serie de nmeros para que acten como identi- ftcadores 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 idcntificador o identificadores de las clases externas.

i 10 Clases internas 686 i Aunque este esquema de generacin de nombres internos resulta simple y directo, tambin es bastante robusto y permite tratar la mayora de las situaciones.5 Puesto que se trata del esquema estndar de denominacin para Java, los archivos generados son automticamente independientes de la plataforma (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 lenguajes de programacin orientada a objetos; por ejemplo, no podremos encontrar nada similar en O-h Ambos conceptos resuelven, conjuntamente, el mismo problema que C++ trata de resolver con su mecanismo de herencia mltiple. Sin embargo. el mecanismo de herencia mltiple en C-*-+ resulta bastante difcil de utilizar, mientras que las interfaces y las clases internas de Java son, por comparacin, mucho ms accesibles.

Aunque estas funcionalidades son. por s mismas, razonablemente sencillas, el uso de las mismas es una de las cuestiones fundamentales de diseo, de forma similar a lo que ocurre con el polimorfismo. Con el tiempo, aprender a reconocer ' Por otro lado. $' es un mcucaricicr de la shet! Unix, por lo que en ocasiones podemos encontramos con problemas o la hora de listar los archivos .class. Resulta un tanto 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 que los programadores se centraran principalmente en los archivos de cdigo fuente aquellas situaciones en las que debe utilizarse una interfaz o una clase interna, o ambas cosas. Pero al menos, en este punto del libro, s que el lector debera sentirse cmodo con Ja sintaxis y la semntica aplicables. A medida que vaya viendo cmo se aplican estas funcionalidades, terminar por interiorizarlas.

Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico Tin Dimkiug in JUM AnnoraledStduUon Gnlda, disponible pa l.i venia en www.MlHdlrhfw.rtei.Almacenamiento de objetos

11

Es un programa bastante simple que slo dispone de una cantidad de objetos con tiempos de

vida conocidos.

Eli general, los programas siempre crearn nuevos objetos basndose en algunos criterios que slo sern conocidos en tiempo 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 tamo, no podemos limitamos 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 formas 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 forma ms eficiente de almacenar un grupo de objetos, v 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 mas general, no podemos saber en el momento de escribir el programa cuntos objetos vamos a necesitar o si har falta una forma mas sofisticada de almacenar los objetos, por lo que la restriccin relativa al tamao fijo de una matriz es demasiado limitante.

La biblioteca java.til tiene un conjunto razonablemente completo de clases contenedoras para resolver este problema, siendo los principales tipos bsicos Lisi. Set. Queue y Map (lista, conjunto, cola y mapa) Estos tipos de objetos tambin se conocen con el nombre de clases de coleccin, pero como la biblioteca Java utiliza el nombre Collection para hacer referencia a un subconjunto concreto de la biblioteca, en este texto utilizaremos el trmino ms general de contenedor Los coniencdores proporcionan formas sofisticadas 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 permite asociar objetos con otros objetos), las clases contenedoras de Java tienen la funcionalidad de cambiar automticamente de tamao. Por lano, a diferencia de las matrices, podemos almacenar cualquier nmero de objetos y no tenemos que preocupamos, mientras estemos escribiendo el programa, del tamao que tenga que tener el contenedor

An cuando no tienen soporte directo mediante palabras clave de Java, 23 las clases contenedoras son herramientas fundamentales 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 utilizacin tpica de los contenedores Aqu, vamos a centramos en los contenedores que se utilizan de manera cotidiana en las tareas de programacin. Posteriormente, en el Captulo 17. Anlisis detallado de fus contenedores, veremos el resto de los contenedores y una serie de detalles acerca de su funcionalidad y de cmo utilizarlos. Diverso* lenguajes como Perl. Poyton y Ruby nenen soporte nativo pan los contenedores Ksic es uno de ti casos en los que lo sobrecarga de operadores resultara conveniente Las clases contenedoras de C*H- y O producen una sintaxis ms limpia utiLi/ando la sobrecarga de operadores.
23

Genricos y contenedores seguros respecto al tipo

Uno de los problemas de utilizar los contenedores anienores a Java SE5 era que el compilador permita insertar un upo incorrecto dentro de un contenedor. Por ejemplo, considere un contenedor de objetos Apple que utilice el contenedor ms bsico general. ArrayList. Por ahora, podemos considerar que Array List es 'una matriz que se expande automticamente''. La utilizacin de una matriz \rrayList es bastante simple: basta con crear una. insertar objetos utilizando add() v acceder ellos mediante get(), utilizando un ndice: es lo mismo que hacemos con las matrices, pero sin emplear corchetes-' ArrayList tambin dispone de un mtodo size ) que nos permite conocer cuntos elementos se han aadido, para 110 utilizan inadvertidamente ndices que estn ms all del contenedor que provoquen un error (generando una excepcin Je tiempo Je ejecucin: hablaremos de las excepciones en el Captulo 12, Tratamiento Je errores mediante excepciones).

En este ejemplo, insertamos en el contenedor objetos Apple \ Orante y luego los extraemos. Normalmente, el compilador Java proporcionar una ad\erteneia. debido a que el ejemplo no usa genricos. Aqui. empleamos una anotacin de Java SE5 para suprimir esas advertencias del compilador. Las anotaciones comienzan cot un signo de *{<? y admiten un argumento; esta anotacin en concreto es a SuppressWarnings > el argumento indica que slo deben suprimirse las advertencias no comprobadas **uncheckeJ): //. Holding/ApplesAr.dOrangesWithoutGeneri es.java f / Ejemplo simple de contenedor (produce advertencias dei compilador). // (ThrowsException)

} Al final del Capitulo 15. Genricas. se incluye una explicacin sobre la gravedad de esie problema. Sin embargo, dicho capitulo tambin explica que los genricos de Java resultan tiles para olras cosas adema* de definir contenedores que sean eeuros con respecto al Upo de los datos.

import java.til.; class Apple { prvate static long counter; prvate final long id = counter-* t ; oublic long id) { retum id; )

} class Orange () public class ApplesAndOrangesWlthoutGenerics j SuppressWarninga ("unchecked"1 public etatic void main(String{] args) { ArrayList apples new ArrayList(); forint i = 0: i < 3; i-f+1 apples.add(new Apple()J; // No se impide aadir un objeto Orange: apples . add tnw Orange O) t forlint i *s 0 ; i < apples . size() ; i-*--) ((Apple)apples.get< i)).id()? i // Orange slo se detecta en tiempo de ejecucin

) / Execute .o see output) *///.- Hablaremos ms en detalle de las anotaciones Java SE5 en el Capitulo 20. Anotaciones.

as 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
I

automticamente de Object). Puesto que ArravIJst almacena objetos de tipo Object. no slo se pueden aadir objetos Apple a este contenedor utilizando el mtodo add( ) de ArrayList. sino que tambin se pueden aadir objetos Orange sin que se produzca ningn tipo de advertencia ni en tiempo de compilacin ni en tiempo de ejecucin. Cuando luego tratamos de extraer lo que pensamos que son objetos Apple utilizando el mtodo get( ) de ArrayList. obtenemos una referencia a un objeto de tipo Objeet que deberemos proyectar sobre un objeto Apple Entonces, necesitaremos encerrar toda la expresin entre parntesis para forzar la evaluacin de la proyeccin antes de invocar el mtodo id( ) de Apple: en caso contrario, se producir un error de sintaxis.

En tiempo de ejecucin, al intentar proyectar el objeto Orange sobre un objeto Apple, obtendremos un error en la forma de |u excepcin que antes hemos mencionado.

En el Capitulo 20. Genricos, veremos que la creacin de clases utilizando los genricos de Java puede resultar muy compleja Sin embargo, la aplicacin de clases genericas predefinidas suele resultar sencilla Por ejemplo, para definir un contenedor ArrayUxt en el que almacenar objetos Apple, tenemos que indicar ArrayLJst<Apple> en lugar de slo ArrayUst. Los corchetes angulares rodean los parmetros de tipo (puede haber ms de uno), que especifican el tipo o tipos que pueden almacenarse en esa instancia del contenedor

Con los genricos ev itamos, en tiempo de compilacin, que se puedan introducir objetos de tipo incorrecto en un contenedor He aqu de nuevo el ejemplo utilizando genricos: : holding/ApplesAndOrangesWithGene rics.java import java.til.; public class ApplesAndOrangesWithGeneri.es ( public static void main(String(] args) { ArrayList<Appie> appies = new ArrayList<Apple>O; forint i = 0 ; i < 3; i*+) appies.addInew Apple(I) ;

// Error en tiempo de compilacin: // appies addinew Orangei I); forfint i = 0 ; i < applea.sizel ; i-f+J System.out .prmtlnappies get(i) . id0)f // Utilizacin de foreach: for(Apple c : appies) j System.out .println le. id()) /

) /* Output:

0 2 0 X 2

///?-

Ahora el compilador evitar que introduzcamos un objeto Orange en appies. conviniendo el error en tiempo de ejecucin en un error de tiempo de compilacin

Observe tambin que la operacin de proyeccin ya no es necesaria a la hora de extraer los elementos

de la lista. Puesto que la lista conoce que tipos de objetos almacena, ella misma se encarga de realizar la proyeccin por nosotros cuando invocamos *et( ) Por tanto con los genricos no slo podemos estar seguros de que el compilador comprobar el tipo de los objetos que introduzcamos en un contenedor, sino que tambin obtendremos una sintaxis 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 sintaxisforeach para seleccionar cada elemento de la lista.

No estamos limitados a almacenar el tipo exacto de objeto dentro de un contenedor cuando especificamos dicho tipo como un parmetro genrico. La operacin de generalizacin ( upeasting) funciona de igual forma con los genricos que con los demas tipos: //: holding/GenericsAndUpcasting.jav a -mport java.til.*; class GrannySmith extends Apple (} class Gala extends Apple )) class Fuji extends Apple {) class Braeburn extends Apple {} public class GenericsAndUpcasting ( public static void main(Stringti args) ( ArrayLast<AppIe> apple6 new ArrayList<Apple> { ) apples. add (new GrannySmith.() J ; apples. add <new Gala (M apples .add \ new FujiO); apples.add(new Braeburn())/ for(Apple c : apples) System.cut.println(c);

! } /* Output; (Sample) GrannySmi th37d772e Galallb8Se7 Fuji35ce36 Braeburn 757aef *///:-

Por tanto, podemos aadir un subtipo de Apple a un contenedor que hayamos especificado que va a almacenar objetos Apple.

La salida se produce utilizando el mtodo toString( ) predeterminado de Object, que imprime el nomhrede la clase seguido de una representacin hexadecimal sin signo del cdigo hash del objeto (generado por el mtodo liashCodef )). Veremos ms detalles acerca de los cdigos hash en el Capitulo 17. Anlisis detallado de los contenedores.

Ejercicio 1: (2) Cree una nueva clase llamada (erbil con una variable int gerbUNumber 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 lista. Ahora, utilice el mtodo get( ) para desplazarse a travs de la lista 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 distintos, expresados mediante las interfaces bsicas de la biblioteca

Collection: una secuencia de elementos individuales a los que se aplica una o ms reglas Un contenedor List debe almacenar los demonios en la forma en la que fueron insertados, un contenedor Set no puede lener elementos duplicados y un contenedor Queueproduce los elementos en el orden determinado por una disciplina de cola (que normalmente es el mismo orden en el que fueron insertados)
1.

Map un grupo de parejas de objetos clave-valor, que permite efectuar bsquedas de valores utilizando una clase Un contenedor ArrayList permite buscar un objeto utilizando un nmero, por lo que en un cierto sentido sirve para asociar nmeros con los objetos. 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 utiliza para buscar un objeto valor mediante un objeto clave, de la misma forma que buscamos una definicin utilizando una palabra). Los contenedores Map son herramientas de programacin muy potentes.
2.

Aunque no siempre es posible, deberamos tratar de escribir la mayor parte del cdigo para que se comunique con estas interfaces; asimismo, el nico lugar en el que deberamos especificar el tipo concreto que estemos usando es el lugar de la creacin del contenedor. Asi, podemos crear un contenedor List de la forma siguiente: Lst<Apple> apples - new ArrayListcApple> () ,*

Observe que el contenedor ArrayList ha sido generalizado a un contenedor List, por contraste con la forma en que lo habamos tratado en los ejemplos anteriores. La idea de utilizar la interfaz es que. si posteriormente queremos cambiar la imple- mentacin. lo nico que tendremos que hacer es efectuar la modificacin en el punto de creacin, de la forma siguiente: Llst<Apple> apples * new LinkedList<Apple>();

/Vsi. normalmente crearemos un objeto de una clase concreta, lo generalizaremos a la correspondiente interfaz y luego utilizaremos la interfaz en el resto del cdigo.

Esta tcnica no siempre sirve, porque algunas clases disponen de funcionalidad adicional. Por ejemplo, LinkedList nene mtodos adicionales que no forman 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 Collection (representado aqu mediante un contenedor Arra> List) con objetos Integer y luego imprime cada elemento en el contenedor resultante: //: holding/SimpleCollection.java itepore java.til.*; public clasa SimpleCollection { public static void mam(Scrmgti args) | Collection<Integer> c = new ArrayLlst<Inteaer>(); forint = 0 ; i < 10 ; i * * ) c. add t i} / / / Aut oboxmg fordnteger i : c) System.out.print(i ", 1M);

) ) / Output:

0, 1. 2. 3. 4, 5, 6, 7. 8. 9.

de una clase de Collection funciona- el contenedor Collection. Sin embar-

Puesto que este ejemplo solo utiliza mtodos Collection. cualquier objeto que herede r. pero ArrayList representa el tipo ms bsico de secuencia.

El nombre del mtodo add( ) sugiere que ese mtodo introduce un nuevo elemento en go, la documentacin indica expresamente que add( ) garantiza que este objeto Collection contenga el elemento especificado" Esto es asi para permitir 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", porque las listas no se preocupan de si existen duplicados.

Todas las colecciones pueden recorrerse utilizando la sintaxis foreach, como hemos mostrado aqu. Ms adelante en el capitulo aprenderemos acerca de un concepto ms flexible denominado teidor.

Ejercicio 2: (I) Modifique SimpleColIcction.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 coleccin. l mtodo \rra\s.asl.ist( ) toma una matriz \ una lista de elementos separados por comas (utilizando vararas) y lo transforma en un objeto List Collections.addAll( ) toma un objeto Collection y una matriz o una lista separada por comas y aade los elementos a la coleccin I le aqu un ejemplo donde se ilustran ambos mtodos, as como el mtodo addAlK ) ms convencional que forma parte de todos los tipos Collection //: holding/AddingQroups.java // Adicin de grupos de elementos a objetos Collection. import java.util.; public class AddingGroups { public static void mainiStrmgll args? | Collection<Integer> collection = new ArrayLst<Integer>(Arrays.asList (1, 2, 3, 4. 5)) r IntegerU morelnts = ( 6 , 7, 8 , 9, 10 );

collection.addAll(Arrays.asList(morelnts)); // Se ejecuta bastante ms rpido, pero no se puede // construir una coleccin de esta forma: Collections.addAli(collection. 11, 12, 13, 14, 15); Collections.addAll(collection. morelnts); t i Produce una lista "respaldada* en una matri2 : List<Integer> list = Arrays.asList(16, 17, 1 $, 19, 20); list.setd, 99); // OK modificar un elemento // list.add(21); // Error de ejecucin porque la matriz // subyacente no se puede cambiar de tamao.

) ///:-

El constructor de una coleccin puede aceptar otra coleccin con el fin de utilizarla para metalizarse a si misma, asi que podemos emplear Arrayi.asLisrt( ) para generar la entrada para el constructor. Sin embargo. Collections.addAll( ) se ejecuta 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 utilizar.

El mtodo miembro Collcction.addAll( ) slo puede tomar como argumento otro objeto Collection. por lo que no es tan flexible como Arrays.asUst( ) o Collections.add\ll( ). que utilizan listas de argumentos variables.

Tambin es posible utilizar directamente la salida de Arrays.asListO como unalista, representacin subyacente en

pero

la

este caso es la matriz, que no se puede cambiar de tamao. Si tratamos de aadir o borrar elementos en dicha lista, 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 atencin a la variable a la que la estemos asignando. lin ocasiones, esto puede causar un problema: //: holding/AsListInference.java // Arrays.asListO determina el tipo en s mismo, import java.util.*; class Snow {} class Powder extends Snow () class Light

extends Powder {} class Heavy extends Powder {} class Crusty extends Snow {} class Slush extends Snow {) public class AsListInference ( public static void main(String{| args' { List<Snow> snowi = Arrays.asList{ new Crusty(), new Slush()f new PowderIJ); // No se compilar: I t List<Snow> snow2 = Arrays.asList( // new Light(), new Heavy()); // El compilador dir: // found : java.util.Llst<Powder> i f required: java.util.List<Snow> f t Collect ions.addAllt) no se confunde: List<Snow> snow3 = new ArrayList<Snow>(); Collections.addAl1(snow3, new Light 0, new HeavyO); Proporcionar una indicacin utilizando una t f especificacin explcita del tipo del argumento:
11

ListSnow> snow4 = Arrays.<Snow>asLisc( new Light O , new HeavyO ) ;

} ///:-

.\l tratar de crear snow2. Arrays.asi Jst() slo dispone del tipo Powder, por lo que crea un objeto List<Powder> en lugar de List<Snow>. mientras que Collections.addAIK ) funciona correctamente, porque sabe, a partir del primer argumento, cual es el tipo de destino.

Como puede ver por la creacin de snow4. es posible insertar una "indicacin en mitad de Arrays.asList( ). para decirle al compilador cual debera ser el tipo de destino real para el objeto List producido por Arrays.asLisK ). Fsto se denomina especificacin explcita del tipo de argumento.

Los mapas son ms complejos como tendremos oportunidad de ver. y la biblioteca estndar de Java no proporciona ninguna forma para inicializarlos de manera automtica, salvo a partir de los contenidos de otro objeto Map Impresin de contenedores

Es necesario utilizar Arrays.toStrinj( ) para generar una representacin imprimible de una matriz, pero los contenedores se imprimen adecuadamente sin ninguna medida especial. He aqu un ejemplo que tambin nos va a permitir presentar los contenedores bsicos de Java: //: holding/PrintmgContainers. ;java /,' Los contenedores se imprimen automticamente. import j ava. til. ,* import static net.mindvlew.util.Print. *; public class Prir.tingContainers { static Collection fill(Collection<S tring> collection) { collection.add(

"rat") ; collection.add("c at"); collect ion.add ("dog"); collection.add{"d og"); return collection; i static Map fill(Map<String,S tring map) ( map.put("rat", "Fuzzy"); map. put ( "catH, "Rags1),* map.put("dog". "Boaco"); map.put("dog". "Spot"); return map;

) public static void main(String 13 args> ( print ifill(new ArrayList<String> 1 ) / print (fill(new LinkedListcString()))j print(Eill(new HashSet<String>I))): printIfill(new TreeSet<String>() ) ) ; print(fill(new LinkedHashSet<String><))); print(fill(new HashMap<String,String>())> print(fill(new TreeMap<String, String>())) ? print ifill(new LinkedHashMap<String,String O));

) } /* Output: [rat, cat, dog, dog] [rat, cat, dog, dog] [dog. cat, rat]

[cat, dog, rat] [rat. cat, dog] (dog=5pot, cat=Rags, rat=Fuzzy) (cat-Rags, dog=Spot, rat=Fuzzy) {rat=Fuzzy, cat=Rags, dog=Spot)

*///:-

Este ejemplo muestra las dos categorias principales en la biblioteca de contenedores de Java. La distincin entre ambas se basa en el numero de elementos que se almacenan en cada position del contenedor. La categora Collection slo almacena un elemento en cada posicin: esta categora incluye el objeto l.ist. que almacena un grupo de elementos en una secuencia especificada, el objeto Set. que no permite aadir un elemento idntico a otro que ya se encuentre dentro del conjumo \ el objeto Queuc, que slo permite insertar objetos en un extremo del contenedor y extraer los objetos del otro 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 pane, almacena dos objetos, una clave y un valar asociado, en cada posicin.

A la salida, podemos ver que el comportamiento de impresin predeterminado (que se implementa medanle el mtodo toStringt ) de cada contenedor) genera resultados razonablemente legibles. Una coleccin se imprime rodeada de corchetes, estando cada elemento separado por una coma. Un mapa estar rodeado de llaves, asocindose las claves y los valoro mediante un signo igual (las claves a la izquierda y tos valores a la derecha).

lil primer mtodo fill( ) funciona con todos los tipos de coleccin, cada uno de los cuales se encarga de implementar el mtodo add( ) para incluir nuevos elementos.

ArrayList y LinkedList son tipos de listas y, como puede vera la salida, ambos almacenan los elementos en el mismo orden en el que fueron insertados. La diferencia entre estos dos tipos de objeto no esta slo en la velocidad de ciertos tipos de operaciones, sino tambin en que la clase LinkedList contiene ms operaciones que ArrayList. Hablaremos ms en detalle de estas operaciones posteriormente en el capitulo.

HashSet. TreeSet y LinkedHashSet son tipos de conjuntos. La calida muestra que los objetos Set slo permiten almacenar una copia de cada elemento (no se puede introducir dos veces el mismo elemento), pero tambin muestra que las diferentes implementaciones de Set almacenan los elementos de manera distinta. HashSet almacena los elementos utilizando una tcnica bastante compleja que analizaremos en el Capitulo 17, Anlisis detallado de los contenedores, lo nico que necesitamos saber en este momento es que dicha tcnica representa la forma ms rpida de extraer elementos y. como resultado, el orden de almacenamiento puede parecer bastante extrao (a menudo, lo nico que nos preocupa es s un cierto objeto forma parte de un conjunto, y no el orden en que aparecen los objetos). Si el orden de almacenamiento fuera importante. 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 aadidos.

Un mapa ( tambin denominado matriz asociativa) permite buscar un objeto utilizando una clave, como si fuera una base de datos simple. El objeto asociado se denomina valor. Si tenemos un mapa que asocia los estados americanos con sus capitales y queremos saber la capital de Ohio, podemos buscarla utilizando "Ohio como clave, de forma muy similar al proceso de acceso a una matnz utilizando 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(kc). valu) 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. stn realizar 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 automticamente. Asimismo, los mapas saben cmo imprimirse, mostrando la asociacin existente entre claves y valores. En el orden en que se mantienen las claves y valores dentro de un objeto Map no es el orden de insercin, porque la implemcn- tacin 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 LinkedlIashMap Al igual que HashSet. HashMap proporciona la tcnica de bsqueda ms rpida, no almacenando los elementos en ningn orden aparente. Un objeto TreeMap mantiene las claves en un orden de comparacin ascendente, mientras que l.nkedHashMap 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: (3) Cree una clase generadora que devuelva nombres de personajes (como objetos String) de su pelcula

favorita cada vez que invoque next( ), y que suelva al principio de la lista de personajes una vez que haya acabado con todos los nombres. Utilice este generador para rellenar una matriz, un ArrayList. un LinkedList. un HashSet. un LinkedHashSet y un TreeSet. y luego imprima cada contenedor.

List

Existen dos tipos de objetos List: 11 Almacenamiento de objetos 709 Las listas garantizan que los elementos se mantengan en una secuencia concreta. La interfaz List aade varios mtodos a Collection que permiten la insercin y la eliminacin de elementos en mitad de una lista.El objeto bsico ArrayList. que es el que mejor permite acceder a los elementos de forma aleatoria, pero que resulta ms lento a la hora de insertar y eliminar elementos en mitad de una lista.

El objeto LinkcdLst. que proporciona un acceso secuencia! ptimo, siendo las inserciones y borrados en mitad de una lista enormemente rpidos. IJnkedL.ist resulta relativamente lento para los accesos aleatorios, pero tiene muchas ms funcionalidades que ArrayList

El siguiente ejemplo se adelanta un poco dentro de la estructura del libro, utilizando una biblioteca del Captulo 14, Informacin de tipos para importar typeinfo.pets. Se trata de una biblioteca que contiene una jerarqua de clases Pet (mascota). junto algunas herramientas para generar aleatoriamente objetos Pet. No es necesario entender todos los detalles en este momento, sino que basta con saber que existe: (l) una clase Pet y varios subtipos de Pet y (2) que el mtodo pet.i4rrayList( ) esttico devuelve un mtodo ArrayList lleno de objetos Pet aleatoriamente seleccionados: //: holding/ListFeatures. java mport typeinfo.pets.*; import java.util.; import static net .mmdview.util. Print. *; public class ListFeatures { public static void mam(Stringti args) ( Random rana = new Random(47); List<Pet> pets * Pets.arrayList(7); print("l: " - pets); Hmster h * new Hmster(); pets.addfh); // Cambio de tamao automtico print<H2 : " print("3:" t + pets); pets.containsh));

Existen dos tipos de objetos List: 11 Almacenamiento de objetos 710 pets.remove(h); // Eliminar objeto a objeto Pet p = pets.get(2); print("4: " + p + " " + pecB.indexOftp)); Pet cytnric = new Cymrie (J ; print(M5:H print (*6 : // Debe ser el objeto exacto: print("7: " pets.remove(p)>; print("8 : " pets); pets.addO, new MouseO); // Insertar en un determinado ndice print ("9; + pets); List<Pet> sub * pets.subList(1, 4); print ("subList: +- sub); print CIO: pets.containsAll(sub)); Ccllections.sort(sub); // Ordenacin de la coleccin print (* sor ted subList: u -t- sub); // El orden no es importante en containsAll(J; print ("11: '* + pets. containsAll (sub) ? ; Collections.shuf fie'.sub, rand) ; // Mezclar los elementos print("shuffled subList: " +sub); print("12: " f pets.containsAll(sub)); List<Pet> copy * new ArrayList<? et>(pets); sub Arrays.asList(pets.get(1) , pets.get(4)); print("sub: " sub); copy.retainAll(sub); print(M13: ** copy); copy = new ArrayList<Pet> (pets) ; // Obtener una nueva copia copy .remo%*e <2); // Eliminar segn un ndice print (14: M - copy); copy.removeAllisub); // Slo elimina objetos exactos print("15: " + copy); copy.set(1, new Mouse O); // Sustituir un elemento print("16: " copy); copy .addAll <2, print(**17: print("18: " " sub),* // Insertar una lista en el medio copy); pets.isEmpty()); * pets.indexOficymric)); M+ pets .remove (cymnc) ) ;

Existen dos tipos de objetos List: 11 Almacenamiento de objetos 711 pets.clear(); // Eliminar todos los elementos print ("19: print("20t " " i tpets); pets.isEmpty))/

pets.addAll(Pets.arrayList(4)); print("21 : " pets); Object [1 o = pets.toArray(); print("22: " 4o[3]); Pet[] pa = pets.toArray <r.ew PetlO]); print ("23: * -t pa[3].idt));

) } /* Output: 1: [Rat,Manx, Cymric, Mutt. Cymric, Pug] 2: [Rat,Manx, Cymric, Mutt. Cymric, Pug, Hamster] 3: true 4: Cymric 2 5: 1 6 : false 7: true 8 : (Rat,Manx, 9: [Rat,Manx,
10

Pug, Pug,

Mutt, Pug, Cymric, Mutt, Mouse, Pug, Cymric, Pug]

Pug]

subList: [Manx, Mutt, Mouse] .* true Mouse, Mutt] true true sorted subList:[Manx. 11 : 12 :

shuffled BubList: [Mouse, Manx, Mutt]

Existen dos tipos de objetos List: 11 Almacenamiento de objetos 712 sub: [Mouse, Pug] 13 i 15: 17: 18: [Mouse, Pug] [Rat, Mutt, Cymric, [Rat, Mouse, false Mouse, Pug] Pug, Cymric, Pug] 14j [P.at, Mouse, Mutt, Pug, Cymric, Pug] 16: [Rat, Mouse, Cymric, Pug]

19; [] 20 : 22: 23; true Rat. EgyptianMau] EgyptianMau 14 21j[Manx, Cymric,

///*-

iemos numerado los lincas de impresin para poder establecer la relacin de la salida con el cdigo fuente. La primera linea de salida muestra la lista original de objetos Pe. A diferencia de las matrices, un objeto List permite aadir o eliminar ele mentos despus de haberlo creado y el objeto cambia automticamente de tamao sa es su caracterstica fundamental, M trata de una secuencia modificable. En la linea de salida 2 podemos ver el resultado de aadir un objeto Hmster El objeto se ha aadido al final de la lista
1

Existen dos tipos de objetos List: 11 Almacenamiento de objetos 713 Podemos averiguar si un objeto se encuentra dentro de la lista utilizando el mtodo contains( ). Si queremos eliminar un objeto, podemos pasar la referencia a dicho objeto al mtodo remove(). Asimismo, si disponemos de una referencia a un objeto, podemos ver en que nmero de ndice esta almacenado ese objeto dentro de la lista utilizando nde\Of( ). como puede verse en la linea de salida 4.

A la hora de determinar si un elemento forma pane de una lista, a la hora de descubrir el ndice de un elemento y a la hora de eliminar un elemento de una lista a panir de su referencia, se utiliza el mtodo equa!s() (que forma parte de la clase ra/ Object) Cada objeto Pet se define como un objeto nico, por lo que, aunque existan dos objetos Cvinric en la lista, si creamos un nuevo objeto Cymric y lo pasamos a ndexOff ). el resultado ser -1 (indicando que no se ha encontrando el objeto); asimismo, s tratamos de eliminar el objeto con remove(). el valor devuelto ser falte Para otras clases, el mtodo equuh( ) puede estar definido de forma diferente: dos objetos String. por ejemplo, sern iguales si los contenidos de las cadenas de caracteres son idnticos. Asi que, para evitamos sorpresas, es importante ser consciente de que el comportamiento de n objeto List varia dependiendo del comportamiento del mtodo equals()

En las lineas de salida 7 y 8. podemos ver que se puede eliminar perfectamente un objeto que se corresponda exactamente con otro objeto de la lista.

Tambin resulta posible insertar un elemento en mitad de la lista, como puede verse en la linea de salida 9 y en el cdigo que la precede, pero esta operacin nos permite resaltar un potencial problema de rendimiento 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 acceso 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 contraro, deberamos emplear un objeto LinkedList en caso de tener que llevar a cabo esa operacin? De ninguna manera: simplemente significa 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 comienza a ralentizarse, podemos sospechar que el posible culpable es la implemento ion de la lista concreta que hemos elegido (la mejor forma de descubrir uno de estos cuellos de botella, como podr

Existen dos tipos de objetos List: 11 Almacenamiento de objetos 714 ver en el suplemento http Mindllew ner Books/BetterJava. consiste en utilizar un perfilador). El de la optimizacin es un problema bastante complicado, y lo mejor es no preocuparse por l hasta que veamos que es absolutamente necesario (aunque comprender los posibles problemas siempre resulta til).

El mtodo sul)List( ) permite crear fcilmente una sublista a partir de otra lisia de mayor tamao, y esto produce de forma natural un resultado truc cuando se pasa la sublista a containsAlH ) para ver si tos elementos se encuentran en esa ista de mayor tamao. Tambin merece la pena recalcar que el orden no es importante: puede ver en las lineas de salida II y 12 que* al invocar los mtodos Collections.sort( ) y Collections.shuffle() (que ordenan y aleatorizan. respectivamente, los elementos) con la sublista sub. el resultado de contuinsAU( ) no se ve afectado. subList( ) genera una lista respaldada por la lista original. Por tanto, los cambios efectuados en la lista devuelta se vern reflejados en la lista original, y viceversa.

El mtodo retainAll( ) es. en la prctica, una operacin de interseccin de conjuntos, que en este caso conserva todos los elementos de copy que se encuentren tambin en sub De nuevo, el comportamiento resultante depender del mtodo eqmils( )

La linea de salida 1-4 muestra el resultado de eliminar un elemento utilizando su nmero ndice, lo cual resulta bastante ms directo que eliminarlo mediante la referencia al objeto, ya que no es necesario preocuparse acerca del comportamiento de cquals( ) cuando se utilizan ndices.

Existen dos tipos de objetos List: 11 Almacenamiento de objetos 715 El mtodo removeAIK ) tambin opera de manera distinta dependiendo del mtodo equals(). Como su propio nombre sugiere, se encarga de eliminar de la lista todos los objetos que esten en el argumento de tipo List.

El nombre del mtodo set( ) no resulta muy adecuado, debido a la posible confusin con la clase Sel. un mejor nombre habra sido rcplacc'* (sustituir) porque este mtodo se encarga de sustituir eJ elemento situado en el ndice indicado (el primer argumento) con el segundo argumento.

La linea de salida 17 muestra que, para las listas, existe un mtodo uddA!l( ) sobrecargado que nos permite insertar I nueva lista en mitad de la lista original, en lugar de limitamos a aadirla ul final con el mtodo addAll( ) incluido en Collection.

Las lineas de salida 18-20 muestran el efecto de los mtodos isF.mpty() y clear( ).

Las lineas de salida 22 y 23 muestran cmo puede convertirse cualquier objeto Collection en una matriz utilizando 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 matriz del tipo especificado (suponiendo que los mecanismos de comprobacin de tipos no detecten ningn error). Si la matriz utilizada como argumento resulta demasiado pequea para almacenar todos los objetos de la lista List (como sucede en este ejemplo). toArray() crear una nueva matriz del tamao apropiado. Los objetos Pet tienen un mtodo id(). pudiendo ver en el ejemplo como se invoca dicho mtodo para uno de los objetos de la matriz resultante.

Existen dos tipos de objetos List: 11 Almacenamiento de objetos 716 Ejercicio 5: ModifiqueListFeatures.java autoboxing) en lugar de parautilizar enteros(recuerdela caracterstica de

objetos Pet. y explique las diferencias que haya en los resultados.

Ejercicio 6: (2)Modifique ListFeatures.java para utilizar cadenas de caracteres en lugar de objetos Pet. y explique

las diferencias que haya en los resultados.

Ejercicio 7: (3) Cree una clase y construya luego una matriz imcializada de objetos de dicha clase. Rellene una lisia a

partir de la matriz. Cree un subconjunto de la lista utilizando sub Jst(). y luego elimine

Existen dos tipos de objetos List: 11 Almacenamiento de objetos 717 dicho subconjim- to de la lista. Iterator

En cualquier contenedor, tenemos que tener una forma de insertar elementos v de volver a extraerlos. Despus de todo, esa es la funcin principal de un contenedor almacenar cosas. En una lista, add( ) es una de las formas de insertar elementos y gei() es una de las formas de extraerlos.

Si queremos razonar a un 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 que sucede si escrib, mos codigo para una lista y posteriormente descubrimos que seria conveniente aplicar ese mismo cdigo a un conjunto? Suponga que quisiramos escribir desde el principio, un fragmento de cdigo de propsito general, que no supiera con que tipo de contenedor est trabajando, para poderlo utilizar con diferentes tipos de contenedores sin reescribir dicho cdigo Cmo podramos hacer esto?

Podemos utilizar el concepto de teidor (otro patrn de diseo) para conseguir este grado de abstraccin. Un iterador es un objeto cuyo trabajo consiste en desplazarse a travs de una secuencia y seleccionar cada uno de los objetos que la componen. sin que el programa cliente tenga que preocuparse acerca de la estructura 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 encontramos con restricciones aparentemente extraas que afectan a los iteradors; 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:

Existen dos tipos de objetos List: 11 Almacenamiento de objetos 718


1.

Pedir a una coleccin que nos devuelva IUI iterador utilizando un mtodo terator( ). Dicho iterador estar preparado para devolver el primer elemento de la secuencia.

2.

Obtener el siguiente objeto de la secuencia mediante next().

3.

Ver si liay ms objetos en la secuencia utilizando el mtodo hasNext()

4.

Eliminar el ultimo elemento devuelto por el iterador mediante remove().

Para ver cmo funciona, podemos volver a utilizar las herramientas de la clase Pet que hemos tomado prestadas del Capitulo 1 4, Informacin de tipos. //: haldina/Simplelteration. java import typeinfo.pets.; lmport nava.til.; public class 5impleIteration ( public static void maini.String(] args) [ List<Pet> pets = Pets.arrayList(12);

Existen dos tipos de objetos List: 11 Almacenamiento de objetos 719 Iterator<Pet> it = pets.iterator(}; while(it.hasNextt)1 ( Pet p = it.nextM; System, out .prinMp. id () + ":" -*> p " *)

) System.out.printlnl}; // Un enfoque ms simple, siempre que sea posible: for(Pet p : pets) System.out-print (p. id () " : + p * " System.out.println (); //Un iterador tambin permite eliminar elementos: it = pets. iterator O ,* forlint i = 0 ; 1 <r 6 ; i**) ( it.next(); it.remove(); i System.out.println(pets) ;

) / Outputs O.-Rat l:Manx 2 .-Cymric 3:Mutt 4: Pug 5: Cymric 6 : Pug 7:Manx 8 :Cymric 9:Rat 10 : Egypt ianNau 11 :Kamerer 0:Rat I:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric :?ug 7:Manx 8 :Cymric 9:Rat 10:EgyptianNau 11 :Hamster [Pugj Manx. Cymric, Rat, EgyptianMau, Hamster]

Existen dos tipos de objetos List: 11 Almacenamiento de objetos 720 //A-

Con un objeto Iterator, no necesitamos preocupamos acerca del nmero de elementos que haya en el contenedor. Los meto* dos hasNext() y nex(( ) se encargan de dicha tarea por nosotros.

Si simplemente nos estamos desplazando hacia adelante por la lista y no pretendemos modificar el propio objeto l.ist. la sintaxis fijreach resulta ms sucinta.

Un iterador permite tambin eliminar el ltimo elemento generado por next( ). lo que quiere decir que es necesario invocar a nextl ) antes de llamar a remove( ).4

Esta idea de tomar un contenedor de objetos y recorrerlo para realizar una cierta operacin con cada uno resulta muy potente y haremos un extenso uso de ella a lo largo de todo el libro.

Ahora consideremos la creacin de un mtodo displayt ) que sea neutral con respecto al contenedor

Existen dos tipos de objetos List: 11 Almacenamiento de objetos 721 utilizado: //: holdmg/CrossContamer Iteration, java import typeinfo.pets.*; import java.util. * public class CrossContainerlteration { public static void display(Iterator<Pet> it) | while(it,ha9Next{)) { Pet p = it.next()/ i i System out.print(p.id() * -r p " ");

System.out.printlni) ;

public static void main(String[J args) ( ArrayList<Pet> pets =* Pets .arrayList (8 ) ; LinkedList<Pet> petsLL = new LinkedList<Pet>(pets) ; HashSet<Pet> petsHS ^ new HashSet<Pet> (pets) ; TreeSet<Pet> petsTS = new TreeSet<Pet>(pets) ; display(pets.iterator U J ; display(petsLL.iterator I I I ; display(petsHS.iterator()) ; display(oetsTS.iterator I));

} } / Output:
0 :Rat

l:Manx 2:Cymnc 3:Mutt 4: Pug 5:Cymric 6 :Pug 7:Manx 0:Rat l:Manx 2:Cymric 3rMutt 4:Pug 5:Cymric 6 :Pug 7:Manx 4: Pug 6 :Pug 3:Mutt l:Manx 5:Cymric 7:Manx 2:Cymric 0:Rat 5:Cymric 2:Cymric 7:Manx l:Manx 3:Mutt 6 : Pug 4:Pug 0:Rat *///:-

Ejercicio 8:(I)Modifique el Ejercicio I para que utilice un iterador para recorrer la lista mientras se invoca hop( ) 722 Piensa en Java Observe que <lisplav() no contiene ninguna informacin acerca del tipo de secuencia que est recorriendo, lo cual nos muestra la verdadera potencia del objeto Iterator: la capacidad de separar la operacin de recorrer una secuencia de la estructura subyacente de recorrer dicha secuencia. Por esta razn, decimos en ocasiones que los iteradores unifican el acceso a los contenedores.Ejercicio 9: (4) Modifique innerclasses/Sequence.java para que Sequencc funcione con un objeto Itcrafor en lugar

de un objeto Selector

Ejercicio 10: (2)Modifique el Ejercicio 9 del Capitulo 8. Polimorfismo para utilizar un objeto ArrayList para almacenar los objetos Rodent y un iterador para recorrer la secuencia de objetos Rodent.

Ejercicio 11: (2) Escrba un mtodo que utilice un iterador para recorrer una coleccin e imprima el resultado de

toString( ) para cada objeto del contenedor. Rellene todos los diferentes tipos de colecciones con una serie de objetos y aplique el mtodo que haya diseado a cada contenedor.

Ejercicio 8:(I)Modifique el Ejercicio I para que utilice un iterador para recorrer la lista mientras se invoca hop( ) 723 Piensa en Java Listlterator

Listlterator 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 ce los elementos siguiente y anterior, en relacin al lugar de la lista hacia el que el iterador est apuntando, permite sustiruir el ltimo elemento listado utilizando el mtodo set() Podemos generar un iterador Listlterator que apunte al principio de la lista invocando listlterator( ). y tambin podemos crear un iterador Listlterator que comience apuntando a un ndice n de la lista invocando listlterator(n). He aqu un ejemplo que ilustra estas capacidades //: holding/Listlterat ion.java import typeinfo.pets.; import java.til.*; public clasa ListIteration ) public static void main(String[1 args) ( List<Pet> pets = Pets.arrayList(8 ); ListIterator<Pet> it * pets.listlterator(); while(it.hasNext()) System.out.print(it.next() M, " ? it .nextIndex(}
it.previouslndex() + "/ ")j System.out.println);

// Hacia atrs: while it.hasPrevious()) System.out .print (it .previous { \ . id(J M "),* System.out.printlnO; System.out.printlnpets)? it e pets.listlterator (3) , while(it.hasNextO) ( it.next(; it.set(Pets.randomPet (M ;

Ejercicio 8:(I)Modifique el Ejercicio I para que utilice un iterador para recorrer la lista mientras se invoca hop( ) 724 Piensa en Java } System.out.println(pets); I ) / Output: Rat, 1, 0; Manx, 2,1; Cymric, Cymric, 6 , S; Pug, 7, 6 ; Manx, 7 6 5 4 3 2 1 0 Rat, Manx, Cymric,Mutt, Pug, Cymric, Pug, Manx] Hmster, 3, 2 Mutt, 8 , 7; 4, 1; Pug, 5. 4;

[Rat, Manx, Cymric,Cymric, Rat, EgyptianMau, EgyptianMaul

///*-

El mtodo Pets.randomPeM ) se utiliza para sustituir todos los objetos Pet de la lista desde la posicin } en adelante.

Ejercicio 12: (3) Cree y rellene un objeto List<lnteger>. Cree un segundo objeto List<lnteger> del mismo tamao que

Ejercicio 8:(I)Modifique el Ejercicio I para que utilice un iterador para recorrer la lista mientras se invoca hop( ) 725 Piensa en Java el primero y utilice sendos objetos Listlterator para leer los elementos de la pnmera lista e insertarlos en la segunda en orden inverso (pruebe a explorar varias formas distintas de resolver este problema). UnkedList

Linkedl.ist tambin implementa la interfaz List bsica, como ArrayList. pero realiza ciertas operaciones de forma ms eficiente que ArrayList (la insercin y la eliminacin en la mitad de la lista). A la inversa, resulta menos eficiente para las operaciones de acceso aleatorio.

UnkcdlJst tambin incluye mtodos que permiten usar este tipo de objetos como una pila, como una cola o como una cola bidircccional

Algunos de estos mtodos son alias o ligeramente variantes de los otros, con el fin de disponer de nombres que resulten ms familiares dentro del contexto de un uso especifico (en particular en el caso de los objetos Queuef que se usan para imple- mentar colas). Por ejemplo. getFirstf ) \ element() son idnticos: devuelven la cabecera (primer elemento) de la lsta sin eliminarlo y generan la excepcin NoSuchElcmentException si la lista est vacia. peek() es una variante de estos mtodos que devuelve nuil si la lista est vaca.

renioveFirst( ) y remove( ) tambin son idnticos: eliminan y devuelven la cabecera de la lista, generando la excepcin NoSucliLlementLxception para una lista vaca: polI( ) es una variante que

Ejercicio 8:(I)Modifique el Ejercicio I para que utilice un iterador para recorrer la lista mientras se invoca hop( ) 726 Piensa en Java devuelve nuil si la lista est vaca

addFirst() inserta un elemento al principio de la lista.

offer( > es el mismo mtodo que add() y addl.ast( ). Todos ellos aaden un elemento al final de la lista. removeLast( ) elimina y devuelve el ltimo elemento de la lista.

He aqu un ejemplo que muestra las similitudes y diferencias bsicas entre todas estas funcionalidades. Este ejemplo no repite aquellos comportamientos que ya han sido ilustrados en ListFeatures.java: ' : holding/LinkedListFeatures, java import typeinfo.pets.*; import java.til.*; import static net.mindview.til.Print. * ; public class LinkedLListFeatures { public static void main(trina] args) { LmkedList<Pet> pets = new LinkedList<Pet>(Pets.arrayList( S ) ) print(pets); // Idnticos: print\ pets.getFirst(1: " + pets.getFirst(!); print("pets.element(}; " -

Ejercicio 8:(I)Modifique el Ejercicio I para que utilice un iterador para recorrer la lista mientras se invoca hop( ) 727 Piensa en Java pets.element(J); Slo difiere en el comportamiento con las listas vacas: print ("pets .peekO : " pets.peek{1 );
II

// Idnticos; elimina y devuelve el primer elemento: print"pets . remove () - " + pets. remrve (' ) ; print("pe:s.removeFirBt(); w + pets.removeFirst()); // Slo difiere en el comportamiento con las listas vacias: print ("pets.poli 0 : " pets .poli ()) ; print(pets); pets . addFirst (new P.at ()) ; print{"After addFirst 0: " * pets) pets . of f er (Pets. randomPet ()) print("After offer) : " + pets); pets.add(Pets.randomPet1) ) ; print ("After add(): " + pets); pets.adaLastInew Hmster i ) ) : print l "After addLLast O : " -* pets); print("pets.removeLast(J : " pets.removeLast()); i ) /* Output: [Rat, Manx, Cymric, Mutt. Pug] pets.getFirst(>: Rat pets.elemer.t{): Rae pets.peek): Rat pets.remove(); Rat pets.removeFirst() : Manx petspcll(J: Cymric (Mutt, Pug} After addFirstf): [Rat, Mutt, PugJ After offerO: [Rae,Mutt.Pug, Cymric) After addO : [Rat, Mutt, Pug, Cymric, Pug] After addLastO: [Rat, Mutt, Pug, Cymric, Pug, Hmster] pets.removeLastt): Hmster *///:-

Ejercicio 8:(I)Modifique el Ejercicio I para que utilice un iterador para recorrer la lista mientras se invoca hop( ) 728 Piensa en Java Hl resultado de Pets.arrayList( ) se entrega al constructor de LinkedList con el fin de rellenar la lista enlazada. Si analiza la interfaz Queue, podr ver los mtodos clement( ). offer( ). peek(), poll() y renmve( ) que han sido aadidos a LinkedList para poder disponer de la implemcntacin de una cola. Ms adelante en el capitulo se incluyen ejemplos completos del manejo de colas. Ejercicio 13: (3) En el ejemplo innerclassos/GreenhouseControiler.java, la clase Controller utiliza un objeto

Arraxi ist Cambie el cdigo para utilizar en su lugar un objeto LinkedList y emplee un iterador para recorrer el conjunto de sucesos.

Ejercicio 14: (3) Cree un objeto vacio LinkedList<lnteger>. Utilizando un iterador Listlterator, uada valores ente

ros a la lista insertndolos siempre en mitad de la misma. Stack

l na pila (.stack) se denomina en ocasiones "contenedor de tipo LIFOM (lasr-tn, first-out. el ltimo en entrares el primero en salir). El ltimo elemento que pongamos en la parte superior de la pila ser el

Ejercicio 8:(I)Modifique el Ejercicio I para que utilice un iterador para recorrer la lista mientras se invoca hop( ) 729 Piensa en Java primero que tengamos que sacar de lu misma, como si se tratara de una pila de platos en una cafetera.

LinkedList tienemtodos que implcmentan de forma directa la funcionalidad tambin podramosusar

de pila,por

lo

que

una lista enlazada pila

LinkedList en lugar de definir una clase con las caractersticas de una Sin embargo, definir una clase

a propsito permite en ocasiones clarificar las cosas: //: net/minexview/util/Stack.java // Definicin de una pila a partir de una lista enlazada, package net.mindview.util; import 3 ava.til.LinkedList; public class Stack<T> { private LinkedList<T> storage = new LinkedList<T>()j public void push(T v) { storage .addLFrst (v) ; ) public T peekO { return storage.getFirst()/ ) public T pop() ( return storage.removeFirst0/ } public boolean emptyO ( return storage. isEmpty ) ) public String toStringO ( return storage.toString(); )

Ejercicio 8:(I)Modifique el Ejercicio I para que utilice un iterador para recorrer la lista mientras se invoca hop( ) 730 Piensa en Java ) //)v-

Esto nos permite introducir el ejemplo ms simple posible de definicin de una clase mediante genricos, l a <T> que sigue al nombre de la clase le dice al compilador que se trata de un tipo parametrizado y que el parmetro de tipo (que ser sus* tituido por un tipo real cuando se utilice la clase) es T. Bsicamente, lo que estamos diciendo es: Estamos definiendo una pila Stack que almacena objetos de tipo T. La pila se implementa utilizando un objeto LinkedList. y tambin se define dicho objeto LinkedList para que almacene el tipo T Observe que puxh() (el mtodo que introduce objetos en la pila) toma un objeto de tipo T, mientra? que peek( ) y pop( ) devuelven 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.

He aqu una sencilla demostracin de esta nueva clase Stack 11 Almacenamiento de objetos 731 Si lo nico que queremos es disponer del comportamiento de pila, el mecanismo de herencia resulta inapropiado, porque generara una clase que incluira el resto de los mtodos de LinkedList (en el Captulo 17. Anlisis detallado de los cont- ncdotvs. podr ver que los diseadores de Java I.O cometieron este error al crear java.util. Stack i/ f : holdmg/StackTest .java import. net.mindview.til ; public class StackTest { public static void main(String[] args) { Stack<String> stack = new Stack<String>()/ or(Stnng s : "My dog has fleas".spliti " " ) l stack.pushis) ; while(istack.emptyO) System.out .orint (stack. poo (> + " "),*

) } / Output: fleas has dog My ///:-

Si quiere utilizar esta clase Stack en su propio cdigo, tendr que especificar completamente el paquete (o cambiar e! nombre de la clase) cuando cree una pila: en caso contrario, probablemente entre en colisin con la clase Stack del paquete java.util. Por ejemplo, si importamos java.util.* en el ejemplo anterior, deberemos usar los nombres de los paquetes para evitar las colisiones. //: holding/StackCaiiision.}ava import net.mindview.til.; public class StackCollision ( public static void mainiString] args) ( net.mindview.til.Stack<String> stack * new net.mindview.til.Stack<String>(I ; forString s : "My dog has fleas" .split ( ")) stack.push(s); while<Istack.empty())

He aqu una sencilla demostracin de esta nueva clase Stack 11 Almacenamiento de objetos 732 System,out .print (stack.pop 11 * " System.out.println(); iava.util.Stack<String> stack2 = new java .til .Stack<Strmg> 0 t Cor(String s : "My dog has fleas".split(" ")) stack2 .pushts) ; while ( stack?. .emptyl)) System, out .Drint (stack2 .Dop ()" ");

} ) / Output: ieas has dog My flean has dog My *///;-

Las dos clases Stack tienen la misma interfaz, pero no existe ninguna interfaz comn Stack en java.util. probablemente porque lu clase origina) java.util,Stack, que estaba diseada de una forma inadecuada, ya tena ocupado el nombre. Aunque java.util.Stack existe. IJnkedLlst permite obtener una clase Stack mejor, por lo que resulta preferible la tcnica basada en net.niind> iew.til.Stack

Tambin podemos controlar la seleccin de la implementacin Stack preferida** utilizando una instruccin de importacin explcita: import net.mindview.til.Stack;

He aqu una sencilla demostracin de esta nueva clase Stack 11 Almacenamiento de objetos 733 Ahora cualquier referencia a Stack luir que se seleccione la versin de net.mlndvlew.util. mientras que para seleccionar java.uril.Stack es necesario emplear una cualificacin completa.

Ejercicio 15: (4) Las pilas se utilizan u menudo para evaluar expresiones en lenguajes de programacin Utilizando

net.mindview.til.Stack, evale la siguiente expresin, donde **' significa introducir la letra siguiente en la pila* mientras que V significa extraer la parte superior de la fila e imprimirlo*'-

+U+n^-c+e4r+l+a-+i-^n+t^y-+r**u"H+e+s Set

Los objetos de tipo Set (conjuntos) no permiten 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 mas comn de Set consiste en comprobar la pene, nencia. para poder preguntar de una manera sencilla si un determinado objeto se encuentra dentro de un conjunto. Debido a esto, la operacin ms importante de un conjunto suele ser la de bsqueda, asi que resulta habitual seleccionar la imple- mentacin HashSet. que esta optimizada para realizar bsquedas rpidamente

He aqu una sencilla demostracin de esta nueva clase Stack 11 Almacenamiento de objetos 734 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 Collection. salvo porque tiene un comportamiento distinto (ste es un ejemplo ideal del uso de los mecanismos de herencia y de polimorfismo permiten expresar diferentes comportamientos). Un objeto Set determina la pertenencia basndose en el "valor de un objeto, lo cual constituye un terau relativamente complejo del que hablaremos en el Capitulo 17. Anlisis detallado de los contenedores

He aqu un ejemplo que utiliza un conjunto HashSet con objetos Integer: //: holdlng/SetOflnteger.java import java.Util. * ; public class SetOfInteger ( public static void mainStrlng[] aros) {

Randam rand = new Random(47); Set<Integer> intset = new HashSet<Integer>t); forint i * 0 ; i < 10000 ; i++) intset.add(rana.nextlnt(30U ; l System.out.println<intset

J / * Output: tl5, B, 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. 0]

*///:-

He aqu una sencilla demostracin de esta nueva clase Stack 11 Almacenamiento de objetos 735 En el ejemplo, se aaden diez mil nmeros aleatorios entre 0 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 especifico. Esto se debe a que HashSet utiliza el mecanismo de hash para acelerar las operaciones; este mecanismo se analiza en detalle en el Capitulo 17. Anlisis detallado de los contenedores. El orden mantenido por un conjunto HashSel es diferente del que se mantiene en un TreeSet o en un LinkedHashSei ya que cada implementacin almacena los elementos de forma distintu. TreeSet mantiene los elementos ordenados en una estructura de datos de upo de rbol rojo-negro, mientras que HashSet utiliza una funcin de hash. LinkedHashSet tambin emplea una funcin hash para acelerar las bsquedas, pero fxirece mantener los elementos en orden de insercin utilizando una lista enlazada.

Si queremos que los resultados estn ordenados, una posible tcnica consiste en utilizar un conjunto TreeSet en lugar de HashSet: // : holding/SortedSetOfInteger.java import java.til.*; public class SortedSetOfInteger ( public static void main{String(] aras? { Random rand = new Random(47) ; SortedSetInteger intset = new TreeSet<Integer>()/ forint i * 0 ; i < 10000 ; i*+) intset.add(rand.nextlnt130)) ; System.out .prmtln intset) ;

He aqu una sencilla demostracin de esta nueva clase Stack 11 Almacenamiento de objetos 736 } /* Output: {0, 1, 2, 3, 4, 5, 6 , 7, 8 , 9, 10, II. 12, 13, 14, 15. 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 20, 29]

*///-

y na de lfls operaciones ms comunes que tendremos que realizar es comprobar la pertenencia al conjunto de un determinado miembro usando contains* ). pero hay otras operaciones que nos recuerdan a los diagramas Venn que ensean en el colegio:
holding/SetOperations.jav a import java.util.;

import static net.mindview.util.Print.*; public class SetOperations { public static void main (String[] aras) ( Set<String> setl = new HashSet<String> ( '> ; Collections.addAll(setl, " A B C D E F G H I J K L" . spilt ( " )); setl.add("M); print IMH: " r setl.contains IMH" ) ) ; print(HN: t setl.contains); Set<String> set2 = new HashSet<String>I) ; Collections .addAll I set 2, "H I J K L" . split [" ")),* printl"set2 in setl: " -* setl.containsAll(set2)); setl.remove(nHM); print("setl: " + setl); print(Mset2 in seel: M setl.containsAll(set2)); setl.removeAli(set2);

He aqu una sencilla demostracin de esta nueva clase Stack 11 Almacenamiento de objetos 737 pnnt("set2 removed from setl: " + setl); Collections.addAll(setl, "X Y 2".split(H M)>; print(H,X Y Z' added to setl: w setl);

} ) / Output: H: true N: false set2 in setl: true setl: [D, K, C. B. L, G, I. M, A, F, J, E] [D, C, B, G, M, A, F, E] set2 in setl: false set2 removed from setl: X Y Z added to setl: [ Z . D, C, B, G, M, A, F, Y, X. E]

*///:-

Los nombres de los mtodos resultan bastante descriptivos, y existen unos cuantos mtodos adicionales que podr encontrar en el JDK.

He aqu una sencilla demostracin de esta nueva clase Stack 11 Almacenamiento de objetos 738 Generar una lista de elementos diferentes puede resultar muy til en determinadas ocasiones. Por ejemplo, suponga que quisiera enumerar todas las palabras contenidas en el archivo SetOperations.java anterior. Con la utilidad neLmindview. Text File que presentaremos ms adelante en el libro, podremos abrir un archivo y almacenar su contenido en un objeto Set: //: holding/UniqueWords.j ava import java.util.*; import net.mindview.ut i1 .* public class UniqueWords ( public static void main(String] args) { Set<String> words new TreeSetString>( new TextFile I"SetOperations.java", ,'\\W+M)); System.out.println(words);

) ) / Output: [A, B, C. Collections, D, E. F, G# H, HashSet, I, J, K, L, M, N, Output, Print, Set, SetOperations, String, X, Y, Z, add, addAll, added, args, class, contains, containsAll, false, from, holding, import, in, java, main, mindview, net, new, print, public, remove, removeAli, removed, setl, set2, split, static, to, true, util, void!

///:^

He aqu una sencilla demostracin de esta nueva clase Stack 11 Almacenamiento de objetos 739 TextFile hereda de List<Strlng>. El constructor de TextFUe 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 Capitulo 13 Cadenas de caracteres). El resultado se entrega al constructor de TreeSet que aade el contenido del objeto List al conjun. lo. Puesto que se trata de un objeto TreeSet. el resultado est ordenado. En este caso, la reordenacin se realiza lexicogr. pamente de modo que las letras maysculas y minsculas se encuentran en grupos separados. Si desea realizar una ordenacin alfabtica, puede pasai el comparador String.CASE_lYSENSITIVE_ORDR (uji comparador es un objeto que establece un orden) al constructor TreeSet //: holding/niqueWordsAlphabetic.j ava // Generacin de un listado alfabtico, import j ava.uti 1 .; import net.mindview.til.*; public class UniqueWordsAlphabetic ( public static void mainString] args) ( Set<String> words * new TreeSet<String> (String. CASE_INSENSITIVE_ORDER> ,* words.addAll new TextFile("SetOperations.java", "\\W*M)); ) System.out.println{words),*

) / Output: [A. add, addAll, added, args, B, C, class, Collections, contalns, containsAll, D. E, F, false, from, G, H, HashSet, holding, I, import, in, J, java, K, L, M, mam, mindview, N, net, new, Output, Print, public, remove, removeAll, removed. Set, setl, set2, SetOperations, split, static, String, to, true, til, void, X, Y, 2]

*///:-

Los comparadores se analizaran en el Capitulo 16. Matrices

He aqu una sencilla demostracin de esta nueva clase Stack 11 Almacenamiento de objetos 740 Ejercicio 16: (5) C ree un objeto Sel con todas las vocales. Utilizando el archivo l ique Words. ja va. cuente y muestre

el nmero de vocales en cada palabra de entrada, y muestre tambin el nmero total de vocales en el archivo de entrada. Map

La posibilidad de establecer correspondencias entre unos objetos y otros puede ser enormemente potente a la hora de resolver cienos problemas de programacin. Por ejemplo, consideremos un programa que permite examinar la alcatoricdad de la clase Random Idealmente. Random debera producir la distribucin perfectamente aleatoria de nmeros, pero para comprobar si esto es asi debera generar numerosos nmeros aleatorios y llevar la cuenta de cules caen dentro de cada uno de los rangos definidos. Un objeto Map nos permite resolver fcilmente el problema: en este caso, la clase sera el nmero generado por Random y el valor ser el nmero de veccs que esc nmero ha aparecido: //; holding/Statiatics.java // Ejemplo simpie de HashMap. import java.util.*; public class Statistics { public static void mainlStringU args) ( Random rand * new P.andom(47 ); Mapdnteger, Integer> m = new HashMap<Integer.Integer>O; fordnt 1 *0 ; 1 < 10000 ,* i++) f // Generar un numero entre 0 y 20: me r - rand .nextInt (20 ) ; Integer freq = m.get(r) ; m.putlr, freq * nuil ? 1 : freq 1 );

He aqu una sencilla demostracin de esta nueva clase Stack 11 Almacenamiento de objetos 741 )

i 11 Almacenamiento de objetos 742 System.out.println<m);) / Output: {15*497. 4=481, 19=464, 8=468. 11=531, 16=533, 18=478. 3=503, 7471. 12=521. 17=509, 489, 0=48l)
2-

13*506.

9=549,

6=519,

1=502,

14=477,

10=513,

5503,

///:-

pn main( ). la caracterstica de autoboxing convierte el valor int aleatoriamente generado en una referencia a Integer que puede utilizarse con el mapa llasliMap (no pueden utilizarse primitivas en los contenedores) El mtodo gel( ) devuelve nuil 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 get( ) devuelve el valor Integer asociado con esa clave, el cual tendremos que incrementar (de nuevo, la caracterstica de aurohoxing simplifica la expresin, pero en la prctica se llevan a cabo las necesarias conversiones hacia y desde Integer)

He aqu un ejemplo que nos permite utilizar la descripcin de Siring para buscar objetos Pet Tambin nos muestra cmo podemos comprobar si un determinado objeto Map contiene una clave o un valor utilizando los mtodos containsKey( ) y containsN alue( ): //: holding/PetMap.j ava import type info .pets. * import java.util.; import stat ic net .mir.dview. util. Print. ; public class PetMap (

i 11 Almacenamiento de objetos 743 public static void main(String[] args) { Map<Stnng, Pet> petMap = new HashMap<String.Pet>O; petHap.put I"My Cat", new Cat("Molly")); petMap.put<"My Dog", new Dog"Ginger")); petMap .put ( u My Hamster'*, new Hamster (Boscc'M ); print(petMap); Pet dog = petMap.get("My Dog"); print(dog); print (petMap. contamsKey f "My Dog*') ) ; print(petMap.containsValue(dog));

) } / Output: {My Cac=Cat Molly, My Hamster=Hamster Bosco, My Dog-Dog Ginger} Dog Ginger true true ///;-

Los mapas, al igual que las matrices y las colecciones, pueden expandirse fcilmente para que sean multidimensionales; basta con definir un objeto Map cuyos valores .sean tambin mapas (y los valores de esos otros mapas pueden ser. a su vez. otros contenedores o incluso otros mapas). As, resulta bastante fcil combinar los contenedores para generar estructuras de datos muy potentes. Por ejemplo, suponga que queremos llevar una lista de personas que tienen mltiples mascotas, en ese caso, lo nico que necesitaremos es un objeto Map<Person. Llsl<Pel: holdi ng/MapOfList.j ava package holding; import typeinfo.pets.; import java.util.; import static net .mmdview,util .Print. *; public class MapOfList (

i 11 Almacenamiento de objetos 744 public static Map<Person. List? extends Pet>> petPeople * new HashMap<Person, List<? extends Pet>>(); 3tatic { petPeople.put(new Person("Dawn"), Arrays.asList(new Cymric( "Molly"I,new Mutt I"Spot"))); petPeople.put(new Person( "Kate"). Arrays. asList (new Cat ("Shackleton"), new Cat("Elsie May"), new Dog("Margrett"))); petPeople.put(new Person!"Marilyn"), Arrays.asList( new PugC'Louie aka Louis Snorkelstein Dupree"), new Cat("Stanford aka Stinky el Negro"), new Cat(MPinkola"))),* pet People, put; (new Person ("Luke) . Arrays.asList(new Rat("Fuzzy), new Rat("Fizzy"))); petPeople.put(new Person("IsaacH), Arrays.asList(new Rat("Freckly")));

) public static void main (String [] args) { print("People: " -* petPeople.keyset 0 ) ; print ("Pets: " pet People .values ()); for(Person person : petPeople.keyset()) ( print (person *- * has:"); for(Pet pet : petPeople.get(person)) print(" " -f pet);

i 11 Almacenamiento de objetos 745 i } / * Output: People: fPerson 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 Pinkola], (Rat Freckly], [Cymric Molly, Mutt Spot], (Cat Shackleton, Cat Elsie May, Dog 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 Pexaoii Kale litxa; Cat Shackleton Cat Elsie May Dog Margrett *///:-

Un mapa puede devolver un objeto Sel con sus claves, un objeto Collection con sus valores o un objeto Sel con las parejas clave-valor que tiene almacenadas. El mtodo kevSet() genera un conjunto de todas las claves de pel People, que se utiliza en la instruccin foreach para iterar a travs del mapa.

Ejercicio 17: (2) Tome la clase Gcrbll del Ejercicio I y cambie el ejemplo para incluirla en su lugar en un objeto Map

i 11 Almacenamiento de objetos 746 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 tabla. Genere un iterador para el conjunto de claves keySet() y utilcelo para desplazarse a lo largo del mapa, buscando el objeto Gerbil correspondiente a cada clave, imprimiendo la clave c 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 LinkedHashMup. Demuestre que se mantiene el orden de insercin.

Ejercicio 19: (2) Repita el ejercicio anterior con sendos conjuntos HashSet y LinkedHashSet.

Ejercicio 20: (3)ModifiqueelEjercicio16parallevar vocal

lacuenta decuntasveces haaparecido

cada

i 11 Almacenamiento de objetos 747 Ejercicio 21: (3) Utilizando Map<String.lnteger>, siga el ejemplo de l niqueWords.java para crear un programa que

lleve la cuenta del numero de apariciones de cada palabra en un archivo. Ordene los resultados utilizando CoIlectons.sort() proporcionando como segundo argumento String.CASE_INSENSITIVE_ORDF.R (para obtener una ordenacin alfabtica), y muestre los resultados.

Ejercicio 22: (5) Modifique el ejercicio anterior para que utilice una clase que contenga un campo de tipo String y un

campo contador para almacenar cada una de las diferentes palabras, as como un conjunto Set de estos objetos con el fin de mantener la lista de palabras.

Ejercicio 23: (4) Partiendo de Statistlcs.ja\a. cree un programa que ejecute la prueba repetidamente y compruebe si hay

i 11 Almacenamiento de objetos 748 algn nmero que tienda a aparecer ms que los otros en los resultados.

Ejercicio 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. Ejercicio 25: (3) Cree un objeto Map<String,ArrayList<lnteger. Utilice net.mindview. TextFile para abrir un

archivo de texto y lea el archivo de palabra en palabra (utilice "\\W+" como segundo argumento del constructor TextFile). Cuente las palabras a medida que lee el archivo, y para cada palabra de un archivo, anote en la matriz. ArrayList<Integer> el contador asociado con dicha palabra; esto es, en la prctica, la ubicacin dentro del archivo en la que encontr dicha palabra

Ejercicio 26: (4) Tome el mapa resultante del ejercicio anterior y ordene de nuevo las palabras, tal como aparecan en

i 11 Almacenamiento de objetos 749 el archivo original. Queue

Una cnl (queue) es normalmente un contenedor de tipo FIFO ifirst-in, first-out, el primero en entrar es el primero er salir). 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, como veremos en el Capitulo 21. Concurrencia, porque permiten transferir objetos con seguridad de una a otra tarea.

LinkedList dispone de mtodos para soportar el comportamiento de una cola e implemcnta la interfaz Queue. por lo que un objeto LinkedList puede utilizarse como implementacin de Queue. Generalizando un objeto LinkedList a Queue. este ejemplo utiliza los mtodos especficos de gestin de colas de la interfaz Queue: //: holding/QueueDemo.java // Generalizacin de un objeto LinkedList a un objeto Queue. import java.util.*; public class QueueDemo ( public static void printQ{Queue queue) { while (queue.peekO ! = nuil)

System.out.print(queue.remove() System.out.println );

"

"

)j

i 11 Almacenamiento de objetos 750 }

public static void main (String [] args) { Queue<lnteger> queue * new LmkedList<Integer> () ; Random rand new Random(47) ; for(int i * 0 ; i < 10 ; i-f-f) queue.offer(rand.nextInt(i + 10 )); printQ(queue); Queue<Character qc = r.ew LinkedList<Character> () ; for(char c : "Brontosaurus".tocharArrayo) qc.offer c); printQ(qc);

} } /* Output: 8 1 1 1 5 1 4 3 1 0 1 B r o n t o s a u r u s

offer( ) es uno de los mtodos especficos de Queue: este mtodo insena un elemento al final de la cola, siempre que sea posible, o bien devuelve el valor false. Tanto peek( > como e!ement() devuelven la cabecera de la cola sin eliminarla, pero peek( ) devuelve nuil si la cola esta vacia y elemente) genera NoSuchElcmcntException Tanto poU() como removc() eliminan y devuelven la cabecera de la cola, pero poll( ) devuelve nuil si la cola est vaca, mientras que remove() genera NoSuch'lementF.xception

i 11 Almacenamiento de objetos 751 La caracterstica de aittoboxing convierte automticamente el resultado int de ne\tlnt( ) en el objeto Integer requerido por queue. v el valor char c en el objeto Charactcr requerido por qc La interfaz Queue limita el acceso a los mtodos de LinkedList de modo que slo estn disponibles los mtodos apropiados, con lo que estaremos menos tentados de utilizar los mtodos de LinkedList (aqu, podramos proyectar de nuevo queue para obtener un objeto LinkedList. pero al menos nos resultar bastante ms complicado utilizar esos mtodos).

Observe que los mtodos especficos de Queue proporcionan una funcionalidad completa y autnoma. F.s decir, podemos disponer de una cola utilizable sin ninguno de los mtodos que se encuentran en Collection. que es de donde se lia heredado

Ejercicio 27: (2) Escrba una clase denominada Command que contenga un objeto String y que tenga un mtodo ope-

ration( ) que imprima la cadena de caracteres. Escrba 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 meto- do de una tercera clase que consuma los objetos de la cola e invoque sus mtodos opera!ion( ). PriorityQueue

El mecanismo FIFO (First-in, first-out) describe la disciplina de gestin de colas ms comn. La

i 11 Almacenamiento de objetos 752 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 disciplina FIFO dice que el siguiente elemento es aquel que haya estado esperando durante mas tiempo.

Por el contraro, una cola con 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 >er atendido inmediatamente si su avin est a punto de salir. Si construimos un sistema de mensajera, algunos mensajes sern mas importantes que otros y sera necesario tratar esos mensajes antes, independientemente de cundo hayan llegado. El contenedor PriorityQueue ha sido aadido en Java SF.5 para proporcionar una implementacn automtica de este tipo de comportamiento.

Cuando ofrecemos, como mtodo offcr( ). un objeto a una cola de tipo PriorityQueue. dicho objeto se ordena dentro de l.i cola-' El mecanismo de ordenacin predeterminado utiliza el orden natural de los objetos de la cola* pero podemos modificar dicho elemento proporcionando nuestro propio objeto Comparator La clase PriorityQueue garant za que cuando se invoquen los mtodos peek( ). poll( ) o reniove( ). el elemento que se obtenga ser aqul que tenga la prioridad mas .ilt.i

Kesulta trivial implementar una cola de tipo PriorityQueue que funcione con tipos predefinidos como Integer, Strinj* < Charactcr En el siguiente ejemplo, el primer conjunto de valores son los valores aleatorios del ejemplo anterior, con I' cual podemos ver cmo se los extrae de manera diferente de la cola PriorityQueue //: holding/PriorityOueueDemo . java import j avaUtil.*; public elase PriorityQueueDemo | public static void mam(String[] args) {

i 11 Almacenamiento de objetos 753 PnorityQueue< Integer > priorityQueue = new PriorityQueue<Integer>O; Random r^nd ^ new Randomi47)/ " F.Iu depende, tic hecho. de ht implemcnwcin. Lo.% algoritmo de colas con prioriiiad huelen realizar Iti ordenacin durante la insercin (monten tcnilv unn estructuro de memoria que *e conoce con el nombre de cmulo), pero lumbien puede perfectamente seleccionarse et elemento nutt. importante cu * roomenlo de la extraccin La eleccin del algoritmo podra tener MI importancia si la pnondad le IH objeto* puede modificarse mientras estos estn C>|H raudo en tu cola. for <int i * 0 ; i < 10: priorityQueue.offeri rand.nextlnt(i * 20 ) i ; QueueDemo.printQi priorityQueuel; List<Integer> ints * Arrays.asList(25. 22, 20, 18, 14, 9, 3, 1, 1, 2. 3, 9, 141 18, 21, 23, 25); priorityQueue = r.ew PriorityQueueInteger> ints); QueueDemo.prmrQpriorityQueue) priorityQueue = new PriorityQueueIntegeri ints.sise O, Collections.reverseOrder( ) ) ; priorityQueue.addAH i ints) ; QueueDemo.printQ(priorityQueuei i String act = "EDDCATI0N SHOULD ESCHEW OBFUSCATION* ; List<String strings Arrays.asList (fact.split ("") J ; PriorityQueueString stringPQ ^ new PriorityQueue<Strmg> (strings) ; QueueDemo.printQ(stringPQ); stringPQ * new PriorityQueue<String>t strings.sisei), Collections.reverseOrder()) ; stringPQ.addAll(stringsI ; QueueDemo.printQ{stringPQ); Set<Character charSet new HanhSetCharacter>(); forlchar c : fact.toCharArrayi)i charSet.add(el; // Autoboxing PriorityQueue<Character> characterPQ = new PriorityQueuecCharacter><charSet); QueueDemo. DrmtQ( characterPQ) ;

i 11 Almacenamiento de objetos 754 ) ) / Output:


0 1

1 1 1 1 1

3 5 6 14 21 22 23 9 3 3 25 25 2 1 1

1 2 3 3 99 14 14 18 18 20

25 25 23 2221 20 18 18 14 14 9

A A B C C C D D E E E F H H I I L N N O O O O S S S T T U U U W W U T T S S S O O O O N N L I 1 H H F E E E D D C C C B A A A B C D E F H X L H 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 valores y tienen una prioridad superior a la de las letras) Para ver cmo podemos verificar la ordenacin proporcionando nuestro propio objeto comparador, la tercera llamada al constructor de ProritvQueue<lntcger> y la segunda llamada a Priorit>Queue<Strinu> utilizan el comparador de orden inverso generado por Collections.reverseOrdcr( ) (aadido en Java SE5),

La ltima seccin aade un conjunto llashSet para eliminar los objeios Charaeter duplicados, simplemenie con el fin de hacer las cosas un poco ms interesantes

Integer. String y Characler funcionan con PriorityQueue 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 generar una ordenacin natural, o bien proporcionar nuestro objeto Comparator. En el Capitulo 17. Anlisis detallado de los contenedores se proporciona un ejemplo ms sofisticado en el que se ilustra este mecanismo.

i 11 Almacenamiento de objetos 755 Ejercicio 28: (2) Rellene la cola PriorityQueue (utilizando offer( )) con valores de tipo Douhle creados utilizando

java.mil.Random. y luego elimine los elementos con poII() y visualcelos

Ejercicio 29: (2) Cree una clase simple que herede de Object y que no contenga ningn nombre y demuestre que se

pueden aadir mltiples elementos de dicha clase a una cola PriorityQueue Este tema se explicar en detalle en el Capitulo 17. Anlisis detallado de tos contenedores. Comparacin entre Collection e Iterator

( olleciion es Id interfaz raz que describe las cosas que tienen 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 lus otras interfaces. Adems, la clase java.util.AbstractCollection proporciona una implementacion predeterminada de Collection. para poder crear un nuevo subtipo de AbstractCollection sin duplicar innecesariamente el cdigo

i 11 Almacenamiento de objetos 756 Un argumento en fav or de disponer de una interfaz es que sta nos permite crear cdigo genrico. Escribiendo como una interfaz en lugar de como una implementacion. nuestro cdigo puede aplicarse a ms tipos de objetos.6 Por tanto, si escribimos un mtodo que admita un mtodo Collection. dicho mtodo podr aplicarse a cualquier tipo que implemcnte Collection. y esto permite implementar Collection en cualquier clase nueva para poderla usar con el mtodo que hayamos escrito. Resulta interesante resaltar, sin embargo, que la biblioteca estndar 0*-+ no dispone de ninguna clase base comn para sus contenedores: los aspectos comunes entre los contenedores se coasiguen utilizando iteradores. En Java, podra parecer adecuado seguir la tcnica utilizada en C++ y expresar los aspectos comunes de los contenedores utilizando un iterador en lugar de una coleccin. Sin embargo, ambos enfoques estn entrelazados, ya que implementar Collection tambin implica que deberemos proporcionar un mtodo iterator(): f /: holding/InterfaceVsIterator.jav a import typeinfo.pets. import java.til.*; public class InterfaceVsIterator { public static void display(Iterator<Pet> it) ( while(it.hasNext(}J { Pet p = it. next {) ; System.out.pnnt (p.idt) H: " + p " w);

) System.out.println():

) public static void display(Collection<Pet> pete) { fortPet p : pets) System.out .print (p. id() + System. out. prmtln () ; p + " n) ;

i 11 Almacenamiento de objetos 757 ) public static void main(String[] args) ( List<Pet> petList = Pets.arrayList(8 ); Set<Pet> petSet = new HashSet<-Pet> (petList) ,* Map<String.Pet> petMap = new LinkedHashMap<String,Pet>()/ StringH ames = ("Ralph, Eric, Robin, Lacey, " -* "Britney, Sam, Spot, Fluffy">.split(", forint i = 0 ; i < ames.length; i+t) petMap.put(ames tiJ , petList.get(i)); display(petList); display (petSet) display(petList.iterator()); display (petSet. iterator O ) ,* System.out.printlnlpetMap); System.out.println(petMap.keySet()) ;

display(petMap.vales() I ; display(petMap.vales().iterator());

) } /* Output: 0:Rat 1:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6 :Pug 7:Manx 4:Pug :Pug 3:Mutt l:Manx 5:Cymric 7:Manx 2:Cymric OrRat * Alguna?. persona* defienden IM creacin automtica de una interfaz para toda posible combinacin de mtodos en una cla.se; en ocasiones, defienden que >e haga calo pan odas las clases. En mi opinin, una interfaz debera tener un significado mayor que unu mera duplicacin mecnica de combinaciones de mtodos, por lo que suelo preferir esperar y ver que valor aadira una inierfa/ antes

i 11 Almacenamiento de objetos 758 de crearla o Rat IsManx 4 : Pug 2:Cymric 0:P.at 2:Cymric3:Mutt 4: Pug5:Cymric 6 : Pug 7:Manx 6 : Pug 3:Mutt 1 :Manx 5:Cymric 7:Manx

(fialph=Rat. Eric=Manx, Robin=Cymric, Lacey=Mutt# Britney=Pug, Sam=Cymric, Spot=Pug. Fluffy=Manx) [Ralph, Eric, Robn, Lacey, Britney, Sam, Spot. Fluffy] Q:Hat 0:Rat I:Manx 2:Cymric3:Mutt liManx 2:Cymric3:Mutt 4:Pug5:Cyroric 6 :Pug 4:Pug5:Cymric 6 :Pug 7:Manx 7:Manx

*///-*-

Ambas versiones de display( ) funcionan con objetos Map y con subtipos de Collcction. y tanto la interfaz Collection como Itcrator penmten desacoplar los mtodos displuy(). 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. Collectinn lleva el concepto un paso ms all porque es de tipo Iterahli' y por tanto, en la implementacin de dsp!ay(Cu!li*ctiun) podemos usar la estructura /oreadt. lo que hace que el cdigo sea algo tn;> limpio

i 11 Almacenamiento de objetos 759 El uso de Iterator resulta muy recomendable cuando se implementa una clase externa, es decir, una que no sea de tipo Collcction. ya que en ese caso resultara difcil o incomodo hacer que esa clase iinplementara la interfaz Collection Por ejemplo, si creamos una implementacin de Collcction heredando de una clase que almacene objetos Pet. deberemos imple* nientar iodos 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 tmplemeritar iterator( ) de todas formas, junto con sizet ). para proporcionar los mtodos que no estn implementados en AbstractCollection. pero son Utilizados por los otros mtodos de AbstractCollection: //: holding/CollectionSequence .java .mporr typeinf.pets.*; import ]a va.uLil.*; public class ColleerionSequence extend AbstiaetColiection<-Pet> { private Pee ti pets = Pets.createArray(8 ); publlc lnt slzeP { retum pets.lenath; ) puble lterator<;Pet> iterator() { retum new Iterator<Pet> () { private int ndex * 0 ; pufclic boolean hasNext) { return ndex ? pets.length; i public Pet nextO { return pets Undex-t-^ J ; } public void removed ( // Ko impiementado throw new UnsupportedOperationException);

) b

} public static void main(String[] axgs) {

i 11 Almacenamiento de objetos 760 CollectionSequence c = new CollectionSequenceO; InterfaceVsrterator.display(c); InterfaeeVsIterator.display(c.iterator)I;

) ) /* Output: 0:Rat 1:Manx 2: Cymrlc 3:Mutt 4:Pug 5:Cyraric 6 :Pug 7:Manx 0:Rat 1:Manx 2:CymriC 3:Mutt 4:Pug 5:Cymric 6 :Pug 7:Manx ' / / / : -

lil mtodo remove( ) es una operacin opcional", de la que hablaremos mas delante en el Capitulo 17. Anlisis detallado de los awtenedores Aqu, no es necesario implementarlo y, si lo invocamos, generar una excepcin.

En este ejemplo, podemos ver que si implementamos Collcction. tambin implemcntamos iterator( ). adems, vemos que implementar nicamente lterator( ) slo requiere un esfuerzo ligeramente menor que heredar de AbstractCollection Sin embargo, -.i nuestra clase ya hereda de otra clase, no podemos heredar de AbstractCollection. F.n tal caso, para implemen- tur Collection seria necesario implementar todos los mtodos de la interfaz. En este caso, resultara mucho ms sencillo heredar y aadir la posibilidad de crear un llorador: //: holding/NonCollectionSequence.java import typeinfo.pets.*; import java.til.*; class PetSequence { protected Petf] oets * Pets.createArray18);

i 11 Almacenamiento de objetos 761 i public class NonCollectior.Sequence extends PetSequence ( public Iterator<Pet> iterator0 ( return new Iterator<Pet>O { orivate lnt Index = 0 ? public boolear. hasNext() { return index < pets.length; i public Pet nextII ( return petslindexn-] ) public void removed ( // No impismentado throw new nsuoportedOperationExceptionO ;

) Yi

) public static void mam (St ring [] args) ( NonCollectionSequence nc new NonCollectionSequence(); InterfaceVsIterator.display(nc.iteratorO ); 1 ) /* Output: 0:Rat l:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6 :?ug 7:Manx ///:-

Generar un objeto Iterator es la forma con acoplamiento ms dbil para conectar una secuencia con un mtodo que consuma esa secuencia; adems, se imponen muchas menos restricciones a la clase correspondiente a la secuencia que si imple- mentumos Collection

i 11 Almacenamiento de objetos 762 Ejercicio 30: (5)ModifiqueCnllectionSequence.javaparaque AbstractCollection. sino que implemcnte Collection. La estructura foreach y los iteradores no herede de

Hasta ahora, hemos utilizado principalmente la sintaxis foreach con las matrices, pero dicha sintaxis tambin funciona con cualquier objeto de tipo Collection I lemos visto algunos ejemplos en los que empleaba ArrayList, pero he aqu una demostracin general: //: holding/ForEachCollections .java // All collections work with foreach. import java.til.*; public class ForEachCollections ( public static void mairuStringU args) { Collection<String> es = new LinkedList<String>O ; Collections.addAll(es, "Take the long way hotne".split(" ")); for(String s : es) i System, out .print (H * " s + 1,1 "i;

) /* Output: 'Take' 'the' long* way' 'home*

11 Almacenamiento de objetos 763 *///:-Como es es tina coleccin, este cdigo demuestra que todos los objetos Collcction permiten emplear la estructura /breach.

La razn de que este mtodo funcione es que en Java SE5 se lia introducido una nueva interfaz denominada Itcrahlc que contiene un mtodo iterator^ ) para generar un objeto Iterator, y la interfaz Itcrahlc es lo que la estructura foreach utiliza para desplazarse a travs de una secuencia. Por tanto, si creamos cualquier clase que mplemente Itcrahlc. dicha clase podr ser utilizada en una instruccin foreach: //: holding/IterableClass.java ft Anything Iterable works with foreach. import java.util.*; public class IterableClass implements Iterable<Strina> { protected String fl words * '"And that is how * Mwe know the Earth to be shapedsplit{" "); public Iterator<String> iterator!i Iterator<String>u ( private 0 ; public boolean hasNext1 1 index < words . length,* banana( return new int index = { return

public String next!) { retum words [ index++1 ; ) public void remove 0 { // No implementado throw new UnsuoportedOperat ionException (1 ,*

} I;

11 Almacenamiento de objetos 764 public static void main(String(] args) ( for(Stnng s r new IterableClass()I System.out .print (s " *) ;

) } /* Output! And that is how we know the Earth to be banana-shaped.

>//:-

El mtodo lterator( ) devuelve una instancia de una implementacin interna annima de Iterator<String> que devuelve cada palabra de la matriz En main( ). podemos ver que IterableClass funciona perfectamente en una instruccin foreach.

En Java SE5. hay varias clases que se han definido como Itcrahlc. principalmente todas las clases de tipo Collection (pero no las de upo Map). Por ejemplo, este codigo muestra todas las variables del entorno del sistema operativo: 7: holding/EnvironmentVariables.java import java.til.*; public class EnvironmentVariables { public static void main(String(] args' {

11 Almacenamiento de objetos 765 for(Map.Entry entry: System.getenv().entrySet(): { System, out. print In (entry, get Key 0 * "2 N entry.getValue()l ;

>

} ) / Execute to see output) ///:-

Systcm.getenv( ) devuelve un objeto Map. cntrySet( ) produce un conjunto de elementos Map.Entry v un conjunto (Set) de tipo Itcrahlc. por lo que se le puede usar en un bucle foreoch.

La instruccin foreach funciona con una matriz o cualquier cosa de tipo Itcrahlc, pero esto no quiere decir que una matriz sea automticamente de tipo Itcrable. m tampoco que se produzca ningn tipo de mecanismo de auroboxing: E*to no taba disponible units tie Java SE5. porque *e pensaba que tffttafa acoplado de una maneta demasiado entecha con el sistema operativo, lo que violaba la regla de "escribir k programa una ve/ y ejecutar 1 us en cualquier lugar" f-.l hecho de que se haya incluido ahora mugiere que los discadore. de Java h.tn decidido ver m pragmtico*. //: holdmg/ArraylsNotIterable. java import j ava * ut i1.;

11 Almacenamiento de objetos 766 public class ArraylsNotlterable { static <T> void test(Iterable<T> ib) { forlT t : ib) System.out.print(t + " ");

) public static void main (String [] args) { test(Arrays.asList(1, 2, 3)); String[] strings = { -A, // Una matriz Iterable: funciona CM )f con foreach, pero no es de tipo

//i testistrings); // Hay que convertirla explcitamente al tipo Iterable: test(Arrays.asList(strings));

) } / Output: 1 2 3 A 5 C *///:-

Al tratar de pasar una matriz como argumento de tipo Iterable el programa falla. No hay ningn tipo de conversin automtica a Iterable; sino que debe realizarse de forma manual. Ejercido31: (3) Modifique polymorphism/shape/RandoniShapeCenerator.java para hacerlo de tipo Iterable.

11 Almacenamiento de objetos 767 Tendr que aadir un constructor que admita el numero de elementos que queremos que el iterador genere antes de pararse. Verifique que el programa funciona. El mtodo basado en adaptadores

Qu sucede si tenemos una clase existente que sea de tipo Iterable. y queremos aadir una o ms formas nuevas de utilizar esta clase en una instruccin foreach1} Por ejemplo, suponga que queremos poder decidir si hay que iterar a travs de una lista de palabra* 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 utilizar lo que yo denomino Mtodo basado en adaptadores. Cuando se dispone de una interfaz y nos hace falta otra, podemos resolver el problema escribiendo un adaptador. Aqu, lo que queremos es aadir la capacidad de generar un iterador inverso, pero sin perder el iterador directo predeterminado, as que no podemos limitamos a sustituir el mtodo, lin 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 permite proporcionar mltiples formas de usar foreach'. //: holding/AdapterMethodldiom.java // El mtodo basado en adaptadores permite utilizar // foreach con tipos adicionales de objetos itarables. import java.til.*; class P.eversibleArrayList<T> extends ArrayList<T> { public ReversibleArrayList(Collection<T> c) { super(c); ) public Iterable<T> reversedO { return new Iterable<T>() ( public Iterator<T> iterator() { return new Iterator<T>O ( int current = sizeO - 1; public boolean hasNextO ( return current > -1; ) public T nextO { return getcurrent); ) public void removeO ( // No implementado throw new

11 Almacenamiento de objetos 768 UnsupportedOperationException();

}>

) public class AdapterMethodldiom { public static void main(String[] args) ( ReversibleArrayList<String> ral = new ReversibleArrayList<String> ( Arrays .asList I "To be or not to be*. split (" **))); // Toma el iterador normal va iterator(l3 for(String s : ral) System.out.print(s " "); System. out. print In {) f* // Entregar el objeto Iterable de nuestra eleccin for(String s : ral.reversed()) System.out.print(s " H);

11 Almacenamiento de objetos 769 ) } /* Output: Te be or not to be be to not or be To

*///:-

ii nos

limitamos a poner el objeto ral dentro de la instruccin foreach. obtenemos el iterador directo (predeterminado). Pero i invocamos reversed( ) sobre el objeto, el comportamiento ser distinto.

Jtilizando esta tcnica, podemos aadir dos mtodos adaptadores al ejemplo ltcrablcClass.java: //: holding/MultilterableClass.jav a // Adicin de varios mtodos adaptadores, import java.util.*; public class MultilterableClass extends IterabieClass { public Iterable<String> reversed() ( return new Iterable<String>() ( public Iterator<Strmg> iterator0 { return new Iterator<String>() ( int current words.length - 1; public boolean hasNext() { return current > -lj ) public String next(){ return words f cur rent.--J ; } public void remove () { // No implementado throw new UnsupportedOperationException();

11 Almacenamiento de objetos 770 ) ):

};

) public Iterable<String> randomizedO { 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();

11 Almacenamiento de objetos 771 ) public static void main(String 11 args) { MultilterableClass mic = new MultilterableClass{); for(String s : mic.reversed0 ) System.out .print (s " *); System.ouc.prlntln ti; for(String s : mic.randomized OJ Systetn.out .print (s t- " M); System.out.printIn() for i String s : mic) System .out .print <s *r " i ] I* Output: banana-shaped. be to Earth, the know we how is that And is banana-shaped. Earth that how the be And we know to And that is how we know che Earth to be bananashaped.

///=-

Observe que el segundo mtodo, random ). no crea su propio Iterator sino que se limita a devolver el correspondiente a la lista List mezclada.

Puede ver a la salida que el mtodo Collcctions.shufflel ) no afecta a la matriz original, sino que slo mezcla las referencias en shuffled. Esto es asi porque el mtodo randomized() empaqueta en el nuevo objeto ArrayList el resultado de Arrays.asList( ) Si mezclramos directamente la lista generada por

11 Almacenamiento de objetos 772 Arrays.asLlst( ) se modificara la matriz subyacente, como puede ver a continuacin: /(: holding/ModifymaArraysAsLis t. java import java.util. ; public class ModifyingArraysAsList { public static void main(StringH args) { Random rand new Random(47); Integerll ia = ( 1, 2, 3, 4, 5, 6, 1, 8r 9, 10 }; List<Integer> listl = new ArrayList<Integer>(Arrays.asList(ia)); System.out.printIn("Before shuffling: " + listl); Collections.shuffle(listl, rand); System, out .print In ( "After shuffling: * -* listl); System.out.println("array: " + Arrays.toString(ia) 1 ; List<Integer> list2 = Arrays.asList(ia); System.out.println("Before shuffling: + last2); Collections.shuffleilist2, rand); System.out.println("After shuffling: " > list2) r System.out.printlnI"array: " Arrays.tdSrring();

} | /* Output: Before shuffling: [1, 2, 3, 4. 5, 6, 7, 8, 9. 10)

///:-

11 Almacenamiento de objetos 773 En el pnmer caso, la salida de Arrays.asList( ) se entrega al constructor de Arra>List( ). v esto crea una lista ArrayList que hace referencia a los elementos de ia Mezclar estas referencias no modifica la matriz. Sin embargo, si utilizamoj* directamente el resultado de Arrays.asUst(a). el mezclado modifica el orden de ia. Es importante tener en cuenta que Arrays.asList( ) genera un objeto l-ist que utiliza la matriz subyacente como implementacin tsica Si hacemos algo a

ese objeto List que lo modifique y no queremos que la matriz original sevea afectada, entonces necesario realizar una

ser

copia en otro contenedor. Ejercicio 32: (2) Siguiendoel ejemplo de MultilterableClass. aada mtodos reversed( ) y randomi/cd( ) a

11 Almacenamiento de objetos 774 NonC'olleetionSequenee.java. Haga tambin que NonCollectionSequence impicmente Itcrablc y muestre que las distintas tcnicas funcionan en las instrucciones foreach.Resumen

Java proporciona varias formas de almacenar objetos:

1.

Las matrices asocian ndices numricos con lo* objetos. Almacenan objetos de un tipo conocido, asi que no es necesario proyectar el resultado sobre ningn otro tipo a la hora de buscar un objeto Pueden ser multidimensio- nales y tambin almacenar tipos primitivos. Sin embargo, su tamao no puede modificarse despus de haberlas creado.

2.

Las colecciones (Colleciion) almacenan elementos independientes, mientras que los mapas (Mnp) 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 introducir un tipo incorrecto en un contenedor y tambin para no tener que efectuar 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 permiten almacenar tipos primitivos, pero el mecanismo de autoboxing se encarga de producir las primitivas a los tipos envoltorio almacenados en el contenedor.

Al igual que una matriz, una lista (List) tambin asocia ndices numricos con objetos; por tanto, las matrices y las listas son contenedores ordenados.

11 Almacenamiento de objetos 775


4.

Utilice una lista 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, utilice LinkedList.

5.

Ll comportamiento de las colas (Queue) y de las pilas se obtiene mediante LinkedList

6.

Un mapa (Map) es una forma de asociar los objetos no con valores enteros, sino con otros objeto> Los mapas HashMap estn diseados para un acceso rpido, mientras que un mapa TreeMap mantiene sus clases ordenadas y no es tan rpido como HashMap. Un mapa l.inkedHashMap mantiene sus elementos en orden de insercin. pero proporciona un acceso rpido con mecanismos de hash.

7.

Los conjuntos (Sel) slo aceptan objetos no duplicados. Los conjuntos llashSet proporcionan las bsquedas mas rpidas, mientras que TreeSel mantiene los elementos en orden LinkedHashSet mantiene los elementos en orden de insercin.

8.

No hay necesidad de utilizar las clases antiguas Vector. Hashtahlc y Staek en los nuevos programas

- Collection Genera

11 Almacenamiento de objetos 776 Map T Genera Genera I HasMap I TreeMap

ArrayList j | LinkedList | PriorityQueue J^HashSetl TreeSet J Comparable JLmkedHashSet

LinkedHashMap r Utilidades "\

Taxonoma simple de los contenedores

11 Almacenamiento de objetos 777 Como puede ver. slo hay realmente cuatro componentes contenedores bsicos Map. List. Sel y Queue, y slo dos o tres implementaciones de cada uno (las implcmentaciones de java.utiLconcurrent para Queue no estn incluidas en este diagrama). Los contenedores que ms habitualmente se utilizan son los que tienen lineas gruesas de color negro a su alrededor.

11 Almacenamiento de objetos 778

Los recuadros punteados representan interfaces, mientras que los recuadros de lnea continua son clases normales (concretas). Las lineas de puntos con flechas huecas indican que una clase concreta est implcmentando una interfaz. Las Hechas 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 Lis! puede generar un objeto Listlterator (adems de un objeto Iterator normal, ya que Llsl hereda de Collection)

He aqu un ejemplo que muestra la diferencia en mtodos entre las distintas clases. El cdigo concreto se ha tomado del Capitulo 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 //: holding/ContainerMethods.java import net.mindview.util. public class Corita i nerMet hods ( public static void main(String[1 argsl { ContainerMethodDirferences.main(aras I;

) ) /* Output: (Sample) Collection: [add, addAll, clear, contains, containsAll, equals, hashCode, isEmpty, itera tor, remove, removeAll, retainAll, size, toArrayJ Interfaces in Collection: [Iterable]

11 Almacenamiento de objetos 779 Set extends Collection, adds: Interfaces in Set:[Collection] HashSet extends Set, adds: (J Interfaces in HashSet: [Set, Cloneable, Serializable] LinkedHashSet extends HashSet, addE [] Interfaces in LmkedHashSet:[Set, Cloneable, Senalizabiel TreeSet extends Set, adds: (pollLast, naviqableHeadSet, descencLinglterator, lower, headset, ceiling. pollFirst, subset, navigableTailSet. comparator, first, floor, last, navigableSubSet, higher, tailSet] Interfaces in TreeSet: [NsvigableSet, Cloneable, Serializable] List extends Collection, adds: (listIterator, mdexCf, get, sunList, set. lastlndexQf] Interfaces in List: [Collection] ArrayList extends List, adds: [ensureCapacity, tnmToSize) Interfaces in ArrayList: Serializable] [List, RandomAccess, Cloneable, offer, []

LinkedList extends List, adds: [pollLast, descendinglterator. addFirst, peefcLast, removeFirst, peekFlrst, removeLast. getLast, poll First, pop. poll, aadLast, remfrveFiistOccurrence, getFirst. element, peek. offerLaBt. push, offerrirst. removeLastOccurrence]

Interfaces in LinkedList* [List, Deque. Cloneable, Serializable] Queue extends Collection, adds; ioffer, element, peek, poll.1 Interfaces in Queue: [Collection! PriorityQueue extends Queue, adds: [comparator1 Interfaces in PriorityQueue: (Serialisable] Map: [clear, containsKey. contamsValue. entrySet, equals, get. hashCode, isEmpty, keySet. put, putAll, remove, size, values! HashMap extends Map, adds: [J Interfaces in HashMap: [Map. Cloneable, Serializable] LinkedHashMap extends HashMap, adds: [J Interfaces in LinkedHashMap: [Map] SortedMap extends Map, adds: [subMap. comparator, firstKey, laatKey, headMap, tailMap] Interfaces in SortedMap: [Map] TreeMap extends Map, adds: tdescendmyEntrySet, subMap, pollLastSntry, lastKey, floorEntry. lastEntry, lowerKey,

11 Almacenamiento de objetos 780 naviaableHeadMap, navigableTailMap, descendinaKeySet, tailMap, ceilingEntry, higherKey, pollFiratEntry, comparator, firstKey, floorKey, higherEntry, firstEntry, navigableSubMap, headMap, lcwerEntry, ceilingKey] Interfaces m TreeMap: [NavigableMap, Cloneable, Serializable]

///:*-

Como puede ver, todos los conjuntos, excepto TreeSel, tienen exactamente la misma 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 implementacion de Queue funcional. Finalmente, la nica interseccin entre Map y Collection es el hecho de que un mapa puede generar colecciones utilizando los mtodos entrySet( ) y values( )

Observe l interfaz de marcado ja\a.util.RandomAccess, que est asociada a ArrayList perno no a LinkedList. Esta interfaz proporciona informacin para aquellos algoritmos que quieran modificar dinmicamente su comportamiento dependiendo del USc 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 embargo. a medida que conozca ms detalles acerca de los contenedores en java.util (en particular, en el Captulo 17, Anlisis chillado de los contenedores), vera que existen otros problemas ms importantes que esa estructura de herencia ligeramente extraa Las bibliotecas de contenedores

11 Almacenamiento de objetos 781 han constituido siempre un problema de diseo realmente difcil; resolver estos problemas implica tratar de satisfacer un conjunto de fuerzas que a menudo se oponen entre s Como consecuencia, debemos preparamos para llegar a ciertos compromisos en algunos momentos.

A pesar de estos problemas, los contenedores de Java son herramientas fundamentales que se pueden utilizar de forma cotidiana para hacer nuestros programas ms simples, mas potentes y mas efectivos. Puede que tardemos un poco en acostumbramos a algunos aspectos de la biblioteca, pero lo ms probable es que el lector comience rpidamente a adquirir y utilizar los clases que componen esta biblioteca. Puede en tonina lts olueione* a los ejercido* seleccionados en I domnenlo electrnico The Thinking M Ja\\i AnnntutfdSvluttan Guille, disponible para la venta en mi SfimtView.m

iTratamiento de errores mediante excepciones La filosofa bsica de Java es que el cdigo errneo no ser ejecutado'.

F.l momento ideal de detectar un errores en tiempo de compilacin, antes incluso de poder ejecutar el programa. Su embargo. no todos los errores pueden detectarse en tiempo de compilacin. ll resto de los problemas debern ser gestionados en tiempo de ejecucin, utilizando algn tipo de formalidad que permita que quien ha originado el error pase la informacin apropiada a un receptor que sabr cmo hacerse cargo de las dificultades apropiadamente.

Una ile las formas ms potentes de incrementar la robustez del cdigo es disponer de un mecanismo avanzado de recuperacin de errores. La recuperacin de errores es una de las preocupaciones principales de todos los programas que escribimos, pero resulta especialmente importante cu Java, donde uno de los objetivos principales consiste en crear componentes de programas para que otros los utilcen. Para crear un sistema robusto. cada componente tiene que ser robusto. Al proporcionar un modelo coherente de informe de errores utilizando excepciones. Java permite que los componentes comuniquen los problemas de manera fiable al codigo cliente.

Los objetivos del tratamiento de excepciones en Java son simplificar la creacin de programas fiables de gran envergadura utilizando menos cdigo de lo habitual y llevar esto a cabo con una mayor confianza en que nuestra aplicacin no va a encontrarse con errores no probados. F.l lema de las excepciones no resulta demasiado difcil de aprender y el mecanismo de excepciones es una de esas caractersticas que proporcionan beneficios inmediatos y de gran importancia a cualquier proyecto.

Puesto que el tratamiento de excepciones es la nica forma oficial en la que Java informa acerca de los

errores, y dado que dicho mecanismo de tratamiento de excepciones es impuesto por el compilador 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 cdigo 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 0 y otros lenguajes anteriores disponan a menudo de mltiples esquemas de tratamiento de errores, y dichos esquemas se solan establecer por convenio y no como pane del lenguaje de programacin. Normalmente, 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 > determinar que algo iba mal Sin embargo, a medida que fueron pasando los aos, se descubri que los programadores que utilizaban una biblioteca tendan a pensar en si mismos como si fueran inmunes al error: era casi como si dijeran "Si. puede que a otros se les presenten errores, pero en mi axJtgo no hay errores. Por tamo, de forma bastante natural, los programadores tendan a no comprobar las condiciones del error (y adems, en ocasiones, esas condiciones de error eran demasiado tontas como para comprobarlas1) 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 ilegible. Puesto que los programadores 1-1 programador en t puede, por ejemplo, consultai el valor de tviorno de printfO

podian, a pesar de iodo, 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 mantcnibles.

La solucin consiste en eliminar la naturaleza casual del tratamiento de errores e imponer una cierta formalidad. Este modo de proceder tiene, en la prctica, una larga historia, porque las tmplementaciones de los mecanismos del tratamiento de excepciones se remontan a los sistemas operativos de la dcada de 1960, e incluso a la instruccin "on error goto" de BASIC Pero el mecanismo de tratamiento de excepciones de C++ estaba basado en Ada. y el de Java se basa principalmente en C-H- (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 si podemos estar seguros es de que no podemos continuar 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 informacin en el contexto actual como para poder corregir el problema, asi 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 llamada a un mtodo, ya que la excepcin garantiza que alguien detecte el error Adems, slo hace falta tratar el problema en un nico sitio, en lo que se denomina la ratina de tratamiento 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 cuando las cosas van mal. En general, la lectura, la esentura y la depuracin del cdigo se hacen mucho ms claras con las excepciones que cuando se utiliza la antigua forma de tratar los errores.

Excepciones bsicas

Una condicin excepcional es un problema que impide la continuacin del mtodo o mbito actuales. Resulta importante distinguir las condiciones excepcionales de los problemas normales, en los que disponemos de la suficiente informacin 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 informacin necesaria 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 divisin representa un ejemplo sencillo: si estamos a punto de dividir por cero, merece la pena comprobar dicha condicin. pero que implica que el denominador sea cero? Puede que sepamos, en el contexto del problema que estemos intentando 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 forma 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 continuar ejecutando el programa. Dicho lugar apropiado es la rutina de tratamiento 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 estuviera haciendo.

Como ejemplo simple de generacin de excepciones vamos a considerar una referencia a un objeto denominada t Es posible que nos pasen una referencia que no haya sido inicializada, asi que podramos tratar de comprobarlo antes de invocar un mtodo en el que se utilice dicha referencia al objeto. Podemos enviar la informacin acerca del error a otro contexto de orden superior, creando un objeto que represente dicha informacin y extrayndolo del contexto acta!. Este proceso se denomina generar una excepcin. He aqu el aspecto que tendra en el cdigo: i(t == nuil) throw new NullPointerException(l;

Esto enera la excepcin, lo que nos permite, en el contexto actual, olvidamos del problema: dicho problema ser gestionado de manera transparente en algn otro lugar. En breve veremos dnde exactamente se gestiona la excepcin.

Las excepciones nos permiten 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 hacia falta un mecanismo de tratamiento de excepciones en la informtica distribuida. Las transacciones son el equivalente informtico de los contratos legales. Si algo va mal. detenemos lodos los clculos2. Tambin podemos pensar en las transacciones como si se tratara de un sistema integrado para deshacer acciones, porque (con un cieno cuidado) podemos establecer varios puntos de recuperacin en cualquier programa. Si una parte del programa falla, la excepcin se encarga de deshacer" las operaciones hasta un punto estable conocido dentro del programa.

Uno de los aspectos ms importantes de las excepciones es. que si sucede algo realmente grave, no permiten que el programa contine con la ruta de ejecucin ordinaria. Este tema ha constituido un grave problema en lenguajes como C y C++; especialmente en C, donde no habia ninguna forma 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 permiten (entre otras cosas) obligar al programa a detenerse y a informamos 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 ne, lo que asigna el correspondiente espacio de almacenamiento c invoca un constructor. Existen dos constructores en todas las excepciones estndar El primero es el constnictor predeterminado V el segundo toma un argumento de cadena de caracteres para poder aportar la informacin pertinente a la excepcin: throw new NullPointerExceptionf"t * nuil);

Esta cadena de caracteres puede posteriormente extraerse utilizando vanos 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 forma 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 metemos en problemas Tambin podemos salir de mbitos de ejecucin ordinarios generando una excepcin. En cualquiera de los casos, se devuelve un objeto excepcin y se sale del mtodo o mbito donde la excepcin se haya producido.

Las similitudes con el proceso nomial de devolucin de resultados por pane de un mtodo terminan aqu, porque el lugar al que se vuelve es completamente distinto de aquel al que volvemos en una llamada normal a un 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 raiz 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 detenninar qu es lo que hay que hacer con la excepcin (a menudo, la nica informacin es el tipo de excepcin, no almacenndose ninguna informacin significativa dentro del objeto excepcin). Deteccin de una excepcin

Para ver cmo se detecta una excepcin, primero es necesario entender el concepto de regin protegida. Se trata de una seccin de cdigo que puede generar excepciones que est seguida por el cdigo necesario para tratar dichas excepciones. ' Jim Cira y. ganador del premio Turint* Award por las contribuciones que su equipo ha rea!iado al tema de la* transacciones La> palabras estn tomadas de nnu entrevista publicada en ten m triquen? irrx 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 terminar al generarse la excepcin Si no queremos generar (con throw) la excepcin para salir del mtodo, podemos definir un bloque especial dentro de dicho mtodo para capturar la excepcin. Este hlo- que se denomina bloque try (probar) porque lo que hacemos en la prctica es probar" dentro de esc bloque las diversas llamadas a mtodos. El bloque try es un mbito de ejecucin ordinario precedido por la palabra clave try try { // Cdigo que podra generar excepciones

Si quisiramos comprobar cuidadosamente los errores en un lenguaje de programacin que no tuviera mecanismos de tratamiento 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 varas ocasiones. Con el tratamiento de excepciones incluimos todo dentro del bloque try y capturamos todas 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 Je tratamiento de excepciones, existiendo una de dichas rutinas por cada tipo de excepcin que queramos capturar. Las rutinas de tratamiento de excepciones estn situadas inmediatamente a continuacin del bloque try y se denotan mediante la palabra clave catch try { // Cdigo que podra generar excepciones ) catch(Tipal idl) ( // Tratamiento de las excepciones de Tipo! ) catch(Tipo2 id2) ( // Tratamiento de las excepciones de Tipo2 | catch(Tipo3 id3) ( i // Tratamiento de las excepciones de Tit>c3

// etc...

Cada clusula catch (rutina de tratamiento de excepciones) es como un pequeo mtodo que toma un nico argumento de un tipo concreto. Ll ulentificador (idl. id2. etc.) puede utilizarse dentro de la rutina

de tratamiento de excepciones exactamente igual que un argumento de un mtodo En ocasiones, nunca utilizamos el identificador. porque el tipo de la excepcin nos proporciona ya suficiente informacin como para poder tratar con ella, pero a pesar de todo el identificador debe estar presente.

Las rutinas de tratamiento de excepciones deben aparecer inmediatamente despus del bloque try Si se genera una excepcin. el mecanismo de tratamiento de excepciones trata de buscar la primera rutina de tratamienio cuyo argumento se ajus- le al tipo de la excepcin. A continuacin, entra en la clusula catch. y la excepcin se considera tratada, l a bsqueda de rutinas de tratamiento se detiene en cuanto finalizada la clusula catch. Slo se ejecutara la clusula catch que se ajuste al tipo de la excepcin; estas clusulas no son como las instrucciones switch. en las que hace falta una instruccin break despus de cada clusula case para impedir que las restantes clusulas se ejecuten

Observe que. dentro del bloque try. puede haber distintas llamadas a mtodos que generen la misma excepcin, pero slo hace falta una nica rutina de tratamiento. Terminacin y reanudacin

Existen dos modelos bsicos en la teora de tratamiento de excepciones. Java sopona el modelo denominado de termina- i ion* en el que asumimos que el cnor es tan critico que no hay forma de volver al lugar en el que se gener la excepcin. Como b mnyoriH de U>s lenguajes, incluyendo C ++. C. Pylhon, D. etc.

Quienquiera que generara la excepcin, decidi que no 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. F.sta alternativa implica que la rutina de tratamiento de excepciones debe hacer algo para rectificar la situacin, despus de lo cual se vuelve a intentar ejecutar el mtodo fallido, presumiendo que esa segunda ve/ tendr xito. Si queremos utili/ar 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 corrija el problema. Alternativamente, coloque el bloque try dentro de un bucle wblle que contine volviendo a entrar en el bloque try hasta que el resultado sea satisfactorio.

Histricamente, los programadores que utilizaban sistemas operativos donde se admita el tratamiento de excepciones con reanudacin terminaron utilizando codigo basado en el mecanismo de terminacin y evitando emplear la reanudacin. Por tanto, aunque la reanudacin pueda parecer atractiva a primera vista, no resulta demasiado til en la practica. La razn principal es, probablemente, el acoplamiento resultante: las rutinas 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 distintos. Creacin de nuestras propias excepciones

No tenemos porque limitamos a utilizar las excepciones Java existentes. La jerarqua de excepciones de Java no puede prever todos los errores de los que vayamos a querer informar, asi que podemos crear nuestros propios errores para indicar un problema 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 este prximo al de nuestra propia excepcin (aunque a menudo esto no es posible). La forma ms trivial de crear un nuevo tipo de excepcin consiste, simplemente, en permitir que el compilador cree el constructor predeterminado por nosotros, por lo que no hace falta prcticamente ningn cdigo: // : exceptions/InhentmgExceptions. java // Creacin de nuestras propias excepciones. class SlmpleException extends Exception () public class InneritmgExceptions { public void f t) throws SlmpleException { System.out.println("Throw SlmpleException from throw new SlmpleException();

public static void manStringU args ( InhentmgExceptions sed = new InheritingExceptions()j try | sed. () ; ) catch(SimpleException e) ( ) System.out.printlnl"Caught t!"J;

l / Output: Throw SlmpleException from f{) Caught it!

*///:-

El compilador crea un constructor predeterminado, que de forma automtica (e invisible) invoca al constructor predeterminado de la clase base Por supuesto, en este caso no obtenemos un constructor SimplcException(Strng), pero en la prctica dicho constructor no se usa demasiado. Como veremos, lo

ms importante acerca de una excepcin es el nombre de la clase, por lo que la mayor pane de las veces una excepcin como la que aqu se muestra ser perfectamente adecuada.

Aqu, el resultado se imprime en la consola, donde se captura y se comprueba automticamente utilizando el sistema de visual ilacin de la salida de programas de este libro. Sin embargo, tambin podramos enviar la informacin de error a la salida de error estndar escribiendo en System.crr. Normalmente, suele ser mejor enviar aqu la informacin de error que . System.out. que puede estar redirigido. Si se enva la valida ;i Syslcm.err. no ser redirigida junto con Syslem.out. por lo que es ms probable que el usuario vea la informacin.

Tambin podemos crear una clase de excepcin que tenga un constructor con un argumento String; f f : exceptions/FullConstructors.java class MyException extends Exception ( public MyException<) (} public MyException(Strina msg) ( superimsgl; ) J public clas3 FullConstructors { public static void f<) throws MyException { System.out.printIni"Throwing MyException from fil"); throw new MyException() t

public static void g() throws MyException { System.out.println("Throwing MyException from g l l ; throw new MyExceptlon(MOriginated in g()*);

] public static void main/String[J

args) { try ( it); } catch(MyException el { e printStackTrace(System.out);

} try ( 9 ill } catch{MyException el ( e.printStackTrac*(System.out)t )

) ) / Output Throwing MyException from f(} MyExcept i on at Ful iconst rue tora. f {Ful 1 Construe tors. java *. 11) at FuilCottflaructoxs .main (Ful iConstructars .}ava:19) Throwing MyException from g(j MyExceptionr Originated tn gl) at FullConstruetors.g(FullConstructors.j ava:151 at FullConstructors main IFullConstructors.java:24)

///!-

I cdigo aadido es de pequeo tamao; dos constructores que definen la forma en que se crea MyException En el segundo constructor, se invoca explcitamente el constructor de la clase base con un argumento String utilizando la palabra clavc super
I

n las rutinas de tratamiento, se invoca uno de los mtodos Throwable (de donde se hereda Exception): printStackTracet ) Como puede ver a la salida, esto genera informacin acerca de la secuencia de mtodos que fueron invocados hasta llegar al punto en que se gener la excepcin. Aqu, la informacin se enva a S>stem.out. siendo automticamente capturada y mostrada a la salida. Sin embargo, si invocamos la versin predeterminada:
i

e.printStarrkTrace t) ;

la informacin va a la salida de error estndar.

Ejercicio 1: (2) Cree una clase con un mtodo main( ) que genere un objeto de la clase Exception dentro de un blo

que ir> Proporcione ul constructor de Exceptiun un argumento String. Capture la excepcin dentro de una clusula catch e imprima el argumento String. Aada una clusula fnally e imprima un mensaje para demostrar que pas por all.

Ejercicio 2: (I) Defina una referencia a un objeto e inicialicela con nuil Trate de invocar un mtodo a travs de esta

referencia. Ahora rodee el cdigo con una clusula trv-catch para capturar la excepcin.

Ejercicio 3: (I) Escriba cdigo para ArraylndcxOutOfBoundsExeeption i ndice de

generar

capturar

una

excepcin

matriz fuera de limites).

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

deje de generar una excepcin. Excepciones y registro

Tambin podemos registrar la salida utilizando java.util.logging. Aunque los detalles completos acerca de los mecanismos de registro se presentan en el suplemento que puede encontrarse en la direccin http./ZMimil uw.rtet/Books/BelterJuva, los mecanismos de registro bsicos son lo suficientemente sencillos como para poder utilizarlos aqui. //: exceptione/LoggmgExceptions. java // 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 .get:Logger ("LoggmgExcepcion*4) ; public LoagingExceptionO ( Strir.gWriter trace new StringWriter () ,* printStackTrace {new PrintWncer (trace) ) ; logaer.severe(trace.toString() );

) public class LogglngExceptions { public static void main(String(] args' ( try { throw new LoggingException(); ) catchiLoggingException e) ( System.err.println{"Caught " e) ;

) try { throw new LoggingException() ; } catch(LoggingException e) { System err.printIn("Caught " e);

}
I

/ Output: (85% match)

Aug 30, 2005 4:02:31 PM LoggingException <init> SEVERE: LoggingException at LogoingExceptions.main(LoggingExcept ions.j ava:19 J Caught LoggingException Aug 30, 2005 4:02:31 PM LoggmgExcepcin <init> SEVERE: Loag mgExcept ion

at Lcgg mgExcept ions. mam (Loga mgExcept i ons .java: 24) Caught LoggmgException ///?-

ti mtodo esttico Logger.getLogger( ) crea un objeto Logger asociado con el argumento String (usualmente. el nombre del paquete 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 mensaje de registro; aqu utilizamos severe( ) Para producir el objeto String para el mensaje de registro, convendra disponer de la traza de la pila correspondiente al lugar donde se gener la excepcin, pero printStackTrace ) no genera un objeto String de forma predeterminada. Para obtener un objeto String. necesitamos usar el mtodo sobrecargado printStackTrace( ) que toma un objeto javs.io.PrintW riter como argumento (todo esto se explicar con detalle en el Capitulo 18. E S ) . Si entregamos al constructor de PrintWritcr un objeto java.io.StringW riter. la salida puede extraerse como un objeto String invocando toString()

Aunque la tcnica utilizada por LoggmgException resulta muy cmoda, porque integra toda la infraestructura de registro dentro de la propia excepcin, y funciona por tanto automticamente sin intervencin del programador de clientes, lo ms comn es que nos veamos en la situacin de capturar y registrar la excepcin generada por algn otro, por lo que es necesario generar el mensaje de registro en la propia rutina de tratamiento de excepciones: //: except ions/LoggmgExceptions2 . java // Registro de las excepciones capturadas, import java.til.logging. import java.lo.*: pubiic class LoggingExceptians2 ( prvate static Logger loggex * Logger.geCLogger("LogaingExcepti ons2"); static vod logException(Exception e) { StringWriter trace = new StringWriter < > ,* e.printStackTracelnew PrIntWriter trace)); logger .severe(trace.toStringO ) ;

pubiic static void mair. (String U args) ( try ( throw new NtillPo nter Except ion () ,*

cacch (NullPomterException e) ( logException le)/


I

> / Outputi (90% match) Aug 30, 2005 4:07:54 PM LogaingExceptions2 logException SEVERE: Java.iang.NullFainterException at LoggmgExceptions2 .mam (Logo mgExcept i ons2 .java: 16)

*///:-

F1 proceso de creacin de nuestras propias excepciones puede llevarse un paso ms all. Podemos aadir constructores N miembros adicionales: //: exceptons/ExtraFeaturea.java // Mejora de las clases de excepcin, import static net.mindview.util.Print.*; class MyExceptian2 extends Excepcin { private int xj pubiic MyException2() ()

pubiic MyException2(String msg) ( superlmsgj; ( pubiic MyException2(String msg, int x) (

this.x = x; i 12 Tratamiento de errores mediante excepciones 802 3uper(msg); public int val) ( return x; ) public String getMessage{) ( return Detail Message: x + * **+ super .getMessage () ;

} public class ExtraFeatures | public static void ft) throws MyException2 { print("Throwing MyException2 from f I)M)* throw new MyExceotion2O ; i public static void g I) throws MyException2 ( print ("Throwing MyException2 from gM")/ throw new MyException2("Originated in g()");

] public static void hfl throws MyException2 { print("Throwing MyException2 from h()M); throw new MyException2("Originated in h(>". 47);

this.x = x; i 12 Tratamiento de errores mediante excepciones 803 ) public static void main(String[] args) ( try ( ft); ) catch(Myxception2 e) { e.printStackTrace(System.out);

) try ( gO ; ) catch(MyExceptionS ej ( e.printStackTrace(System.out);

) try (

h() f ) catchMyException2 e) { e.printStackTrace(System.out I; System.out.printIn("e-val() = " + e.val(J);

this.x = x; i 12 Tratamiento de errores mediante excepciones 804 }

> | / Output: Throwing MyException2 from ft) MyException2! Detail Message: 0 null at ExtraFeatures.fiExtraFeatures.jav a:22) at ExtraFeatures.main<ExtraFeatures. java:34) Throwing MyException2 from g() MyException2: Detail Message: 0 Originated in gl) at ExtraFeatures.g(ExtraFeatures. java26) at ExtraFeatures.main<ExtraFeatur es.java:39) Throwing MyException2 from h() MyException2: Detail Message: 47 Originated in h() at ExtraFeatures.h(ExtraFeatures.j ava:301 at ExtraFeatures.main(ExtraFeatures.java: 44) e.vald - 47

*///:

this.x = x; i 12 Tratamiento de errores mediante excepciones 805 Sc ha aadido un campo x junto con un mtodo que Ice dicho valor v un constructor adicional que lo inicializa. Adems,

I hro\vable.get!Vlcssage<) ha sido sustituido para generar un mensaje de detalle ms interesante. get.Mcssage() es un mtodo similar a toString() que se utiliza para las clases de excepcin.

Puesto que una excepcin no es ms que otro tipo de objeto, 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 cliente que utilicen nuestros paquetes, ya que dichos programas puede que simplemente miren cual es la excepcin que se ha generado y nada ms (sa es la forma en la que se utilizan la mayora de las excepciones de la biblioteca Java).

Ejercicio 6: (I) Cree dos clases de excepcin, cada una de las cuales realice su propia tarea de registro automticamen

te. Demuestre que dichas clases funcionan.

this.x = x; i 12 Tratamiento de errores mediante excepciones 806 Ejercicio 7: < 1) Modifique el Ejercicio 3 para que la clusula catch 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 conveniente porque quien realiza la invocacin puede saber asi exactamente qu cdigo necesita escribir para capturar todas las excepciones potenciales. Por supuesto, si el cdigo fuente est disponible, el programador de clientes podra examinarlo y buscar las instrucciones hrow. pero puede que una biblioteca se distribuya sin el correspondiente cdigo fuente. Para evitar que esto constituya un problema. Java proporciona una sintaxis (y nos obliga a usar esa sintaxis) para permitimos informar al programador de clientes acerca de cules son la* excepciones que el mtodo genera, de modo que el programador pueda tratarlas. Se trata de la especificacin de excepciones que forma parte de la declaracin del mtodo y que aparece despus de la lista de argumentos

La especificacin de excepciones utiliza una palabra clave adicional, throws. seguida de todos los tipos potenciales de excepciones. Por tanto, la definicin de un mtodo podra tener el aspecto siguiente: void f() throws TooBig, TooSmall, DivZero ( //,..

Sin embargo, si decimos void f 0 { // ...

this.x = x; i 12 Tratamiento de errores mediante excepciones 807 significa que el mtodo no genera ninguna excepcin (salvo por las excepciones heredadas de KuntimeException. que pueden generarse en cualquier lugar sin necesidad de que exista una especificacin de excepciones; hablaremos de estas excepciones ms adelante).

No podemos proporcionar informacin falsa acerca de la especificacin de excepciones. Si el cdigo dentro del mtodo provoca excepciones y ese mtodo no las trata, el compilador lo detectar y nos informara 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 garantiza en tiempo de compilacin que exista una cierta correccin en la definicin de las excepciones.

Slo hay una manera de que podamos proporcionar informacin 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 disponer por adelantado de una forma de aadir posteriormente la excepcin; si ms adelante decidimos comenzar a generar esa excepcin, no tendremos que realizar cambios en el cdigo existente. Tambin resulta importante esta caracterstica para crear interfaces y clases bases abstractas cuyas clases derivadas o implcmentaciones puedan necesitar generar excepciones.

Las excepciones que se comprueban y se imponen en tiempo de compilacin se denominan excepciones comprobadas.

this.x = x; i 12 Tratamiento de errores mediante excepciones 808 Ejercicio 8: (I) Escriba una clase con un mtodo que genere una excepcin del tipo creado en el Ejercicio 4. Trate de

compilarlo sin incluir una especificacin de excepcin para ver qu es lo que dice el compilador. Aada la especificacin de excepcin apropiada. Pruebe la clase y su correspondiente excepcin dentro de una clusula trv-catch Cmo capturar una excepcin

Resulta posible crear una rutina de tratamiento que capture cualquier tipo de excepcin. Para hacer esto, podemos capturar el upo de excepcin de la clase base Exception (existen otros tipo de excepciones base, pero Exception es la base que resulta apropiada para casi todas las actividades de programacin): catch(Excepcin e) ( System.ouc.princln("Caughc an excepcin"}?

Esto permitir 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 evitar que se impida entrar en accin a otras rutinas de tratamiento de excepciones que puedan estar situadas a continuacin.

this.x = x; i 12 Tratamiento de errores mediante excepciones 809 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 informacin especifica acerca de la excepcin concreta que hayamos capturado, pero podemos invocar los mtodos incluidos en su tipo base Throwable: Strin getMessage()

Strinj getLocalizcdMcssage()

Obtiene el mensaje de detalle o un mensaje ajustado para la configuracin de idioma utilizada.

String toString()

Devuelve una descripcin corta del objeto Throwable. incluyendo el mensaje de detalle, si es que existe uno.

this.x = x; i 12 Tratamiento de errores mediante excepciones 810 vnid printStaek'Traee( )

void printStackTraee(PrintStrearo)

void prntStackTrace(java.io.Print\Vriter)

Imprime el objeto Throwable y la traza de pila de llamadas de dicho objeto. La pila de llamadas muestra la secuencia de llamadas 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 i8. F/S, veremos por qu existen dos tipos de flujos de salida). Throwable filllnStack Irace()

Registra informacin dentro de este objeto Throwable acerca del estado actual de los marcos de la pila. Resulta til cuando una aplicacin est volviendo a generar un error o una excepcin (hablaremos de esto en breve).

this.x = x; i 12 Tratamiento de errores mediante excepciones 811 Adems, tambin tenemos acceso a otros mtodos del tipo base de Throwable 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 getName( ). que incluye informacin o getSimpleName(). que slo devuelve el nombre de la clase.

He aqui un ejemplo que muestra el uso de los mtodos bsicos de Exeepiion //: exceptions/BxcepcionMechods.java // IlusCracin de los mcodos de Excepcin, import static nec .mindview.ucil.princ . .pubiic class ExceptionMethods { pubiic scacic void main (String [] args) { try f throw new Excepcinl"My Excepcin);

} cacch(Excepcin e) { princ("Caughc Excepcin); princ ("gecMessage () : " + e.gecMessage()); prinC("gecLocalizedMessage():" + e.gecLocalizedMessage()>; pnnt ("toString () : * + e); prinC{"prinCSCackTraceO :M) ; e.princScackTrace(SysCem.ouC);

this.x = x; i 12 Tratamiento de errores mediante excepciones 812 ) ) /* Output: Caughc Excepcin gecMessageO:My Excepcin gecLocalizedMessage():My Excepcin C0SCring():java.lana.Excepcion: My Excepcin prlntStackTrace(): j ava. i ana. Except ion: My Except i on at ExceptionMethods.raain(ExceptionMethods.java:0)

///:-

Podemos ver que los mtodos proporcionan sucesivamente mas informacin, de hecho, cada uno de ellos es un supercon- junto del anterior.

Ejercicio 9: las tres n

(2) Cree tres nuevos lipos de excepciones. Escriba una clase con un mtodo que genera

this.x = x; i 12 Tratamiento de errores mediante excepciones 813 main( ). llame al mtodo pero utilice una nica clusula catch que permita capturar los tres tipos de excepciones. La traza de la pila

Tambin puede accedente directamente a la informacin proporcionada por printStackTracd ). utilizando gctStack l race( ) Este mtodo devuelve una matriz de elementos de traza de la pila, cada uno de los cuales representa un murco de pila. El elemento cero es la parte superior de la pila y se corresponde con la ltima invocacin de mtodo de la secuencia (el punto en que este objeto Throw able fue generado). El ltimo elemento de la matriz ( la pane 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() ( // Generar una excepcin para rellenar la traza de pila, try ( throw new Exception O ; ) catch (Exception e) ( for(StackTraceElement ste . e.getStackTrace(}> System.out.printIn(ste.getWethodName<));

this.x = x; i 12 Tratamiento de errores mediante excepciones 814 static void g() ( fllr ) 6tatic void h() ( gU; ) public static void main (String {,] args> { f O; System.out .printIni" - - ------------* I ;

g () ; System, out. print In ("-------- ----fall; " ] -

) ) /* Output: f main f

g ma in f

this.x = x; i 12 Tratamiento de errores mediante excepciones 815 g h main

///:-

Aqui. nos limitamos a imprimir el nombre del mtodo, pero tambin podramos imprimir el objeto completo StackTraceElement. que contiene informacin adicional Regeneracin de una excepcin

En ocasiones, conviene regenerar una excepcin que hayamos capturado, particularmente cuando se utili/a Exception para capturar todas las excepciones. Puesto que ya disponemos de la referencia a la excepcin actual, podemos simplemente volver a generar dicha referencia: catch(Exception e) ( System.out.printIR("An exception was thrown"); throw s; I

La regeneracin de una excepcin hace que esta pase a las rutinas de tratamiento de excepciones del

this.x = x; i 12 Tratamiento de errores mediante excepciones 816 siguiente contexto de nivel superior. Las clusulas catch adicionales incluidas en el mismo bloque try seguirn ignorndose. Adems, se preserva toda la informacin acerca del objeto de excepcin, por lo que la rutina de tratamiento en el contexto de nivel superior que capture ese tipo excepcin especifico 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 printStackTrace() ser la relativa al origen de la excepcin, no la relativa al lugar donde la hayamos regenerado. Si queremos insertar nueva informacin de traza de la pila podemos hacerlo invocando filllnStackTrace( ). que devuelve un objeto rhrowable que habr generado insertando la informacin de pila actual en el antiguo objeto de excepcin. He aqu un ejemplo: f/: exceptions/Rethrowing.java // Ilustracin de fllllnStackTrace1 1 public class Rethrowing { public static void () throws Exception { System, out .print in ("originating the exception in f()*Mj throw new Exception I"thrown from 0");

) public static void gtJ throws Exception ( try { f l l i ) catch(Exception e) { System, out. print In 1 "Inside gO ,e.printStackTrace () ") ; e.print3tackTrace(System.out); throw ej )

puhlic starir. void hi) throws F.xc^prlrm { try {

this.x = x; i 12 Tratamiento de errores mediante excepciones 817 U); ) catchiExcept ion e j System.out.printin("Inside hi)e.printStackTrace 0"); e.printStackTrace <: System, out) ; throw (Exception)e.filllnStackTraceil;

public static void main(Stringfl argsi { try { g() ; ) catch'Exception e\ j System.out.printIn("main: printStackTracei "); e.printStackTrace(System.ouc); 1 try | h ( ) ; | catch(Exception e> ( System, out. print In ("main: printStackTrace0 ' > ; e.printStackTrace(System.out};

) ) /* Output: originating the exception in f{) Inside g(), e.printStackTrace() java.lang.Exception: thrown from f() at Rethrowing.f(Rethrowing.java:7) at Rethrowing.g(Rethrowing.java:11) at Rethrowing.main(Rethrowing.java:29 ) main: printStackTrace{)

this.x = x; i 12 Tratamiento de errores mediante excepciones 818 java.lang.Exception: thrown from f{) at Rethrowing.f(Rethrowing.java:7) at Rethrowing.giRethrowing.j ava:11) at Rethrowing.main(Rethrowing.java;29 ) originating the exception in ff) Inside h(),e.printStackTrace() java.lang.Exception: thrown from f() at Rethrowing.f(Rethrowing.java:7) at Rethrowing.h(Rethrowing.java:20) at Rethrowing.main(Rethrowing.java:35 ) main: printStackTrace() java.lang.Exception: thrown from f() at Rethrowing.hRethrowing.java :24) at Rethrowing.main(Rethrowing.j ava:35)

*///:-

La linea donde se invoca fillInStackTrace() ser el nuevo punto de origen de la excepcin.

Tambin resulta posible volver a generar una excepcin distinta de aquella que hayamos capturado. Si hacemos esto, obtenemos un efecto similar a cuando usamos fillInStackTracc(): la informacin acerca del lugar de origen de la excepcin se pierde, y lo que nos queda es la informacin correspondiente a

this.x = x; i 12 Tratamiento de errores mediante excepciones 819 la nueva instruccin throw: //: except ions/RethrowNew.java // Regeneracin de un objeto distinto de aqul que fue capturado. class OneException extends Exception { public OneException(String s) { super(s); )

) class TwoException *xt#ririfi Except-inn ( public TwoException(Strina s) { super(s); |

) public class RethrowNew ( public static void ft) throws OneException { System.out .println l "originating the exception in f () ") ; throw new OneException("thrown from f()M); I public static void main(String[] args) { try ( try { f0J } catch(OneException e) { System.out.println( "Caught in inner try, e.printStackTrace(J"); e.printStackTrace(System.out); throw new TwoException("from inner try"); i ) catch(TwoException e) ( System.out.println( "Caught in outer try, e.printStackTrace)"); e.printStackTrace(Sy9tera.out);

this.x = x; i 12 Tratamiento de errores mediante excepciones 820 )

) ) / Output: originating the exception in () Caught in inner try, e.printStackTrace0 OneException: thrown from {) at RethrowNew. f IRethrowNew. java: 15) at RethrowNew.main(RethrowNew.java:2 0) Caught in outer try. e.printStackTracet) TwoException: from 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 ninguna 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.

this.x = x; i 12 Tratamiento de errores mediante excepciones 821 Encadenamiento de excepciones

A menudo, nos interesa capturar una excepcin y generar otra, pero manteniendo la informacin acerca de la excepcin de origen; este procedimiento se denomina cncaiienamiento de excepciones Antes de la aparicin del JDK 1.4* los programadores tenan que escribir su propio cdigo para preserv ar la informacin de excepcin original, pero ahora todas las subclases de Throwable tienen la opcin de admitir un objeto cansa dentro de su constructor. Lo que se pretende es que la causa represente la excepcin original, y al pasarla lo que hacemos es mantener la traza de la pila correspondiente al origen de la excepcin, incluso aunque estemos generando una nueva excepcin.

Resulia interesante observar que las nicas subclases de Throwable que proporcionan el argumento causa en el constructor son las tres clases de excepcin fundamentales Error (utilizada por la mquina virtual JVM para informar acerca de los errores del sistema), Exception y RuntimeF.xception Si queremos encadenar cualquier otro tipo de excepcin, tenemos que hacerlo utilizando el mtodo init( ause ). en lugar del constructor.

He aqu un ejemplo que nos permite aadir dinmicamente campos a un objeto DynamicFields en tiempo de ejecucin: //: exceptlons/DynamicFields.java // Una cla.se que aade dinmicamente campos a s misma. // Ilustra el mecanismo de encadenamiento de excepciones import static net.mindview.util.Print.; class DynamicFieldeExcepticn extends Exception )) public clas3 DynamicFields ( private Object 13 U fields; public DynamicFieldstint initialSi2ei ( fields = new ObjectLinitialSizeJ [2]; for I int 1 =

this.x = x; i 12 Tratamiento de errores mediante excepciones 822 0; i < initialSize; i-f-f) fields(i] new Object[] { null, null };

} public String toStringi) ( StringBuilder result new StringBuilder(I; for(Objecttl obj : fields) ( result.append(ob j[0J1; result.append<": *); result .append(obj [1) ) ; result.append(\n" );

) return result.toString();

private int hasField(String id) ( for(int i = D; i < fields.length; if(id.equals(fields tj [0])f return i; return -1;

} private int getFieldNumber(String id) throws NoSuchFieldException ( lnt fieldNum = hasField I id) ; iflfieldNum *= -1)

this.x = x; i 12 Tratamiento de errores mediante excepciones 823 throw new NoSuchFieldException(/ ; return fieldNum;

) private int makeFieldtString id) { for(int i = 0; i < fields.length; i+4) if(fieldsfi] [0] ** null) { fields [i] [0] = id; return i;

) // No hay campos vacos. Aadir uno*. Object HU tmp * new Object [fields. length + U 2] ; for (int i = 0; i < fields. length; tmp[i] = fields fi]; for (int i * fields. length; i < tmp. length; !+-*-) tmpti] = new Object [] ) null, null }; fields = tmp; // Llamada recursiva con campos expandidos: return makeField(id); i public Object getFiela(String id) throws NoSuchFieldException { return fields[getFieldNumber(idJ][1];

) public Object setFieldIString id, Object value) throws DynamicFieldsException { if(value == null) (

this.x = x; i 12 Tratamiento de errores mediante excepciones 824 //La mayora de las excepciones no tienen un constructor con "causa*. // En estos casos es necesario emplear initCaucc(), // disponible en todas las subclase de Throwable. DynamicFieldsException dfe = new DynamicFieldsException0: dfe.initCause (new NullPointerException( ) ) ; throw dfe;

) int fieldNumber = hasField(id) ; if(fieldNuraber == -1) fieldNumber = makeField(id); Object result = null; try ( result - getField(id); / / Obtener valor anterior ) catch(NoSuchFieldException e) { I f Utilizar constructor que admite la HcausaM: throw new Run time Except ion (e) ;

) fields(feldNumber](1] = value; return result;

) public static void main(String[] args) {DynamicFields df = new DynamicFields13); print(df); try ( df.setField{"d". HA valu for d"); df.setField("number", 47j ; df.setField("number2", 48); prnc(df); df .setField("d", "A new valu for d"

this.x = x; i 12 Tratamiento de errores mediante excepciones 825 df.setField("number3", 11 ); print("df: * d f ) ; print i "df .getField\nd\") : " f df .getField ("d") I; Object field = df.setField("d", nuil); // Excepcin ) catch(NoSuchFieldException e) ( e .prmtStackTrace (System.out} ; ) catch(DynamicFieldsException e) ( e.printStackTrace(System.cut;

) /* Output: nul1: nuil nuil: nuil nuil: nuil d: A valu for d number: 47 number2: 48 df: d: A new valu for d number: 47 number2: 4B number3: 11 df.getField("d") : A new valu for Q DynamicFieldsException at DynamicFields.setField(DynamicFields.java:64) at DynamicFields.main(DynamicFields.java:94) Caused by: java.lang.NullPointerException at DynamicFields.setField(DynamicFields.java:66) ... 1 more

///Cada objeto DynamicFields contiene una matriz de parejas Object-Object Fl primer objeto es el identificador del campo (de tipo String). mientras que el segundo es el valor del campo, que puede ser de cualquier upo. salvo una primitiva no envuelta en otro tipo de objeto. Cuando se crea el objeto, tratamos de adivinar cuntos campos vamos a necesitar Cuando invocamos sctField(). dicho mtodo localiza el campo existente que tenga dicho nombre o crea un nuevo campo, colocando a continuacin el valor en l. Si se queda sin espacio, se aade nuevo espacio creando una matriz de longitud igual a la

this.x = x; i 12 Tratamiento de errores mediante excepciones 826 anterior ms uno y copiando en ella los antiguos elementos. Si tratamos de almacenar un valor nuil, se genera una excepcin DynamicFieldsExcepfion creando una excepcin y utilizando initCause( ) para insertar una excepcin NullPointerException como la causa. Como valor de retomo. setField( ) tambin extrae el antiguo valor situado en dicho campo utilizando getField( ). que podra generar la excepcin NoSuchFieldException (no existe un campo con dicho nombre) Si el programador de clientes invoca 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 excepcin RuntimeException utilizando el constructor que admite un argumento de causa. Como podr obervar. toStrng( ) utiliza un objeto StringRuilder para crear su resultado. Hablaremos ms en detalle acerca de Strin^Builder en el Capitulo 13. Cadenas de caracteres, pero en general conviene utilizar este tipo de objetos cada vez que estemos escribiendo un mtodo toString( ) que implique la utilizacin de bucles como es el caso aqui.

Ejercicio 10: (2| Cree una clase can dos mtodos; f( ) y g( ) En gl ). genere una excepcin de un nuevo tipo definido

por e) usuario En f), invoque a g ), capture su excepcin y. en la clausula catch. genere una excepcin diferente (de un segundo tipo tambin definido por el usuario). Compruebe el cdigo en muiiH )

Ejercicio 11: < l) Repita el ejercicio anterior, pero dentro de la clausula catch. envuelva la excepcin j( ) dentro de uim excepcin RuntimeException

this.x = x; i 12 Tratamiento de errores mediante excepciones 827 Excepciones estndar de Java

La clase Java Throwable describe todas las cosas que puedan generarse como una excepcin. Existen dos tipos genralo de objetos Throwable (nipos de " que heredan de") Error representa los errores de tiempo de compilacin y del sistema de los que no tenemos que preocuparnos de capturar (salvo en casos muy especiales) Exceptiou es el tipo bsico que puede generarse desde cualquiera de los mtodos de la biblioteca estndar de Java, asi como desde nuestros propios meto- dos y tambin cuando se producen errores de ejecucin. Por tanto, el tipo base que ms interesa a los programadores de Java es usualmente Exeeption

La mejor forma de obtener una panormica de las excepciones consiste en examinar la documentacin del JDK. Conviene hacer esto al menos una vez para tener una idea de las distintas excepciones, aunque si lo hace se dara cuenta pronto de que no existen diferencias muy grandes entre una excepcin > otra, salvo en lo que se refiere al nombre. Asimismo, el numero 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 excepciones. 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.lao. algunas se definen para dar soporte .i Otras bibliotecas como mil. net e io. lo cual puede deducirse a partir del nombro completo de la clase correspondiente o de la clase de la que heredan. Por ejemplo, todas las excepciones de E S heredan de java.io.lOException Caso especial: RuntimeException

El primer ejemplo de este capitulo era: ifit == nulil throw new NuliPointerBxception) ?

this.x = x; i 12 Tratamiento de errores mediante excepciones 828 Puede resultar un poco aterrador pensar que debemos comprobar st todas las referencias que se pasan a un mtodo son iguales a nuil (dado que no podemos saber de antemano si el que ha realizado la invocacin nos ha pasado una referencia vlida). Afortunadamente, no es necesario realizar esa comprobacin manualmente: dicha comprobacin forma parte del sistema de comprobacin estandar en Tiempo de ejecucin que Java aplica automticamente, y si se realiza cualquier llamu- da a una referencia nuil. Java generar automticamente la excepcin NullPointcrExeeption Por tanto, el anterior fragmento de cdigo resulta siempre superfluo. aunque s que puede resultar interesante realizar otras comprobaciones para protegerse frente a la aparicin de una excepcin NuliPointcrException.

Existe un conjunto completo de tipos de excepcin que cae dentro de esta categora Se trata de excepciones que siempre soti generadas de forma 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 hase denominada RuntimeException. que constituye un ejemplo perfecto de herencia: establece una familia de tipos que tienen determina* das caractersticas y comportamientos en comn. Asimismo, nunca es necesario escribir una especificacin de excepcin que diga que un mtodo puede generar RuntimeException lo cualquier tipo heredado de RuntimeException). porque e trata de excepciones no comprobadas. Puesto que estas excepciones indican errores de programacin, normalmente no se suele capturar una excepcin RuntimeException. sino que el sistema las trata automticamente. Si nos viramos obligados a comprobar la aparicin de este upo de excepciones, el cdigo seria enormemente lioso. Pero, aunque normalmente no vamos a capturar excepciones RuntimeException. si 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 una excepcin RuntimeException ascienda por toda la jerar quia Je mtodos sin sor capturada, hasta llegar :il mtodo main( ). Para ver lo que sucede en este caso, trate de ejecutar el siguiente ejemplo: //. exceptions/HeverCaught,j ava Lo que sucede al ignorar una excepcin RuntimeException. // (ThrowsExcepton) public class NeverCauaht f atatic void f() ( throw new RuntimeBxception ("From f 1 ") ; j asatie void g() ( fh'i i public atatc vea main(Stringj args) (

this.x = x; i 12 Tratamiento de errores mediante excepciones 829 90 ;

>

| / / '/ I -

Como puede ver. KuntinieExccption to cualquier 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.err: Excepcin in thread "main" java.lang.RuntimeException: From f(l at NeverCaught.f'NeverCaughr.java:7) at NeverCaught.g(NeverCaught.java:10 > an NeverCaught .mam(NeverCaught. java: 13)

Por tanto, la respuesta es: si una excepcin RuntinicException llega hasta niain( ) sin ser capturada, se invoca printSfackTracc() para dicha excepcin en el momento de salir del programa.

this.x = x; i 12 Tratamiento de errores mediante excepciones 830 Recuerde que slo las excepciones de tipo RuntimeE\ccption (y sus subclases) pueden ser ignoradas en nuestros programas. ya que el compilador obliga exhaustivamente a tratar todas las excepciones comprobadas. El razonamiento que explica esta forma de actuar es que RuntinicException representa un error de programacin, que es:

!. Un error que no podemos anticipar. Por ejemplo, una referencia nuil que escapa a nuestro control.

2. Un error que nosotros, como programadores, deberamos haber comprobado en nuestro cdigo (como por ejemplo una excepcin ArraylndexOutOfBoundsException. 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 enormemente beneficioso disponer de excepciones en este caso, ya que nos ayudan en el proceso de depuracin.

tis interesante observar que el mecanismo de tratamiento de excepciones de Java ni' tiene un nico objetivo. Por supuesto, est diseado 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.

this.x = x; i 12 Tratamiento de errores mediante excepciones 831 Ejercicio 12: (3) Modifique innercIasses/Sequence.java para que genere una excepcin apropiada >i 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 generada dentro de un bloque try. Usualmente, ese fragmento de cdigo se relaciona con alguna operacin distinta de la de recuperacin de memoria (ya que esta operacin es realizada automticamente por el depurador de memoria). Para conseguir este efecto, utilizamos una clusula finally24 despus de todas las rutinas de tratamiento de excepciones. La estructura completa de una seccin de tratamiento de excepciones seria, por tanto:

try { // La regin protegida: actividades peligrosas // que pueden generar A, B o C } catch A al) { // Rutina de tratamiento para la situacin A ] catch(B bl) ( // Rutina de tratamiento para la situacin B } catchIC el) { // Rutina de tratamiento para la situacin C ) finally { Kl mecanismo de tratamiento de excepciones de C++ no dispone de la clusula finullx, porque depende de la utilizacin de destructores pura llevar a cabo esta?, tareas de limpic/a.
24

this.x = x; i 12 Tratamiento de errores mediante excepciones 832 // Actividades que tienen luaar en todas las ocasiones

Para demostrar que la clusula fnally siempre se ejecuta, pruebe a ejecutar este programa: //; exceptions/FinallyWorks.java // La clusula finally siempre se ejecuta. class ThreeException extends Excepton () pubiic class FinallyWorks { static int count 0; puble static void main(StringU args) { while(true) ( try { // El post-incremento es cero la primera vez: if(count+ =* 0) throw new ThreeException0 ; System.out.println("No exception"); } catch(ThreeException e) { System.out.println"ThreeException); ) finally { System.out .println("Tn finally clause),* if(count == 2) break; // fuera del bucle "while

this.x = x; i 12 Tratamiento de errores mediante excepciones 833 ) i

) ) /* Output: ThreeException In finally clause No excepcin In finally clause ///'-

Analizando la salida, podemos ver que la clusula finally 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 permiten continuar con la ejecucin a partir del punto donde se gener la excepcin, como ya hemos indicado anteriormente. Si incluimos nuestro bloque try en un bucle, podremos establecer una condicin que habr que satisfacer antes de continuar con el programa Tambin podemos aadir un contador esttico o algn otro tipo de elemento para permitir 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.

this.x = x; i 12 Tratamiento de errores mediante excepciones 834 Para qu sirve finally?

En un lenguaje que no tenga depuracin de memoria v que no tenga llamadas automticas a destructores.M la clusula finally es importante porque permite al programador garantizar que se libera la memoria, independientemente de lo que suceda en el bloque fry. Pero Java dispone de un depurador de memoria, por lo que la liberacin de memoria casi nunca es un problema Asimismo, no dispone de ningn destructor al que invocar. Por tanto, cundo es necesario utilizar finally en Java?

La clusula finally es necesaria cuando tenemos que restaurar a su estado original alguna otra cosa distinta 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 conexin de red, de borrar algo que hayamos dibujado en la pantalla o incluso de accionar un conmutador en el mundo exterior. ial como se lustra en el siguiente ejemplo: .//: except long /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 offi ( state = false; print(this); ) public String toStringO { return state ? "on : off"; ] | III:exceptions/OnOffExceptionl.java public class OnOffExceptionl extends Exception () ///:

f t r exceptions/OnOffException2.java public class OnOffException2 extends Exception () f t f - . f f : exceptions/OnOffSwitch.java f t Por qu usar finally? destructor. O dispone de llamados aulomaticas a destructores, mientras que O, que se parece ms a Java, dispone de un mecanismo que hace pos ib I que enea lugor la destruccin automtica.
M

this.x = x; i 12 Tratamiento de errores mediante excepciones 835 public class OnOffSwitch ( private static Switch sw - new SwitchO; public static void f(J throws OnOfiExceptionl,OnOffExcepticn2 () public static void main(String[] args) j try { sw.on 0; f t C6digo que puede generar excepciones... fO; SW.Off O ; } catch(OnOffExceptionl e) { System.out.printIn("OnOfiExceptio nl"); sw.off(); ) catch(OnOffException2 ej { System.out.printIn I"GnOf t Exception2); sw.off();

) | /* Output: on off ///:-

Nuestro objetivo es aseguramos de que el conmutador est cerrado cuando se complete la ejecucin de nian( ). 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 genere alguna excepcin que no sea capturada aqu, en cuyo caso

this.x = x; i 12 Tratamiento de errores mediante excepciones 836 no se ejecutara s>v.off( ). Sin embargo, con finally podemos incluir el cdigo de limpieza del bloque try en un nico lugar: f t : exceptions/WithFinally.java 7 Finally garantiza que se ejecuten las tareas de limpieza, public class WthFinally ( static Switch sw = new SwitchO: pubiic static void main(String[] arga) { try | aw. on 0 ; // Cdigo que puede generar excepciones... OnOffSwitch.f(1 ; ) catch(OnOffExceptionl e) ( System.out.println("OnOff Except ionlMM ) ) catch(OnOfException2 e) ( System.out.printlnl"OnOffException2"); ) finally { aw.off( );

) ) / Output; on off ///:-

Aqui, la llamada a su.off) se ha desplazado incluyndola en un nico lugar, donde se garantiza que sera realizada independientemente de lo que suceda.

this.x = x; i 12 Tratamiento de errores mediante excepciones 837 Incluso en aquellos casos en que la excepcin no es capturada en el conjunto actual de clusulas catch. finallv se ejecutara antes de que el mecanismo de tratamiento de excepciones contine buscando una rutina de tratamiento adecuada en el siguiente nivel de orden superior: //: exceptions/AiwaysFinally.java // Finally siempre se ejecuta. import static net.mindview.util.Print.r class FourException extends Exception {} pubiic class AlwaysFinally ( pubiic static void main(String(] argsl ( print<"Entering first try block"); try { print("Entering second try block"); try | throw new FourException O .* ) finally { print("finally in 2nd try block");

) } catch(FourException e) ( System.out.printlnI "Caught FourException in lst try block*); ) finally { System.out.println{"finally in lst try block");

} J / Output:

this.x = x; i 12 Tratamiento de errores mediante excepciones 838 Entenng first try block Entering second try block finally in 2nd try block Caught FourException in lst try block finally in lst try block

///:-

La instruccin finally tambin se ejecutar en aquellas situaciones donde esten implicadas instrucciones break y continu. Observe que la clusula finally junto con las instrucciones break y continu etiquetadas elimina la necesidad de una instruccin goto en Java.

Ejercicio 13: (2) Modifique el Ejercicio 9 aadiendo una clusula finally Verifique que la clusula finally se jecuta,

incluso cuando se genera una excepcin NullPointerException.

this.x = x; i 12 Tratamiento de errores mediante excepciones 839 Ejercicio 14: (2) Demuestre que OnOffSwitch.java puede fallar, generando una excepcin RuntimeException dentro

del bloque In*.

Ejercicio 15: (2) Demuestre RuntimeException dentro del

que

WitbFinallv.java

no

falla,

generando

una

excepcin

bloque try. Utilizacin de finally durante la ejecucin de la instruccin return

Puesto que una clusula finally siempre se ejecuta, resulta posible volver desde mltiples puntos dentro de un mtodo sin dejar por ello de garantizar que se realicen las tareas de limpieza importantes: //: exceptlons/MultipleReturns.jav a import static net .mmdview.util. Print. * ; public class MultlpleReturns ( public static void f(int i) (

this.x = x; i 12 Tratamiento de errores mediante excepciones 840 print("Initialization that requires cleanup"); try { print ("Point 1").; if(i == 1) return; print("Point 2M); ifti -2] return; print I"Point 3"J; if(i 3) return; print("End"); return; } finally { print("Performing cleanup"); i

) public static void main(String(1 arga) { forfint i = 1; i <= 4; i+i1) f UJ ;

) ) / Output: Initialization that requires cleanup Point 1 Performing cleanup Initialization that requires cleanup Point 1 Point 2 Performing cleanup Initialization that requires cleanup Point 1 Point 2 Point 3 Performing cleanup

this.x = x; i 12 Tratamiento de errores mediante excepciones 841 Initialization that requires cleanup Point 1 Point 2 Point 3 End Performing cleanup *///:-

Podemos ver, en la salida del ejemplo, que no importa desde dnde volvamos, ya que siempre se ejecuta la clusula finally

Ejercicio 16: (2) Modifique rcusing/CADSystem.java para demostrar que si volvemos desde un pumo situado en la

mitad de una estructura try-finally se seguirn ejecutando adecuadamente las tareas de limpieza.

Ejercicio 17: (3) Modifique polymorpImm/Frog.juva para que utilice la estructura try-finally con el fin de garantizar

this.x = x; i 12 Tratamiento de errores mediante excepciones 842 que se lleven a cabo las tareas de limpieza y demuestre que esto funciona incluso si ejecutamos una instruccin return en mitad de la estructura try-finally Un error: la excepcin perdida

Lamentablemente, existe un fallo en la implementacin del mecanismo de excepciones de .lava. Aunque las excepciones son una indicacin de que se ha producido una crisis en el programa y nunca deberan ignorarse, resulta posible que una excepcin se pierda sin ms. listo sucede cuando se utiliza una configuracin concreta con una clusula finally: //: exceptions/LostMessage.j ava // Forma de perder una excepcin. class VerylmportantException extends Exception ( pubiic String toStringi. I { return "A very mportant exception! *;

) l class HoHumException extends Exception ( pubiic String toString) { return "A trivial exception";

this.x = x; i 12 Tratamiento de errores mediante excepciones 843 ) pubiic class LostMessage ( void f() throws VerylmportantException ( throw new VeryXmportantException();

) void diBposeO throws HoHumException ( throw new HoHumException(); i pubiic static void main(String11 arga) f try ( LootMescaae Im - new LostMeasage(1j try { lm.f ( ); } finally { lm.dispose () ,*

) ) catch(Excepcin e) ( i ) System.out.println(e) j

} /* Output: A trivial exception *///:-

this.x = x; i 12 Tratamiento de errores mediante excepciones 844 Podemos ver. analizando la salida, que no existe ninguna prueba de que se haya producido la excepcin YoryImportantE\cepton, que es simplemente sustituida por la excepcin HoHumException en la clusula finally. Se trata de un fallo importante. pue*to que implica que puede perderse completamente una excepcin, y adems puede perderse de una forma bastante ms sutil y difcil de detectar que en el ejemplo anterior. Por contraste, C++ comidera 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, como es el caso de dispose( ) en el ejemplo anterior, dentro de una clusula try-catch).

Una forma todava ms simple de perder una excepcin consiste en volver con return desde dentro de una clusula fnally: / f exceptions/ExceptionSilencer-java public class ExceptionSilencer ( public static void toain(StringU args) ( try ( throw new RuntimeException() ; ) finally { // La utilizacin de 'return' dentro de un bloque finally // har que se pierda la excepcin generada. return;

) ///:-

this.x = x; i 12 Tratamiento de errores mediante excepciones 845 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 LnstMessage.java para que la propia excepcin

HnlluinException sea sustituida por una tercera excepcin.

Ejercicio 19: (2) Solucione el problema de LostlMessage.java protegiendo la llamada contenida en la clusula finad) 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 funcione con la clase base funcionara tambin automticamente con cualquier objeto derivado de la clase base (lo cual, por supuesto, es un concepto fundamental dentro de la programacin orientada a objetos), incluyendo las excepciones.

this.x = x; i 12 Tratamiento de errores mediante excepciones 846 Este ejemplo ilustra los tipos de restricciones impuestas a las excepciones (en tiempo de compilacin): //: exceptions/Stormylnning.java // Los mtodos sustituidos slo pueden generar las excepciones 7 especificadas en sus versiones de la clase base, // o excepciones derivadas de las excepciones de la clase base. class BasebaliException extends Excepcin {) class Foul extends BasebaliException () class Strike extends BasebaliException {) abstract class Inmng ( public Inningu thrcws BasebaliException {) public void event() throws BasebaliException ( / / No tiene por qu generar nada

} public abstract void atBacO throws Strike, Foul; public void walkl) () //No genera ninguna excepcin comprobada

) class StormException extends Excepcin {) class RainedGut extends StormException {} class PopFoul extends Foul () interface Storm { public void eventO throws RamedOutj public void rainHardO throws RainedGut;

this.x = x; i 12 Tratamiento de errores mediante excepciones 847 ) public class Stormylnning extends Inning implements Storm ( // Se pueden aadir nuevas excepciones para los constructores. // pero es necesario tratar con las excepciones del constructor base: public Stormylnning| throws RainedOut. 3aseballException () pubiic Stormylnning iString 5 ) throws Foul, BaseballException () 1 1 Los mtodos normales deben adaptarse a la clase base: //l void walkll throws PopFoul {] // error de compilacin // Una interfaz 80 PUEDE aadir excepciones a los mtodos // existentes en la clase base: //I pubiic void evencO throws RainedOut (} // Si el mtodo no existe ya en la clase // base, la excepcin es vlida: pubiic void rainKardO throws RainedOut |) // Podemos definir no generar ninguna excepcin, // aun cuando la versin base lo haga: pubiic void event o {} // Les mtodos sustituidos pueden generar excepciones heredadas: pubiic void atBatiJ throws PopFoul () pubiic atatic void main(StringU args) { try { Stormylnning si = new Stormylnning(I; si.atBat(); ) catch1 PopFoul e) { System.out.printlnI * Pop foul"); | catch(RainedOut el { System.out.println <"Raned out")/ ) catch(BaseballException e | 1 System.out.println("Generic baseball exception"1;

// Strike no se genera en la versin derivada, try { // O^ sucede si generalizamos? Inning i * new Stormylnning () ;
i.

atBat () ;

// Hay que capturar las excepciones de la

this.x = x; i 12 Tratamiento de errores mediante excepciones 848 // versin del mtodo correspondiente a la clase base: } catchStrike e) { System.out.println("StrikeM)/ ) catchFoul e) { System.out.printlnfMFoul"); ) catch1 RainedOut e1 ( System.out .println ("Ramed out") ) catch(BaseballException e) ( System.out .println I "Generic baseball exception"),-

} I ///:-

tu 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 permite obligar al usuario a capturar cualquier excepcin que podamos aadir en las versiones sustituidas de event( ) La misma idea puede aplicarse a los mtodos abstractos como podemos ver en atBat().

this.x = x; i 12 Tratamiento de errores mediante excepciones 849 La interfaz Storm es interesante porque contiene un mtodo (event( )) que est definido en Inning, y otro mtodo que no

loest. Ambos mtodos generan un nuevo tipo de excepcin. RainedOut. Cuando Stormvlnning ampla (extends) Inning e implementa (implements) Storm. podemos ver que el mtodo eventt ) de Storm no puede cambiar la interfaz de excepciones de event() 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 rainllard( ), no existe ningn problema en cuanto a las excepciones que genere.

La restriccin relativa a las excepciones no se aplica a los constructores Podemos ver en Storm\ Inninu que un constructor puede generar todo aquello que desee, independientemente de lo que genere el constructor tic la clase base Sin embargo, puesto que siempre hay que invocar el constructor de la clase base de una forma o de otra (aqu se invoca el constructor predeterminado de manera automtica), el constructor de la clase derivada deber declarar todas las excepciones del constructor 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 Stormy Inning.* alk( ) no podr compilarse es que genera una excepcin mientras

this.x = x; i 12 Tratamiento de errores mediante excepciones 850 que Inning.* alk( ) no lo hace. Si se permitiera esto, entonces podramos escribir cdigo que invocara a Innm^.w alk( ) y que no tuviera que tratar ninguna excepcin, pero entonces, cuando efecturamos una sustitucin y empleramos un objeto de una clase derivada de Inning. podran generarse excepciones, con lo que nuestro cdigo fallara. Obligando a los mtodos de la clase derivada a adaptarse a las especificaciones de excepciones de los mtodos de la clase base, se mantiene la posibilidad de sustituir los objetos.

El mtodo sustituido event ) 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 si las genere De nuevo, no pasa nada por hacer esto, ya que no dejaran de funcionar aquellos programas que se hayan escrito hajo la suposicin de que la versin de la clase base genera excepciones. Podemos aplicar una lgica similar utBatt ). que genera PopFoul. una excepcin que deriva de la excepcin Foul generada por la versin de la clase base de atBat( ) De esta forma, si escribimos cdigo que funcione con Inning y que invoque atBat( ), deberemos capturar la excepcin Foul Puesto que Popl'oul deriva de Foul. la rutina de tratamiento de excepciones tambin permitir capturar PopFoul.

El ltimo punto de inters se encuentra en main( ). Aqu, podemos ver que, si estamos tratando con un objeto que sea exactamente del tipo StormyInning. el compilador nos obligara a capturar nicamente las excepciones que sean especificas de esa clase, pero si efectuamos una generalizacin al tipo base, entonces el compilador (correctamente) nos obligara a capturar las excepciones del tipo base Todas estas restricciones permiten obtener un cdigo de tratamiento de excepciones mucho ms robusto*.

Aunque es el compilador el que se encarga de imponer las especificaciones de excepciones en los casos de herencia, esas especificaciones de excepciones no forman 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 especificaciones 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 bastante de las reglas normales de herencia, segn las cuales un mtodo de la clase base deber tambin existir en la clase derivada. Dicho de otra forma, la interfaz de especificacin de excepciones*' de un

this.x = x; i 12 Tratamiento de errores mediante excepciones 851 mtodo concreto puede estrecharse durante la herencia y cuando se realizan sustituciones, pero lo que 110 puede es ensancharse; se trata.precisamente, de la regla opuesta a la que se uplica a la interfaz, de una clase durante la herencia.

Ejercicio 20: (3) Modifique Slorinylnning.java aadiendo un tipo excepcin l mpireArgumcnt \ 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 apropiadamente' La mayor parte de las veces, podemos estar razonablemente seguros, pero con los constructores existe un problema. El constructor sita los objetos en un estado inicial seguro, pero puede realizar algtina opcraeion (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 lleven a cabo apropiadamente listo 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

this.x = x; i 12 Tratamiento de errores mediante excepciones 852 finally lleva a cabo las tareas de limpieza todas las veces. Si un constructor falla en mitad de la ejecucin, puede que no haya tenido tiempo de crear alguna parte del objeto que ser limpiado en la clusula finally f I estndar ISO ( - - ha aadido unas restricciono *imilnn?>, que obligan a que las excepciones de Un inlodoa derivado* -can iguales u lus excepciones generadas por los mtodos de la clase base, o al menos a que demen de elltv ste o uno de los casos en los que O e* capaz de cumprobur las espe cificaciones de excepciones en tiempo de compilacin

En el siguiente ejemplo, se crea una clase denominada InputFile que abre un archivo y permite leerlo linca a linea. Utiliza las clases FileReader y BufferedReader de la biblioteca estndar E/S de Java que se analizar en el Capitulo 18. E/S Estas clases son lo suficientemente simples como para que el lector no tenga ningn problema a la hora de comprender los fundamentos de su utilizacin: //: exceptions/InputFile.java f / Hay que prestar a las excepciones en los constructores. import java.io.*? pubiic class InputFile ( prvate BufferedReader in; pubiic InputFile (String fr.ame) throws Exception ( try { in -= new BufferedReader (new FileReader < fname>) ; // Otro cdigo que pueda generar excepciones ) catchiFileNotFoundException e) ( System.out.printin("Could not open H * fname); // No estar abierto, por lo que no hay que cerrarlo, throw ef* ) catch(Exception e ) ( // Todas las dems excepciones deben cerrarlo try ( in.cise t J; ) catch(IOException e2) { i System.out.println("in.cise O unsuccessful" ) ;

throw e; // Regenerar ) finally ( // |ir No cerrarlo aqu!11

this.x = x; i 12 Tratamiento de errores mediante excepciones 853 }

) pubiic String getLine() ( String s; try ( s * in.readLine (); } catch(IOException e) ( throw new RuntimeException ("readLine () failed");

) return R;

} pubiic void dispose() ( try { in.cise O i System.out.println("dispose() successful"); ) catch(IOException e2) ( throw new RuntmeException{"in.cise(J failed"I;

this.x = x; i 12 Tratamiento de errores mediante excepciones 854 )

} ///:-

El constructor de InputFile toma un argumento String. que representa el nombre del archivo que queremos abrir. Dentro de un bloque try, crea un objeto FileReader utilizando el nombre del archivo. Un objeto FileReader no resulta particularmente til hasta que lo empleemos para crear otro objeto BufferedReader (para lectura con buffet). Uno de los beneficios de InputFile es que combina las dos acciones.

Si el constructor de FileReader falla, generar una excepcin FlleNotFoundException, que indicara que no se ha encontrado el archivo. Este es el nico caso en el cual no queremos cerrar el archivo, ya que no hemos llegado a poder abrirlo. Cualquier otra 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 FilcNotFoundFxception En dicho caso, normalmente, habr que descomponer las cosas en vanos bloques try). El meto-

this.x = x; i 12 Tratamiento de errores mediante excepciones 855 Jo close() puede generar una excepcin, asi que lo encerramos dentro de una clusula tr> y tratamos de capturar la excepcin an cuando ese mtodo se encuentre dentro del bloque de otra clusula catch: para el compilador de Java se trata simplemente de un par adicional de smbolos de llave. Despus de realizar las operaciones locales, la excepcin se vuelve a generar, lo cual resulta apropiado porque este constructor ha fallado \ no queremos que el mtodo que lia hecho la invocacin 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 close( ), ya que eso huria que el archivo se cerrara cuando el constructor completara su ejecucin. Lo que queremos es que el archivo contine abierto mientras dure la vida til del objeto InputFile

El mtodo gctLine() devuelve un objeto String que contiene la siguiente linea del archivo. Dicho mtodo invoca a readl inei ). que puede generar una excepcin, pero dicha excepcin es capturada, por lo que getl.ine( ) no genera excepcin alguna. Uno de los problemas de diseo relativos a las excepciones es el de si debemos tratar una excepcin completamente en este nivel, si slo debemos tratarla parcialmente y pasar la misma excepcin (u otra distinta) al nivel siguiente,

o si debemos pasar la excepcin directamente al siguiente nivel. Pasar la excepcin directamente, siempre que sea apropiado. puede simplificar bastante el programa. En nuestro caso, el mtodo getLne( ) convierte la excepcin al tipo RuntimeException para indicar que se lia producido un error de programacin.

this.x = x; i 12 Tratamiento de errores mediante excepciones 856 Fl mtodo disposc( ) debe ser llamado por el usuario cuando ya no se necesite el mtodo InputFile. Esto liar que se liberen los recursos del istema (como por ejemplo los descriptores de archivo) que estn siendo utilizados por los objetos BufferedReader y/o FileReader. Evidentemente, no queremos hacer esto hasta que hayamos terminado de utilizar el objeto InputFile Podramos pensar en incluir dicha funcionalidad en un mtodo rnalize( ). pero como hemos dicho en el Capitulo 5.1'metalizacin y limpieza, no siempre podemos estar seguros de que se vaya a llamar a finali/e( ) (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 (exceptuando las de memoria) no tienen lugar automticamente, por lo que es preciso informar a los programadores de clientes de que ellos son los responsables.

La forma 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: //: exceptions/Cieanup.java // Forma de garantizar la apropiada limpieza de un recurso. pubiic class Cleanup { pubiic static void main(String|] args) { try ( InputFile in = new InputFile{"Cleanup.java"); cry { String s ? int = 1; whileUs = in.getLine ()) l- nuil) / / Realizar aqui el procesamiento linea a linea... J catch(Exception e) ( System.out.println("Caught Exception in main*};
e.

printStaclcTrace (System.out) ;

) finally { in.dispose( );

this.x = x; i 12 Tratamiento de errores mediante excepciones 857 } catch(Exception el ( System.out.onntln"InputFile construction failed"!;

,J } / Output: dispose) successful *///:-

Examine cuidadosamente la lgica utilizada: la construccin del objeto InputFik* se encuentra en su propio bloque try Si dicha construccin falla, se entra la clusula catch externa y no se invoca el mtodo dispose(). Sin embargo, si la construccin tiene xito, entonces hav que asegurarse de que el objeto se limpie, por lo que inmediatamente despus de la construccin creamos un nuevo bloque In La clusula finally que lle\a a cabo las tareas de limpieza est asociada con el bloque

try interno: ile esia forma, la clusula finally no se ejecuta si la cons miccin falla, mientras que siempre se ejecuta si la construccin tiene xito.

this.x = x; i 12 Tratamiento de errores mediante excepciones 858 Esta tcnica general de limpie/a debe utilizarse an cuando el constructor no genere ninguna excepcin. La regla bsica es; justo despus de crear un objeto que requiera limpieza, incluya una estructura tr> -finally //: exceptions/Cleanuoldiom.java // Cada objeto eliminable debe estar seguido por try-finally class NeedsCleanup ( // Construction cant fail private static long counter = I; private final long id counter-*-*; public void dispose() { System.out.println("MeedsCleanup id " disocsed") ,*

i class ConstructionException extends Exception {) class NeedsCleanup2 extends NeedsCleanup { f t La construccin no puede fallar: public NeedsCleanuo2(I throws ConstructionException {} l public class Cleanupldiom ( public static void main(String[] args) ( // Seccin 1: NeedsCleanup ncl = new NeedsCleanup(); try (

// ) finally { ncl.dispose( ) )

this.x = x; i 12 Tratamiento de errores mediante excepciones 859 H Seccin 2: // Si la construccin no puede fallar, podemos agrupar ios objetos: NeedsCleanup nc2 = new NeedsCleanup(); NeedsCleanup nc3 ^ new NeedsCleanup(>; try {

// ... } finally { nc3.dispose(I ; // Orden inverso al de construccin nc2.dispose( ) ;

)
II

Seccin 3:

// Si la construccin puede fallar, hay que proteger cada uno: try { NeedsCleanup! nc4 - new NeedsCieanup2(); try ( NeedsCleanup2 nc5 * new NeedsCleanup2(); try { } finally ( nc5.dispose{ ) i ) catch(ConstructionException e) ( // constructor de nc5 System.out.printlnCe); ) finally { nc4.dispose()

i 12 Tratamiento de errores mediante excepciones 860 ;) catch(ConstructionException a) ( f t constructor de nc4 System.out .Dnntln(e) ;

) /* Output: NeedsCleanup disposed NeedsCleanup disposed NeedsCleanup disposed NeedsCleanup disposed NeedsCleanup disposed ///:1 3 2 5 4

Ed maint ). la seccin I nos resulta bastante sencilla de entender: incluimos una estructura try-finalh despus de un objeto eliminable. Si la construccin del objeto no puede fallar, no es necesario incluir ninguna clusula catch. F.n 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 constructores pueden fallar ;- que necesitan 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 try-finally para garantizar la limpieza.

i 12 Tratamiento de errores mediante excepciones 861 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.

Observ e que si disposc( ) puede generar una excepcin, entonces sern necesarios bloques tr> adicionales Bsicamente, lo que debemos hacer es pensar con cuidado en todas las posibilidades y protegemos frente a cada una.

Ejercicio 21: (2) Demuestre que un constructor de una clase derivada no puede capturar excepciones generadas por su

constructor de la clase base.

Ejercicio 22: (2) Cree una clase denominada FailingConstructor con un constructor que pueda fallar en mitad del pro

i 12 Tratamiento de errores mediante excepciones 862 ceso de construccin y generar una excepcin. En main( ). escriba el cdigo que permita protegerse apropiadamente frente a este fallo.

Ejercicio 23: (4) Aada una clase con un mtodo disposed ) al ejercicio anterior. Modifique FailingConstructor para

que el constructor 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 elimmable. Escriba ?l cdigo necesario para protegerse adecuadamente contra los fallos y verifique en main() que estn cubiertas todas las posibles situaciones de fallo.

Ejercicio 24: (3) Aada un mtodo dispose( ) a la clase FailingConstructor y escriba el cdigo necesario para utilizar

adecuadamente esta clase.

i 12 Tratamiento de errores mediante excepciones 863 Localizacin de excepciones

Cuando se genera una excepcin, el sistema de tratamiento de excepciones busca entre las rutinas de tratamiento "ms cercanas". 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.

Localizar la excepcin correcta no requiere que haya una correspondencia perfecta entre la excepcin y su rutina de tratamiento. Todo objeto de una clase derivada se corresponder con una rutina de tratamiento correspondiente a la clase base, como se muestra en este ejemplo: ! f : exceptions/Human.3 ava f f Captura de jerarquas de excepciones. class Annoyance extends Exception {} class Sneeze extends Annoyance {) public class Human { public stacic void main(String(] args) { // Capturar el tipo exacto: try ( throw new Sneeze(); ) catch(Sneeze s) { System.out.println("Caught Sneeze1*) ; ) cacch(Annoyance al { System.out.println("Caughc Annoyance");

i 12 Tratamiento de errores mediante excepciones 864 ) // Capturar el tipo base: try { throw new Sneeze( ) } cacch(Annoyance a) { System.out.println("Caught Annoyance" )

} ) /* 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 primera. 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, caichi Annoyance a) permitir capturar una 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 diente, siempre y cuando el cliente capture las excepciones de la clase base

i 12 Tratamiento de errores mediante excepciones 865 Si tratamos de "enmascarar" las excepciones de la clase derivada, incluyendo primero la clusula catch correspondiente a la clase base, como en el siguiente ejemplo: try { throw new Sneezel); } catch(Annoyance a) { // ... ) catch(Sneeze s) (

// ...

el compilador nos dar un mensaje de error, ya que ver que la clusula catch correspondiente a Sneeze nunca puede ejecutarse.

Ejercicio 25: (2) Cree una jerarqua de excepciones en tres niveles. Ahora cree una clase base A con un mtodo que

i 12 Tratamiento de errores mediante excepciones 866 genere una excepcin de la base de nuestra jerarqua I lerede 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 C de B fcn main(), cree un objeto C y generalcelo a A. invoque el mtodo a continuacin. Enfoques alternativos

Un sistema de tratamiento de excepciones es un mecanismo especial que permite a nuestros programas abandonar la ejecucin de la secuencia normal de instrucciones. Ese mecanismo especial se utiliza cuando tiene lugar una condicin excepcional. tal que la ejecucin normal ya no es posible 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 excepciones es porque la tcnica de gestionar cada posible condicin de error producida por cada llamada a funcin era demasiado onerosa, lo que hacia 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 motivaciones para desarrollar los sistemas de tratamiento de excepciones.

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 tratamiento de excepciones es quitar el cdigo de tratamiento de errores del punto en el que los errores se producen. Esto nos permite concentrarnos en lo que querednos conseguir en cada seccin del cdigo, dejando la manera de tratar con los problemas para una 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, permitiendo que una rutina de tratamiento d servicio a mltiples lugares 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

i 12 Tratamiento de errores mediante excepciones 867 resultado que no se traten ciertas excepciones: try {

tf ... hacer algo til } catch(ObligatorvException e) () // Glubi

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 compilador 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 rodo rastro de la misma una vez que ha sido capturada y se deja sin tratar. Puesio que el compilador nos fuerza a escribir cdigo desde el principio para tratar la excepcin, incluir una rutina de tratamiento vaca parece la solucin ms simple, aunque en realidad es lo peor que podemos hacer.

Horrorizado al darme cuenta de que yo haba hecho precisamente esto, en la segunda edicin del libro "conegf* el problema imprimiendo la traza de la pila dentro de la rutina 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 algunos 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.

i 12 Tratamiento de errores mediante excepciones 868 El tema parece bastante simple, pero no slo resulta complicado sino que tambin es motivo de controversia. Hay personas que sostienen firmemente los argumentos de ambos bandos y que piensan que la respuesta correcta (es decir, la suya) resulta tremendamente obvia. Ln mi opinin, la razn de que den estas posiciones tan vehementes es que resulta bastante obvia la \entaja que se obtiene al pasar de un lenguaje con un pobre tratamiento de los tipos, como el previo ANSI C a un lenguaje fuertemente tipado con tipos estticos (es decir, comprobados en tiempo de compilacin) como C++ o Java. Cuando se hace esa transicin (como hice yo mismo), las ventajas resultan tan evidentes que puede parecer que la comprobacin esttica de tipos es siempre la mejor respuesta a la mayora de los problemas. Mi espetan/.# COn las lineas 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 linea tremendamente difusa a partir de la cual esa comprobacin 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 PL 1 y Mesa, y posteriormente se incorporaron en CLU. Smalltalk. Modula-3, Ada, Eiffel, C++ Pythoo, Java y los lenguajes post-Java como Ruby y CU. El diseo de Java es similar a C +, excepto en aquellos lugares en los que los diseadores de Java pensaron que la tcnica usada en Oh- podra causar problemas

Para proporcionar a los programadores un marco de trabajo que estuvieran ms dispuestos a utilizar para el tratamiento y la recuperacin de errores, el sistema de tratamiento de excepciones se aadi a C+- bastante tarde en el proceso de estandarizacin, promovido por Bjame Stroustrup, el autor original del lenguaje. El modelo de las excepciones de C++ 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).

En su articulo pionero7 sobre el tema. Liskov y Snyder observaron que uno de los principales defectos de los lenguajes tipo C. que informan acerca de los errores de manera transitoria es que:

i 12 Tratamiento de errores mediante excepciones 869 "...inda inuiC acin debe ir seguida de una prueba incondicional para determinar nuil ha sido el resultado. Este requisito hace que se desanvllcn pivgrauuis difciles de leer v que probablemente tambin son poco eficientes, lo que tiende a desanimar a los programadores a la hora de sealizar y tratar 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 continan diciendo: "...si se requiere que se asocie el texto de una rutina de tratamiento a la invocacin que genera la excepcin, el resultado sern programas poco legibles en los que las expresiones estarn descompuestas debido a la presencia de las rutinas de tratamiento "

Siguiendo el enfoque adoptado en CLU. Stroustrup afirm, al disear las excepciones de C-H-. 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 C debido a que la cantidad y la colocacin de dicho cdigo en los programas era muy difcil de manejar y tenda a distraer del objetivo principal del programa, ( orno resultado. los programadores solan abordar el problema de la misma manera que en C. ignorando los errores en el cdigo y utilizando depuradores para localizar los problemas. Para usar las excepciones, haba que convencer a estos programadores de C de que escribieran cdigo "adicionar', que normalmente no escribiran. Por tanto, para hacer que pasen a adoptar una forma ms eficiente de tratar los errores, la cantidad de cdigo que esos programadores deben aadir * no debe ser excesiva Resulta importante tener presente este objetivo de diseo inicial a la hora de eliminar los efectos que las excepciones comprobada-s tienen en Javo.

C++ tom prestado de CLU una idea adicional; la especificacin de excepcin, mediante la cual se enuncian programticamente en la signatura del mtodo las excepciones que pueden generarse como resultado de la llamada al mtodo. La especificacin de excepciones tiene, en realidad, dos objetivos. Puede querer decir1 Puedo generar esta excepcin en mi cdigo, encrgate de tratarla". Pero tambin puede significar: Estoy ignorando esta excepcin que puede producirse como resultado de mi cdigo,

i 12 Tratamiento de errores mediante excepciones 870 encrgate de tratarla*' Hasta ahora, nos estamos centrando en la pane 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 especificacin de excepciones puede indicar.

En C-H-, la especificacin de excepciones no forma parte de la informacin 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 tuncion o un mtodo generan excepciones, entonces las versiones sobrecargadas o derivadas tambin debern generar esas excepciones A diferencia de Java, sin embargo, no >e realiza ninguna comprobacin en tiempo de compilacin para determinar 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 validacin s que se produce, pero slo en tiempo de ejecucin. Si se genera una excepcin que viola la especificacin de excepciones, el programa C-Hinvocar la funcin de la biblioteca estndar une\pccted( ).

Resulta interesante observar que. debido al uso de plantiJlas. las especificaciones de excepciones no se utilizan en absoluto en la biblioteca estndar de O En Java, existen una serie de restricciones que afectan a la forma en que pueden empicarse los genricos Java con las especificaciones de excepciones.

i 12 Tratamiento de errores mediante excepciones 871 Perspectivas

En primer lugar, merece la pena observar que es el lenguaje Java el que ha inventado las excepciones comprobadas (inspiradas claramente en las especificaciones de excepciones de C++ y en el hecho de que los programadores C t no suelen ocuparse de las mismas). Sin embargo, se trata de un experimento que ningn lenguaje subsiguiente ha incorporado Barbara l.inkov y Alan Snydcr. Ercvplittn ItiiriUlinx m CJ.l . Ililil rnmsaction* cm Software hngmecnng. Vol. SI -5, No. fi. Noviembre 1979. *stc artculo no est disponible cu Inicmel. sino sln en copia imprem, pur lo qne lenilr que encargar una copia n irnves de u biblioteca.

En segundo lugar, las excepciones comprobadas parecen ser algo evidentemente bueno" cuando se las contempla dentro de eiemplos de nivel introductorio y en pequeos programas Segn algunos autores, la* dificultades ms sutiles comienzan a aparecer en el momento en que los programas crecen de tamao. Por supuesto, el tamao de los programas no suele incrementarse de manera espectacular de la noche a la maana, sino que lo ms normal es que lo* programas vayan creciendo de tamao poco a poco. Los lenguajes que puedan no ser adecuados para proyectos de gran envergadura, se utilizan sin problema 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 rean va mente difciles, A eso es a lo que me referia al comentar que lo< 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 cuesiiones principales. Y esio es. en si mismo, un problema porque la mayora de los anlisis tienden a utilizar como ilustracin programas de pequeo tamao. Uno de los diseadores de C escribi que: "El examen tic programas de pequeo tamao nos lleva a la conclusin de que imponer el uso de especificaciones de excepciones podra mejorar tanto la productividad del desarroUador como la calidad de cdigo, pero la experiencia con los grandes proyectos de desarrollo softw are sugiere un resultado completamente

i 12 Tratamiento de errores mediante excepciones 872 distinto una menor productividad v un incremento en la calidad del cdigo que es, comii mucho, poco significativo. "x

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

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 requerira que se incluyeran especificaciones de excepcin para casi todas las funciones. hara que fuera necesario efectuar muchas recompilactones y dificultara la cooperacin t on el software escrito en otras lenguajes, Esto animara a los programadores a subvertir tos mecanismos de tratamiento de excepciones v a escribir cdigo espreo para suprimir las execciones. Proporcionara 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 UML DistUed, Refactoring y Analysis Parteras) escriba en cierta ocasin lo siguiente: "...en conjunto, ctva que las excepciones son buenas, pero las excepciones

i 12 Tratamiento de errores mediante excepciones 873 comprobadas en Java causan ms problemas de los que resuelven, **

Actualmente, lo que opino es que el paso ms importante dado por Ja\a fue unificar el modelo de informacin de errores, de modo que de todos los errores se informa utilizando excepciones. Esto no suceda en C*t, porque, debido a la compati- bilidad descendente con C. segua estando disponible el modelo de limitarse a ignorar los errores. Pero, cuando disponemos de un mecanismo de informe 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 modelo C-H- para que las excepciones fueran la nica forma de informar de los errores, la imposicin adicional relativa a las excepciones comprobadas puede que haya dejado de >er tan necesaria.

Ln 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 informacin proporcionada por otros programadores como mi experiencia directa25 con lenguajes que son ms dinmicos que estticos, me han hecho pensar que las mayores ventajas provienen en realidad de:

Indirectamente <on Smalltalk a trove* de conversaciones con muchos programadoro con experiencia en dicho lenguaje, directamente con Python ( uii'h* Pvthnn.myl
25

l: Kees Koster. diseador del Icnguujc CDL. citado por Bertrand Meyer, diseador del lenguaje Etfel. ii ini1.cll.com/rljfvl riJbm/rtghi

i 12 Tratamiento de errores mediante excepciones 874


1.

Un modelo unificado de informe de errores basado en excepciones, independientemente de si el programador esta obligado por el compilador a tratarlas.

2.

Mecanismos de comprobacin de tipos, independientemente de cundo tenia lugar esa comprobacin. Es decir, en tanto que se imponga im uso apropiado de los tipos, a menudo no importa si eso sucede en tiempo de compj. 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 naturaleza demasiado restrictiva del mecanismo esttico de tipos, como 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 reputacin quedar irremediablemente destruida, la civilizacin se vendr abajo y un mayor porcentaje de los proyectos de programacin fallarn. La creencia de que el compilador puede ser la salvacin de nuestro proyecto, al indicamos 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: 'i.MinJUew netfBookxfBettetJava, se hace un gran hincapi en el valor que tienen los procesos automatizados de construccin de programas 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 Je programacin es aqul que ayuda a los programadores a escribir buenos pw- gramas. Ningn lenguaje Je programacin poJr impedir siempre que sus usuarios escribar malos programas "

i 12 Tratamiento de errores mediante excepciones 875 En cualquier caso, la probabilidad de que las excepciones comprobadas sean eliminadas de Java parece muy baja. Seria 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 norma) de ser siempre compatible en sentido descendente: de hecho, casi todo el software de Sun se ejecuta en todo el hardware Sun. independientemente de lo antiguo que ste sea. Sin embargo, si llega 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 alternativas 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 paia Ice lili u (que o algo que veremos cmo hacer en el Capitulo 18. E/S), tenemos que abril y cenar un objclu FilelnputStream. que genera excepciones. En un programa simple, podemos hacer lo siguiente (podr ver esta tcnica utilizada en numerosos lugares de este libro): //t exceptions/MainException.java import java.io.*; publlc class Mainxception ( // Pasar todas las excepciones a La consola: public static void maln(String[1 args) throws Exception f // Abrir el archivo: FilelnputStream file = r.ew FilelnputStream l "MainException. java ) ; // Usar el archivo... / / Cerrar el archivo: flie.cise();

i 12 Tratamiento de errores mediante excepciones 876 ) ///:-

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 ra/ de todas las excepciones comprobadas. Pasando la excepcin a la consola, nos vemos liberados de la obligacin de incluir clusulas try-cateh dentro del cuerpo de main() (lamentablemente, la E S je archivo es significativamente ms compleja de lo que parece en este ejemplo, asi que no se anime en exceso hasta que haya ledo el Captulo 18. E'S)

Ejercicio 26: 11) Cambie la cadena de caracteres que especifica el nombre del archivo en MainEtception.ja\ a para pro

porcionar un nombre de archivo que no exista. Ejecute el programa y anote el resultado Conversin de las excepciones comprobadas en no comprobadas

Pasar una excepcin hacia el exterior desde niain( ) resulta cmodo cuando estamos escribiendo programas simples para nuestro propio consumo, pero no resulta til por regla general. El problema real surge cuando estamos escribiendo el cuerpo de un mtodo normal, V 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 quiero perderla ni imprimir simplemente un mensaje vanaP. Gracias al mecanismo de las excepciones encadenadas existe una solucin nueva y simple, que consiste en limitarse a "envolver" una excepcin comprobada dentro de otra de tipo RuntimeException pasndosela al constructor de RuntimcException, como en el siguiente ejemplo:

i 12 Tratamiento de errores mediante excepciones 877 try f // . . . hacer algo til | catchdDoRtKnowWhatToDoWithThlsCheckedExc eption e) ( throw new RuntimeException(e) ;

Esto parece una solucin 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 perdemos ninguna informacin relativa a la excepcin original.

Esta tcnica proporciona la opcin de ignorar la excepcin y dejarla progresar por la pila de llamadas, sin vemos obligados a escribir clusulas try-cateh y/o especificaciones de excepciones. Sin embargo, seguimos pudiendo capturar v tratar la excepcin especifica, usando get('ause( ). como se muestra a continuacin: / / exceptions/TumOffChecking. java t "Desactivacin" de excepciones comprobadas, import java.io.*; import static r.ec .mindview.utii .Pnnt. * class WrapCheckedSxception { void throwRnntimeExceptionlint type) { try ( switch(type) ( case 0:thrownewFileNotFoundException () , case i:thrownew ICExceptionl);

i 12 Tratamiento de errores mediante excepciones 878 case 2:thrownewRuntimeException("Where am I?12H); default: return;

> [ catch(Exception e) [ I I 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 throwRuntimeException() sin un bloque try // y de^ar que las excepciones de tipo RuntimeException

i 12 Tratamiento de errores mediante excepciones 879 // salgan del mtodo; wce.throwRuntimeExcepcin(3); // 0 podemos elegir capturar las excepciones: for(int i fl| i < 4; i*+) try ( ifli < 3J wce.throwRuntimeExceptio n(i) ; el se throw new SomeOtherExcept ion (I ; ) catch (SomeCtherException e) ( print("SomeOtherException: H * e) ; ) catch(RuntimeException r e ) ( try ( throw re.getCause ); } catch(FileNotFoundException e) ( print("FileNotFoundException: " * e> ; } catch HOException e) { print ("IOException: * e) ; } catch(Throwable e> { printl"Throwable: " + el;

>

} /* Output: FileNotFoundException: java.10 .FileNotFoundException IOException: java.io.IOException

i 12 Tratamiento de errores mediante excepciones 880 Throwable: java.lang.RuntimeException: Where am I? SomeOtherExcept ion: SomeOtherException ///r-

\VrapChcckedException.throwRuntimeException( ) contiene cdigo que genera diferentes tipos de excepciones. Estas se capturan y se envuelven dentro de objetos RuntimeException. asi que se convierten en la causa de dichas excepciones.

En TrnOffChecking, podemos ver que es posible invocar throw RuntimeException( ) sin ningn bloque try porque el mtodo no genera ninguna excepcin comprobada. Sin embargo, cuando estemos listos para capturar las excepciones, seguiremos 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 codigo incluido dentro del bloque try; en este caso, se captura primero SomeOtherExccptton. Finalmente, se captura RuntimeException y se genera con throw el resultado de getCausc( ) (la excepcin envuelta). Esto exirac las excepciones de origen, que pueden ser entonces tratadas en sus propias clusulas catch

La tcnica de envolver una excepcin comprobada en otra de tipo RuntimeException se utilizar siempre que sea apropiado a lo largo del resto del libro. Otra solucin consiste en crear nuestra propia subclase de RuntimeException. De esta forma, no es necesario capturarla, pero alguien puede hacerlo si asi lo desea.

i 12 Tratamiento de errores mediante excepciones 881 Ejercicio 27: (l)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 personalizadaherede de

RuntimeException. y muestre que el compilador nos permite no incluir el bloque try .

Ejercicio 29: (1)Modifique todos los tipos de excepcin de Stormylnning.java extiendan

de modo que

RuntimeException. y muestre que no son necesarias especificaciones de excepcin bloques try

ni

Elimine los comentarios *//!' y muestre cmo pueden compilarse los mtodos sin especificaciones.

i 12 Tratamiento de errores mediante excepciones 882 Ejercicio 30: (2)ModifiqueHuman.javademodoquelas Modifique excepciones hereden de RuntimeException

main( ) de modo que se utilice la tcnica de TurnOfTCheckJng.java para iraiar los diferentes tipos de excepciones. Directrices relativas a las excepciones

Utilice las excepciones para:

Tratar los problemas en el nivel apropiado (e\ ite capturar las excepciones a menos sepa qu hacer con ellas)
1.

que

2.

Corregir el problema e invocar de nuevo el mtodo que caus la excepcin.

i 12 Tratamiento de errores mediante excepciones 883


3.

Corregir las cosas y continuar, sin volver a ejecutar el mtodo.

4.

Calcular algunos resultados alternativos en lugar de aquellos que se supone que mtodo deba producir.

el

5.

Hacer lo que se pueda en el contexto actual y regenerar la mis/na 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

i 12 Tratamiento de errores mediante excepciones 884 complicadas, entonces ser muy molesto de utilizar).

Hacer que la biblioteca y el programa sean ms seguros (esto es una inversin a corto plazo de cara a la depuracin 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 (como las de E S. mencionadas anteriormente) 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 relativos a dicho cdigo en otro lugar. Y. aunque las excepciones se suelen explicar como herramientas que nos permiten informar acerca de los errores y tvc aperamos Je ellos en tiempo de ejecucin, no es tan claro con cuanta frecuencia se impleinenta ese aspecto de recuperacin", como tampoco esta muy claro si resulta siempre posible. Mi percepcin es que la recuperacin es posible en no ms del 10 por cierto de los casos, e incluso en esas situaciones solo consiste en devolver la pila a un estado estable conocido, mas 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 informe 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 informar de los errores de distintas manera o incluso no informar en absoluto. Disponer de un sistema coherente de informe 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 escribamos un fragmento de cdigo (siempre y cuando, no capturemos las excepciones para luego dejarlas sin tratar)

i 12 Tratamiento de errores mediante excepciones 885 Como podra ver en futuros captulos, al permitimos olvidamos de esta cuestin (aunque sea generando una excepcin de tipo Runtimelixception). los esfuerzos de diseo e implementacin pueden centrarse en otras cuestiones ms interesantes y complejas.

Puede encontrar tus soluciones a tus ejercicio seleccionados en el documento electrnico Thi Thinkiiig n Java Annotuu-d Sotuilon G tilde. disponible para la venta en *vww.Atindllm netCadenas de caracteres

1 3

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 utiliza ampliamente. En este captulo, vamos ;i examinar ms en detalle la que constituye, ciertamente, la clase mas 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, lo 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/Immutable.java iraport static net .mindview.util .Print. * ; public class Inmutable { public static String upease(String s) { recura 5 .toUpperCase();

) public static void main(StringI] args) ( String q * howdy"; print (q) / // howdy String qq = upease(); printqq): // HOWDY print(ql; // howdy

) ) / OUtpUt: howdy HOWDY howdy

*///:

Cuando se pasa q a upeaset ) se trata en realidad de una copia de la referencia a q El objeto al que esta referencia esui conectado permanece en una nica ubicacin fsica. Las referencias se copian a medida que se las pasa de un sitio a otro

Examinando la definicin de upeaset ). podemos ver que la referencia que se le pasa tiene un nombre s. y que dicha referencia existe slo mientras que se ejecuta el cuerpo de upcase( ). Cuando se completa upcase( ). la referencia local s desaparece. upcase( ) devuelve el resultado, que es la cadena original con todos los caracteres en mayscula Por supuesto, lo que .se devuelve en realidad es una referencia al resultado. Pero lo cierto es que la referencia que se devuelve apunta a un nuevo objeto, dejndose sin modificar el objeto al que apuntaba la referencia q original

Este comportamiento es normalmente el que deseamos. Suponga que escribimos:

String a * "asd* ; String x * Irnmutabls.upcaae\fl) 7

Realmente queremos que el mtodo upcase( ) m/>difique el argumento'.' Para el lector del cdigo, los argumentos suelen aparecer como fragmentos de informacin proporcionados al mtodo, no como algo que haya que modificar. Esta garanta es importante, ya que hace que el cdigo sea ms fcil de escribir y comprender Comparacin entre la sobrecarga de
26

+ y StringBuilder

Puesto que los objetos String son inmutables, podemos establecer tantos alias como queramos para un objeto String concreto Puesto que un 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, l'n ejemplo claro es el operador que est sobrecargado pura los objetos String La palabra "sobrecargado significa que hay una operacion a la que se le ha proporcionado un significado adicional cuando se la usa con una clase concreta (los operadores *+ y *+=' para objetos String son los nicos operadores sobrecargados en Java, y el lenguaje no permite que el programador sobrecargue ningn otro operador).

( ** permite al programador sobrecargar los operadores a voluntad C omo esto puede sci a menudo bastante complicado {i'ase el Capitulo 10 de Thmkmx in Ct+. 2* Edicin. Prentice Hall. 2000). los diseadores de Java consideraron que era una caracterstica "indeseable' que no habia que incluir en Java Resulta gracioso que al final tenmuaran ellos mismos recurriendo a la sobrecarga de operadores y. lo que lodavia resulta mas irnico, se du la circunstancia de que la sobrecarga de operadores resultara mucho ms fcil en Java que en C+ + Esto es lo que sucede en Python (ira1 wwM'Pviimti /i/yi y ('. que disponen de mecanismo tanto de depuracin de memoria como de sobrecarga *encilla de lo* operadores.
26

El operador nos permite concatenar cadenas de caracteres: //: stringa/Concatenarion.java putolic class Concatenation ( publlc static void main(String!J args) ( String mango * "mango"; String s = "abe" -* mango + "def" + 47; System.out.println/sJ j )

) / Output1 abemangodef4 7 ///:-

Queremos iratar de imaginar cul seria la forma en que este mecanismo funciona. 1 objeto String abe" podra tener un mtodo appentl ) que creara un nuveo objeto String que contuviera *abc* concatenado con el contenida de mango I I nuevo objeto String creara entonces otro objeto String que aadiera defcic.

fisto 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 posteriormente los mecanismos de depuracin de memona. Sospecho que los diseadores de Java intentaron en pnmer 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 codigo anicrior utilizando la herramienta javap. que est incluida en el JDK. He aqui la lnea de comandos necesaria: javap -c Concatenation

El indicador -c genera el cdigo intermedio JVM. Despus de quitar las partes que no nos interesan y editar un poco los resultados, he aqu el cdigo intermedio relevante public static void maintjava.lang.String[]); Code: Stack=2, Loca1s=3, ArgB_size=l 0: Idc #2; //String mango

Si tiene experiencia con el lenguaje ensamblador, puede que este cdigo le resulte familiar: las instrucciones como dup e invokevirtual son los equivalentes en la mquina virtual Java (JVM) al lenguaje ensamblador. Si nunca ha visto lenguaje ensamblador, no se preocupe: lo que hay que observar es que el compilador introduce la clase ja\ a.litug.StringBuiider No haba ninguna mencin de StringBuilder en el cdigo fuente, pero el compilador ha decidido utilizarlo por su cuenta, porque es tnucho ms eficiente

En este caso, el compilador crea un objeto StringBuilder para crear la cadena de caracteres s. y llama a appcnd( ) cuatro veces, una para cada uno de los fragmentos f inalmente, invoca a toStrng( ) para generar el resultado, que almacena (con astore_2) como *.

Antes de dar por supuesto que lo que hay que hacer es utilizar cadenas de caracteres por todas panes 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 codificacin de forma manual con StringBuilder / / : stnngs/WhitherStrlngBuilder. -Java pabilo class WhitherStringBulidex ( public String implicit(Stringti fields) ( String result = "*;

for(int i 0; 1 - fields.length; result += fieids[il; retara result:;

) pubiic String explicit(String(] fields) | StringBuilder result = new StringBuilder(}; far (nt i - O i < fields. length; i*-) result.append(feldsi]); return result.toString<); 1 } ///-.-

Ahora, si ejecutamos ja\ap -c WitherStringBuildcr. podemos ver el cdigo (simplificado) para los dos mtodos diferentes En primer lugar. impliciK ): public java.lang.String implicit(java.lang.String(1 I; Code:
0i

ldc #2; //String 2:

astore_2

3; 4: 5:

iconst_0 istore_3 iload 3

Observe las lineas 8: y 35:. que forman un bucle. La linea 8: realiza tina comparacin entera de tipo mayor o igual que" con los operandos de la pila y salta a la linea 38: cuando el bucle se ha terminado. La linea 35: es una instruccin de sallo que vuelve al principio del bucle, en la linea 5:. Lo ms importante que hay que observar es que la construccin del objeto StringBuilder liene lugar dentro de este bucle, lo que quiere decir que obtenemos un nuevo objeto StringBuilder cada vez que pasemos a travs del bucle.

Me aqu el cdigo intermedio correspondiente a explicit( ): public java.lang.String explicitjava.lang.Stringtl);

No slo es el cdigo de bucle ms cono y ms simple, sino que adems el mtodo slo crea un nico objeto StringBuilder La creacin de un objeto StringBuilder explcito tambin nos permite preasignar su tamao si disponemos de informacin adicional acerca de lo grande que debe ser. con lo cual no es necesario volver a reasignar constantemente el huffer

Por tanto, cuando creamos un mtodo toString( ). si las operaciones son lo suficientemente simples como para que el compilador pueda figurarse el slo cmo hacerlas, podemos generalmente confiar en que el compilador construir el resultado de una forma razonable. Pero si hay bucles, conviene utilizar explcitamente un objeto StringBuilder en el mtodo toStringt ). como se hace a continuacin: //: strxngs/UsingStringBuilderoava imporc java.til.*; public class UsingStringBuilder { public static Random rand = new Random(47); public String toStringO | StringBuilder result * new StringBuilder("f1M); forllnt 1 = 0; i < 25; ++) { result.append(rand.nextlnt(100)); result.append(", "); i result.delete(result.length)-2 , result.length()); result.appendi"]"J; return result.toString();

) public static void main(String[) args) ( UsingStringBuilder uab = new UsingStringBuilderO; i System.out.println(usb);

} /* Output: [56, 55, 93, 61, 61. 29, 68. 0, 22, 7, 88. 28, 51. 89, 9. 78, 98. 61, 20, 58, 16. 40,
11.

22. 41 ///:-

Observe que cada pane del resultado se aade con una instruccin appcnd( ). Si tratamos de seguir un atajo y hacer algo como appeud(a 4-" + c). el compilador saldr a la palestra y comenzar a construir de nuevo ms objetos StringBuilder

Si tiene duda acerca de que tcnica utilizar, siempre puede ejecutar javap para comprobar los resultados.

Aunque StringBuilder dispone de un conjunto completo de mtodos, incluyendo insert( ). replace( ), suhstring() e incluso reverse( ). los que generalmente se usan son appencK ) y toString( ). Observe el uso de delete( ) para eliminar la ltima coma y el ltimo espacio antes de aadir el corchete de cierre.

StringBuilder fue introducido en Java SE5 Antes de esta versin. Java utilizaba StringBuffer, que garantizaba la seguridad en lo que respecta a las hebras de programacin (vase el Capitulo 21. Concurrencia) y era. por tanto, significativamente ms caro en trminos de recursos de procesamiento. Por tanto, las operaciones de manejo de caracteres en Java SI:5 6 deberan ser ms rpidas. Ejercicio 1: (2)AnaliceSprink)erSystem.toStrinR( reusing/SprinklerSystem.java paradescubrir ) en siescribir el

mtodo toStrng( ) con un mtodo StringBuilder explcito permitira ahorrar operaciones de creacin de objetos StringBuilder

Recursin no intencionada

Puesto que los contenedores estndar Java tal igual que todas las dems clases) heredan en ltimo termino de Object. todos ellos contienen un mtodo toString( ). Este mtodo ha sido sustituido para que los contenedores puedan generar una representacin de tipo String de si mismos, incluyendo los objetos que almacenan. ArrayList.toString( ), por ejemplo, recorre los elementos del objeto ArrayLlsf y llama a toString( ) para cada uno de ellos: //: strings/ArrayListDisplay.java import generics.coffee.*; imporc J ava.ut i1.; public class ArrayListDisplay ( public static veid main(Stringti args) ( ArrayListcCoffee^ coffees = new ArrayListcCoffee>() ; for(Coffee c new Cof feeGenerator<10)) coffees.add<c); System.out.println(coffees);

) ) /* Output: IAmericano 0, Latte 1, Americano 2. Mocha 3. Mocha 4. Breve 5, Americano 6, Latte 7, Cappuccino 8. Cappuccmo 91 *///:-

Suponga que quisiramos que el mtodo toStringt > imprimiera la direccin de la clase. Parece que tendra bastante sentido hacer referencia simplemente a this //: strings/InfiniteRecursion.java // Recursin accidental.

// [RunByHand) import j ava.til.4; publc class InfiniteRecursion { public String toString() { i return " InfiniteRecursion addresa: " 4 this 4 "Vn";

public static void main(String(1 args) ( List<InfiniteRecursion> v ^ new ArrayList<lnfin_iteRecursion> () ; fortint i * 0; i < 10; 4>) v.addtnew InfmiteRecursion()); i System.out.Drintlnivj;

) ///<-

Si creamos un objeto InfmiteRecursion y luego lo imprimimos, obtendremos una secuencia muy larga de excepciones. Esto tambin se produce si colocamos los objetos InfmiteRecursion en un contenedor ArrayList e imprimimos dicho contenedor como aqu se muestra Lo que est sucediendo es una conversin automtica de tipos para las cadenas de caracteres Cuando decimos: "InfiniteRecursion address: H this

El compilador ve un objeto String seguido de un smbolo *+ y algo que no es un objeto String, por lo que trata de convertir this a String. El compilador realiza esta conversin llamando a toString( ) , que es lo que produce una llamada recursiva.

Si queremos imprimir la direccin del objeto, la solucin es llamar al mtodo toStrng() de Object. y hace precisamente eso. Por tanto, en lugar de especificar this. lo que tendramos que escribir es super.toString()

Ejercicio 2:

(1) Corrija el error de InflniteRecursion.java.

Operaciones con cadenas de caracteres

He aqui algunos de los mtodos bsicos disponibles para objetos String. Los mtodos sobrecargados se resumen en una nica fila:

Puede ver que iodo 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 devolver una referencia al objeto String original. Esto ahorra espacio de almacenamiento y recursos de procesamiento.

Los mtodos Strinu en ls que estn implicadas expresiones regulares se explican ms adelante en este capitulo.

Formateo de ia salida

L na de las caractersticas ms esperadas que ha sido finalmente incorporada a Java SE5 es el formateo de la salida al estilo de la instruccin printf( l de C. No slo permite esto simplificar el cdigo de salida, sino que tambin proporciona a los desabolladores Java una gran capacidad de control sobre el formato a la alineacin de esa salida.: printfO

La funcin printf( ) de C no ensambla las cadenas de caracteres en la forma en que lo hace Java, sino que toma una nica cadena de formato e inserta valores en ella, efectuando el formateo a medida que lo hace, lin lugar de utilizar el operador sobrecargado *+' (que el lenguaje ( no sobrecarga) para concatenar el texto entrecomillado y las variables. printf( ) utiliza marcadores especiales para indicar dnde deben insertarse los datos. Los argumentos que se insertan en la cadena de formato se indican a continuacin medame una lista separada por comas. Por ejemplo: printf("How L: (Id%f]\nM. x, y);

En tiempo de ejecucin, el valor de * se inserta en %d y el valor de y se inserta en %f. Estos contenedores se denominan especificadores defrmalo y. adems de decimos dnde se debe insertar el valor, tambin nos informan del tipo de variable que hay que insertar y de cmo hay que formatearla. Por ejemplo, el marcador %d* anterior dice que \ es un entero, mientras que *%f dice que y es un valor de punto flotante (float o double).

System.out.format()

Java SE5 introdujo el mtodo formai( ). disponible para los objetos PrinlStream o PrintW riler (de los que hablaremos ms en detalle en el ( aptulo 18. E S ) . entre los que se incluye Systcm.out F.l mtodo forinat( ) 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: //; strings/SimpleFormat.java public clase SimpleFormac { public static void maln (Stringl] args) { int x = 5; double y 5.332542; // A la antigua usanza: System.out .printlni MRow 1: [M -t x " " * y * J ) ; .// A la nueva usanza: System.out.format(MRcw 1:(%d%f]\n", x, y);

// O ' \luik Welsh lu ayudado cu la creacin de c-viii seccin, a>i coma en la seccin Anlisis de lu entrada ' ) System.out.printf("Row i: [%d %f]\n", x, y);

} /* Output: ROW 1: IS 5.332542] Row 1: 15 5.332542] Row 1: [5 5.3325423

Puede ver que formut( ) y printf< ) son equivalentes. En ambos casos, hay una nica cadena de formato, seguida de un argumento para cada especiPicador de formato. La clase Formatter

Toda la nueva funcionalidad de formateo de Java es gestionada por la clase Formatter del paquete java.til Podemos considerar Formatter como una especie de traductor que convierte la cadena de formato y los datos al resultado deseado. Cuando se crea un objeto Formatter, se le indica a donde queremos que se manden los resultados, pasando esa informacin al constructor: //: strings/Turtle.j ava import java.io.*; import java.util.*; public class Turtle ( private String name; private Formatter f; public Turtle(String name. Formatter I) { this.name = name; thi8.f * f;

) public void move(int x. int y) ( f.formatt"%3 The Turtle is at (%d,%d)\n", name, x, y)j

I public static void main(String[] aras I (

FrlntStream outAlias = System.out; Turtle tommy ^ new Turtle("Tommy, new Formatter(System.out)); Turtle terry = new Turtle(Terr y, new Formatter l out Alias) ) .* tommy.move(0 ,0); terry.move(4 ,8); tommy.move(3 ,4); terry.move(2 ,5|; tommy.move(3 ,3); terry.move(3 ,3);

) / Output:

*///:-

Toda la salida representada por tommy va a System.out mientras que la representa por terr> va a un alias de Systeni.out. El constructor est sobrecargado para admitir diversas ubicaciones de salida, pero las ms tiles son PrintStream (como en el ejemplo), OutputStrcam y File. Veremos ms detalles sobre esto en el Captulo IX. Entrada salida.

Ejercicio 3: (I) Modifique Turtle.java de modo que envie toda la salida a System.crr

El ejemplo anterior utili/a un nuevo especificador de formato, '%%' Este marcador indica un argumento de tipo String y es un ejemplo del tipo mas simple de especificador de formato: 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 formato ms elaborados. He aqui la sintaxis ms general. %Undice_argumento](indicadores}{anchural[.precisin]conversin

A menudo, ser necesario controlar el tamao mnimo de un campo. Esto puede realizarse especificando una anchura. El objeto Formalter 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 incluyendo *-* en la seccin de indicadores.

Lo contrario de la anchura es la precisin , que se utiliza para especificar un mximo A diferencia de la anchura, que es aplicable a todos los tipos de conversin de datos y se compona de la misma manera con cada uno de ellos,precisin nene un significado distinto para los diferentes tipos. Para las cadenas de caracteres, la precisin especifica el nmero mximo de caracteres del objeto String 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), efectuando un redondeo si hay mas dgitos o aadiendo ms ceros al final si hay pocos. Puesto que los enteros no tienen pane fraccionaria, precisin no es aplicable a ellos y se generar una excepcin si se utiliza el argumento de precisin con un tipo de conversin entero.

I I siguiente ejemplo utiliza especificadores de formato para imprimir una factura de la compra: //: strmgs/Receipt. java iraport java.til.; public class Receipt { prvate double total 0; prvate Formatter f = new Formarter(System.out); public void printTitleO { f.tormat "%-15s %5s HQsNn", "Item", nQty", "Price"!; f . forma t "%-15s %5s *10s\n", "---", \ ..") ;

) public void print IString ame, int gty, doubie price) ( f.forraatiM%-15.15s %5d %10.2f\nM. ame, qtyf price} total price;

1 public void printTotal t) ( i .format <"%-15s %5s %10-2f\n". "Tax", "*, totalQ.0S); f .forraat Pl-15s %5s U0s\n", M,\ Mt*. " f. formac I "%-15s %5s %10.2f\nf*, "Total", total 1.06); i public static void mam lString (1 args) ( Receipt receipt * new Receipt O? receipt.printTitle <) receipt.print(-Jack1s Magic Seans", 4, 4.25) receipt.printl"Princesa Peas, 3, 5.1); receipt.print<"Three Bears Porridge", 1, 14.29); receiot.printTotal{); );

Total

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 formato se copian simplemente con el fin de producir el espaciado apropiado

Ejercicio 4: (3)Modifique Receipr.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:

He aqu un eiemplo que muestra estas conversiones en accin: //-. strings/Conversion.j ava import iava,math. import java.util.; public class Conversion { public static void main(String[1 args) { Formatter f = new Formatter(System.out I ; char u = 'a* : System.out.printIn I"a = 'a'"J; f.format{"s h s\nM, u); // t . format Cd: %d\nM, u) j f.format I"c: lc\n", u); f.formati"b: %b\n", u); // f. format (*; %.f\n% u) ; // f. format Ce: %e\n, u) ; // f . format < "x; %x\n'*, u) ;

f.format(Mh: %h\n", u); int v * 121;

13 Cadenas de caracteres 909 System.out .printInf v 121") ;f. format {Md: %d\n", v ) ; f.formatiHc: %c\n", v ; f.format("b: %b\aw, v) ; f.format INs: %s\n", v) ; // f.format("f: %f\n", v); ft f.format(Me: %e\n", v); f.formati"x: %x\nn, v) ; f.format(Mh: *h\n", v); Higlnteger w = new Higlnteger("50000000000000") ; System.out.println! "w new Biglnteger!\w50000000000000\"> "1 ; f. format {"d: %d\n"# w) ; // f.format I"c: lc\rt", w) / . format <"bj *b\n", w) ; formate's.%s\n'\ w) ; f.formati"f: fcfVn", w);
I

f. ft

If f .format Ce: Ve\n", wj ? f.format("x: %x\n", w) ; f.format<"h: %h\nM. w) ; double x <= IV 9. 543 ; System.out.println(Hx = 179,543); // f.formatC'd: %d\n", x) ; ff f.format( "c: %c\n", x); f.format("b: %b\n", xi ; f .format Cs: %s\n", xi ; f.formatiMf: %f\nN, x) ; f.format("e: %e\nH, x)j ft f.format("x: %x\n", x); f .format ("h: *h\n" x) ; Conversion y = new Conversion 0 ; System.out.println<"y = new Conversion()"J; ft f. formatC'd: %d\n", y) ; // f.format{"c: %c\nH. y ; f .format ("b: %b\nn, y) ;
f.

format(Ms: %s\n", y);

// f.format("t: *f\n'\ y); tf f.format(Me: %e\nM, y); ff f.format("x: lx\n", y);


f.

format("hi Vh\nM, y) ;

boolean 2 = false; System.out.println( " z = false"); ft f.format{"d: %d\n", 2 ); ft f .format fc: \c\n*, 2 I ,* f .format ("b: *b\n", 2 ); format ("s.- %s\n", z) ; ft f.format("f: 4f\n". 2 ); ff f,format("e: %e\n", 2 ); ff f. format < "x : %x\n", 7. ) ;
f. f.

format Ih: %h\nu, 2 );

910 Piensa en Java j } I* Output: (Sample) u = a' S : a Ct a b: tru eh: 61 v = 12.1 d: 121 C: y b: true s: 121 X: 79 h: 79 w = new Biglnteger("50000000000000") d: 50000000000000 b: true 9: 50000000000000 x: 2d79883d2000 h: 8842ala*7 x = 179.543 b: true s: 179.543 f: 179.543000 e: l.795430e*02 b: lef462c y = new Conversin O b: true s: Conversion*9cabl6 h: 9cabl6 z - faise b: false s: false h: 4d5 ///;-

Las lincas 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 vlida para cualquier tipo de argumento, puede que no se comporte como cabra esperar Para las primitivas boolean o los objetos de tipo boolean, el resultado ser true o false, segn corresponda Sin embargo, para cualquier otro argumento, siempre que el tipo de argumento no sea nuil, el resultado ser siempre true. 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.

13 Cadenas de caracteres 911 Existen otros tipos de conversin y otras opciones de especificador de formato ms extraos. Puede consultarlos en la documentacin 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

mas compleja posible. Es decir, utilice todos los posibles especificadores de formato disponibles para dicho tipo de conversin. Strng.format()

Java SE5 tambin ha tomado prestado de C 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 forma ): //: strings/DacabaseExcep:: ion. java public class DatabaseException extends Excsption ( public DatabaseExceptionlint transactionID, int queryID. String messagel ( super(String.format("(t%d, q %d) %s", transactionlD, queryID, message));

912 Piensa en Java ) public static void main(String] args) ( try ( throw new DatabaseException(3. 7. "Write failed"); ) catch(Exception e) ( i ) } / Output: DatabaseException: it3. q7) Write failed ///:System.out.printin(elj

Enrre bastidores. todo lo que String.format() hace es instanciar el objeto Formatter y pasarle los argumentos que hayamos proporcionado, pero utilizar este mtodo puede resultar a menudo ms claro y ms fcil que hacerlo de forma 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 legible, utilizando Strin.format( ): //: net/mindview/util/Hex .java package net.nundview.util; import ) ava.lo.*; public class Hex {

13 Cadenas de caracteres 913 public static String format IbyteU data) ( StringBuilder result = new StringBuilder(); int n = 0 ; for tbyte b : data\ ( if (n % 16 == 0) result.append(String.format (W%05X: ", n) ) ; result.append(String.format I"%02X ", b)); n*+; iftn % 16 = 01 result.apoend(*\nn):

) result.append <" \nH); return result.toStrinq );

) public static void main I string[J aras) throws Exception ( iflargs.length = 0) // Comprobar mostrando este archivo de clase: System.out.printIn I forman BinaryFile.readl"Hex.class"))); else System.out.printIn I format(BinaryFile.readlnew File(args(0]I) ) ) } } / Output: (Sample)

914 Piensa en Java

Para abrir y leer el archivo binario, este programa presenta otra utilidad que se presentar en el Captulo 18. Entrada salida: net.mindview.util.BInaryFik* El mtodo ruad( ) devuelve el archivo completo como una matriz de tipo bytc

Ejercicio 6: (2) Cree una clase que contenga campos int long. float y duiible. Cree un mtodo to$tring( ) para esta

clase que utilice String.format( ). y demuestre que la clase funciona correctamente Expresiones regulares

Las expresiones regulares han sido durante mucho tiempo pane integrante de las utilidades estndar Unix como sed y awk, y de lenguajes como Python 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 anteriormente delegadas a las clases String, StringBuffer y StringTokenizer de Java, que disponan de funcionalidades relativamente simples si las comparamos con las expresiones regulares.

13 Cadenas de caracteres 915 Las expresiones regulares son herramientas de procesamiento de texto potentes y flexibles. Nos permiten 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 forma que deseemos. Aunque la sintaxis de las expresiones regulares puede resultar intimdame al principio, proporcionan un lenguaje compacto y dinmico que puede emplearse para resolver iodo tipo de tareas de procesamiento, comparacin, seleccin, edicin y verificacin de cadenas de una forma general Fundamentos bsicos

Una expresin regular es una forma de describir cadenas de caracteres en trminos generales, de modo que podemos decir: Si una cadena de caracteres contiene estos elementos, entonces se conesponde 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 intenogacin de la forma siguiente:

Para describir un entero, diremos que est compuesto de uno o ms dgitos. En las expresiones regulares, un dgito se describe mediante *\d\ Si tiene experiencia con las expresiones regulares en otros lenguajes, observara inmediatamente la diferencia en la forma de gestionar las barras inclinadas. En otros lenguajes. 'W significa: Quiero insertar una barra inclinada a la izquierda normal y comente (literal) en la expresin regular. No le asignes ningn significado especial". En Java, \\* significa: Estoy insertando una barra inclinada de expresin regular, por lo que el siguiente carcter tiene un significado especial Por ejemplo, si queremos indicar un dgito, la cadena de la expresin regular sera Wd\ Si deseamos insertar una barra inclinada literal, tendremos que escribir *\\\\\ Sin embargo, elementos tales como de nueva linea y de tabulacin utilizan una barra inclinada simple: *\n\t\

916 Piensa en Java Para indicar una o ms apariciones de la expresin precedente, se utiliza un smbolo *+* Por tanto, para decir posiblemente un signo menos seguido de uno o ms dgitos, escribiramos: -?\\d*

La forma ms simple de utilizar las expresiones regulares consiste en utilizar la funcionalidad incluida dentro Je la clase String Por ejemplo, podemos comprobar si un objeto String se conesponde con la expresin regular anterior //: strings/IntegerMatch.java publlc class IntegerMatch { public static void main(StringU args) { System.out.println("-1234".matches t"-?\\d+M)); System.out.println("5678".matches("-?\\d+M)); System.out.println<"*911".matches{"-?\\d4")>; ) ) / * O u t p u t : t r u e t r u e System.out .println (".matches ( (-1 \\+) ?\\d+) ) /

13 Cadenas de caracteres 917 f a l s e t r u e / / / : -

Las primeras dos expresiones se corresponden, pero la tercera comienza con un *+\ que es un nmero legitimo pero que no se ajusta a la expresin regular Por tanto, necesitamos una forma de decir puede comenzar con un + oun -**. En las expresiones regulares, los parntesis tienen el efecto de agrupar una expresin, y la barra vertical T significa OR (disyuncin). Por tanto.

f-|\\f)?

quiere decir que esta parte de la cadena de caracteres puede ser un o un o nada (debido al *?*). Puesto que el carcter *+* tiene un significado especial en las expresiones regulares, es necesario introducir la secuencia de escape '\V para que aparezca como un

918 Piensa en Java carcter normal dentro de la expresin.

Una herramienta til de expresiones regulares incorporada en String es split( ). que significa partir esta cadena de caracteres justo donde se produzcan las correspondencias con la expresin regular indicada* //: scringa/ Splittin g.java import java.uti l.*; public class Splitting ( public static String knights "Then, when you have found the shrubbery, you must " + "cut dewn the mightiest tree in the forest... " + "with... a herring!M; public static void split{String regex) ( System.out.printIn( i Arrays.toStrtna(knights.split (regex) j ) ;

public static void main(String[] args) ( split(" f f No tiene porqu contener caracteres regex split ("\\W-t-"); // Caracteres no pertenecientes a una palabra split ("n\\W+"); f f *n seguida de caracteres no f f pertenecientes a una palabra

13 Cadenas de caracteres 919 ) } / Output: [Then,, when, you, have, found, the, shrubbery,, you, must,cut,down,the, mightiest, tree, in, the, forest..., with..,, a, herring!) [Then, when, you, have, found, the, shrubbery, you. must, cut, down, the, mightiest, tree, in, the, foreBt, with, a, herring] IThe, dow, tree forest,., with... a herringU ///:whe, you have found the shrubbery, you must cut the mightiest i, the

En primer lugar, observe que puede utilizar caracteres normales como expresiones regulares, una expresin regular no tiene porque contener caracteres especiales, como podemos ver en la primera llamada a split< ). que simplemente efecta la particin de acuerdo con los espacios en blanco.

La segunda y tercera llamadas a splt( ) utilizan '\\V\ que representa caracteres que no pertenezcan a palabras (la versin en minsculas. *\w\ representa un carcter perteneciente a una palabra); podra ver que los signos de puntuacin han sido eliminados en el segundo caso. La tercera llamada a split( ) dice, la letra n seguida de uno o ms caracteres que no pertenezcan a palabras*. Podr ver que los patrones de divisin no aparecen en el resultado.

920 Piensa en Java Una versin sobrecargada de String.split( ) nos permite limitar el nmero de divisiones que hayan de producirse.

La ltima de las herramientas de expresiones regulares incorporada en String es la de sustitucin. Podemos sustituir la primera aparicin o todas ellas: //: strings/Replacing.java import static net.mindview.util.Print.; public class Replacing ( static String s * Splitting.knightsj public static void main(String I) args) ( print is.replaceFirst ("f\\w-M, "located*)) ; printIs.replaceAll("shrubberyltree[herrina","banana"));

) ) /* OUtpUt: Then, wben you have located the shrubbery, you must cut down the mightiest cree in the forest... with... a herrlngJ Then, when you have found the banana, you raust cut down the mightiest banana in the forest... with... a banana i ///:-

13 Cadenas de caracteres 921 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 sustituida por la palabra located.

La segunda expresin se corresponde con cualquiera de las tres palabras separadas por las barras verticales que representan la operacin OR. y sustituye todas las correspondencias que encuentra.

Ms adelante veremos que las expresiones regulares que no son de tipo Si ring disponen de herramientas 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: (5) Utilizando la documentacin de java.util.regex.Pattern como referencia, escriba y pruebe una expre

922 Piensa en Java sin regular de prueba que compruebe una frase para ver si comienza con una letra mayscula y termina con un punto.

Ejercicio 8:

(2) Divida la cadena Splitting.knii'hts por las palabras the" o you.

Ejercicio 9: (4) Utilizando la documentacin de java.util.regcx.Pattern como referencia, sustituya todas las vocales

de Split tn.knihts por guiones bajos. Creacin de expresiones regulares

Podemos comenzar a aprender expresiones regulares con un subconjunto de las estructuras posibles. En la documentacin del JL)K. correspondiente a la clase Pattern de ja\a.util.regex podra encontrar la lisia completa de las estructuras que pueden emplearse para construir las expresiones regulares.

13 Cadenas de caracteres 923

La potencia de las expresiones regulares comienza a hacerse patente cuando se definen clases de caracteres. He aqu algunas formas tpicas de crear clases de caracteres, junto con algunas clases predefinidas:

Lo que se muestra aqui es slo un ejemplo; consultando la pgina de documentacin del JDK correspondiente java.util.regcx.Pattern podr conocer todos los posibles patrones de expresiones regulares.

924 Piensa en Java

Por ejemplo, cada una de las siguientes expresiones permite localizar la secuencia de caracteres "Rudolph //*. strings/Rudolpk.java public class Rudolph j oublic static void main(String[] args) ( for(String pattem : new String ] ( Rudolph, "[rR]udolph", " [rR] [aeiouj [a-z]ol.M, " R. " )) System.out.orintin("Rudolph" .matches (pattem) );

) ) / O u t

13 Cadenas de caracteres 925 p u t : c r u e t r u e t r u e c r u e * / / / : -

Por supuesto, el objetivo no debe ser crear la expresin regular ms complicada sino la que sea ms simple y baste para realizar 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 anteriormente, para facilitar la escritura de nuevas expresiones regulares.

926 Piensa en Java Cuantificadores

Un cuantificadar 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 haya 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 establezca 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 tipo de cuantiftcador slo est disponible en Java (no en otros lenguajes) y es ms avanzado, por lo que es posible que no lo

13 Cadenas de caracteres 927 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 dichos estados intermedios, 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.

Recuerde que la expresin X* necesitar a menudo eneerrurse entre parntesis para que funcione de la forma deseada. Por ejemplo: abe-*podra parecer que se debera corresponder con la secuencia abe una o ms veces, y si la aplicamos a la cadena de entrada abeabeabe. obtendremos de hecho tres correspondencias. Sin embargo, lo que la expresin dice en realidades: localiza ab seguido de una o ms apariciones de *c\ Para buscar correspondencias con la cadena completa abe* una o ms veces, debemos decir

928 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 uitcrfaz denominada CharSequence establece una definicin generalizada de una secuencia de caracteres abstrada de las clase CharBuffer. String, StringBuffer <> StringBuilder interface CharSequence ( charAt{nt i I; length i ) subSequence(int start, int end); toString () ,* i

Dichas clases implementan esta interfaz. Muchas operaciones con expresiones regulares toman argumentos de tipo CharSequence

i i

929 Piensa en Java Pattern y Matcher

En general, lo que se hace es compilar objetos de expresin regular en lugar de emplear las utilidades String. que son bastante limitadas. Para ello, importamos java.til.regex. y luego compilamos una expresin regular utilizando el mtodo static Pattcrn.compile( ). Esto genera un objeto Pattern basado en su argumento String Para utilizar 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 .ufiLregex.Matcher). Por ejemplo, el mtodo rcplacoAil() sustituye todas las correspondencias por el argumento que se proporcione.

Vamos a ver un primer ejemplo: la clase siguiente puede utilizarse para probar expresiones regulares con una cadena de entrada El primer argumento de la lnea de comundos es la cadena de entrada en la que hay que buscar las correspondencias. seguida de una o ms expresiones regulares que haya que aplicar a la entrada En Unix/Linux, las expresiones regulares deben estar entrecomilladas en la linea de comandos. Este programa puede resultar til para probar expresiones regulares mientras las construimos con el fin de comprobar que esas expresiones establecen las correspondencias deseadas //; strings/TestRegularExpressaon.java // Permite probar con facilidad expresiones regulares. // (Args: abcabcabcdefabc Mabc+" "(abe)-*-1' "labe) (2.}" ) import ]ava.util.regex.; import static net.mindview.til.Print.*; public class TestRegularExpression { i i

930 Piensa en Java publlc 3tatic voi main (Strmg [J argsi ( iflargs.length < 2J { print("Usagei\njava TestRegularExpression H * "characterSequence reguiarExpression-**) ; System.exit(0);

} print <"Input: \H" -r args [01 "V"*); for(String arg : args) [ print l"Regular expression: \"" arg Pattern p * Pattern.compile(arg) ; Matcher m = p.matcher(args( 01) ; while Im.findO) { print l "Match \ * m m.groupl) * *\H at positions H * m.startt) * +(m.endt) 1)); ")j

} ) / Output: Input: "abcabcabcdefabe" Pe guiar expression: "ab-rabeabedefabe" i i

931 Piensa en Java Match "abcabcabcdeabcM at positions 0-14 Regular expression: "abe*" Match"abcM Match*abcH MatchuabcM at positions 0-2 at positions 3-5 at positions 6-8

Match "abe at positions 12-14 Regular expression: "(abe)1 Match "abeabeabe at positions 0-9 Match "abe0 at positions 12-14 Regular expression: " (abei (2, }' Match "abeabeabe" at positions 0-B

Un objetoPattern reprsenla la versin compilada expresinregular.C omo hemos visto enel ejemploanterior,

podemos utilizar el un objeto i i

mtodo niatcher() yla cadena de entrada Matcher a partir del objeto

para generar Pattern

932 Piensa en Java compilado. Pattern tambin tiene un mtodo esttico: static boolean matchesrstring regex, CharSeguence input)

para comprobar si regex se corresponde con el objeto input de tipo CharSequcncc utilizado como entrada, v un mtodo *plit( ) que genera una matriz de tipo String despus de descomponer la entrada segn las correspondencias establecidas con la expresin regular regex

Piulemos generar un objeto Matcher invocando Pattern.matcher( ) con la cadena de entrada como argumento. Despus el objeto Matcher se utiliza para acceder a los resultados, utilizando mtodos para evaluar si se establecen o no diferentes tipos de correspondencias: boolean matches l) boolean looking At() boolean find() boolean findin t startl

i i

933 Piensa en Java H mtodo matches! ) tendr xito si el patrn se corresponde con la cadena de entrada completa, mientras que looking A t( ) tendr xito si la cadena de entrada, comenzando por el principio, permite establecer una correpondencia con el patrn.

Ejercicio 10: ( 2) Para la frase Java now has expresiones regulares evale si las siguientes expresiones permitirn loca

lizar la correspondencia: AJava \Breg. n.w\s+h(a(is s?

8* S + S4} s(l}. s{0.3}

i i

934 Piensa en Java Ejercicio 11: (2) Aplique la expresin regular

<?i) II* aeiou])|(\s+[aeiou]))\w+? LaeiouJ\b a "Arline ate eight apples and one orange while Anita hadn't any" find() Matcher.find() puede utilizarse para descubrir mltiples correspondencias de patrn en el objeto C'harSequence al cual se aplique. Por ejemplo ://: strings/Finding.java import java.til.regex.*; iraport static net.mindview.til.Print.*; public class Finding ( pubiic static void main(StringtJ args) ( Matcher m = Patfeem.corapile(\Vw-n) .matcher("Evening ic full of the linnet*s wings"); while{m.find()) printnb(m .groupO - " w); print( ) ; int i * 0 ; while<m.fi nd(i)) { prmtnbm. group) + " "); i++ r

i i

935 Piensa en Java )

) ) /* Output: Evening is full of the linnet s wings Evening vening ening ning ing ng g is is s full full ull 11 1 of of f the che he e linnet linnet innet nnet net et t s s wings wings ings ngs gs s

///:-

El patrn *\\w+* dis ide la entrada en palabras. flncl( ) es como un iterador. que se desplaza hacia adelante a travs de la cadena de caracteres de entrada. Sin embargo, la segunda versin de fnd( ) puede aceptar un argumento entero que le dice cul es la posicin del carcter en el que debe comenzar la busqueda; esta versin reinicializa la posicin de bsqueda con el valor de su argumento, como puede ver analizando la salida. Grupos

i i

936 Piensa en Java Los grupos son expresiones regulares delimitadas por parntesis y a las que luego se puede hacer referencia utilizando su nmero de grupo. Ll grupo 0 indica la expresin completa, el grupo I es el primer grupo entre parntesis, etc. Por tanto, en ABC) )D

existen tres grupos: el grupo 0 es ABC'D. el grupo I es BC y el grupo 2 es C.

El objeto Matcher dispone de mtodos para proporcionamos informacin acerca de los grupos:

public int groupCount( ) devuelve el nmero de gnipo? que hay en el patrn F1 grupo 0 no se incluye dentro de este recuento.

i i

937 Piensa en Java public String group( ) devuelve el grupo 0 ta correspondencia completa) de la operacin anterior de establecimiento de correspondencias (por ejemplo. find( )).

public String grnup(int i) devuelve el numero de grupo indicado dentro de la operacin de establecimiento de correspondencias anterior. Si esa operacin ha tenido xito, pero el grupo especificado no se corresponde con ninguna parte de la cadena de entrada, devuelve el valor nuil.

public int start(int group) devuelve el ndice de inicio del grupo encontrado en la operacion anterior de establecimiento de correspondencias.

public int cnd( int group) devuelve el ndice del ltimo carcter, ms uno. del grupo encontrado en la anterior operacin de establecimiento de correspondencias.

He aqui un ejemplo: i i

938 Piensa en Java //: strings/Groups.java import java .til.regex.*; import static net.mindview.til.Print.*; public class Groups ( static public final String POEM = "Twas brilllg, and the slithy toves\nM + "Did gyre and gimble in the wabe.\n" * Ail mimsy were the borogoves, \n* * "And the mome raths outgrabe.\n\n + "Beware the Jabberwock, ray son,\n" t "The jaws that bite, the claws that catch.\n" * "Beware the Jublub bird, and shun\n" "The fruraious Bandersnatch.H; public static void mam (String [J args) ( Matcher m = Pattern.compile IM(?mj (\\S^)\\s-(l\\S+: I\\a* (\\S+Jj 5 ") . matc her( POEM ); whil e(m. find ()) ( for(int j 0; j <= m.groupCount() ? printnb (" [" * m. group i j} print<); " ] ") ;

i i

939 Piensa en Java i

) ) /* Output: [the siithy roves][the][slithy coves]Islithy][toves] [in the wabe.] (in] [the wabe.] [the] [wabe.] [were the [borogoves,] [mome raths [outgrabe.] borogoves,][were][the outgrabe][mome][raths borogoves,] [the]

outgrabe.][raths]

[Jabberwock, my son,][Jabberwock,][my son,][my][son,] [claws that catch.}[claws][that catch.][that][catch.] [bird, and shun] [bird,] land shun] [and] [shun] [The frumious Bandersnatch.][The][t rumious Bandersnatch.] [frumious][Bandersnatch.]

///;-

i i

940 Piensa en Java Este poema es la primera parte dc Jabbcrwocky". dc Lewis Carroll extrado del libro A travs del espejo. Puede ver que el patrn dc expresin regular tiene una serie de grupos entre parntesis, compuestos de cualquier nmero de caracteres que no sea de espaciado (\S+ ) seguido de cualquier nmero de caracteres de espaciado (*\s*K). El objetivo es capturar las tres ltimas palabras de cada lnea, el final de una linea est delimitado por V Sin embargo, el comportamiento normal consiste en hacer corresponder *$ con el final de la secuencia de entrada completa, por lo que es necesario decir explcitamente a la expresin regular que preste atencin a los caracteres de nueva lnea desde dentro de la entrada. Esro se consigue con el indicador de patrones (?m)* al principio dc la secuencia (los indicadores de patrones los veremos enseguida).

Ejercicio 12: (5) Modifique Groups.java para contar todas las palabras que no empiecen con una letra mayscula

start() y end()

Despus de una operacin de establecimiento de correspondencias que haya permitido encontrar al menos una correspondencia. start() devuelve el ndice de inicio de la correspondencia anterior, mientras que end( ) devuelve el ndice del ltimo carcter de la correspondencia ms uno. AI invocar start() o end() despues de una operacin de localizacin de correspondencias que no haya tenido xito (o antes de intentar una operacin de localizacin de correspondencias), se genera la excepcin lllegalStateF.xceptinn El siguiente programa tambin ilustra los mtodos matches( ) y loukin At( ' i i

941 Piensa en Java //: strinas/S tartEnd.j ava import java.util .regex.; import static net.mindv iew.util Print.*; ptiblic class StartEnd ( public static String input = "As long as there is injustice, whenever a\nrt "Targathian baby cries out, wherever a distress\n" * "signal sounds among the stars ... We'll be there.\n" + "This fine ship, and this fine crew ... An* + "Never give up] Never surrender!** ;private static class Display { private boolean regexPrinted = false; private String regex; Display(String regex) { this regex = regex; } void displayString message} { if(! regexPrinted) { print(regex); regexPrinted * true;

) print(message);

i i

942 Piensa en Java )

) static void examine(String s. String regex) ( Display d = new Display(regex); Pattern p * Pattern.compile(regex); Matcher m * p.matcher(si; while(m.find 0) d.display("find() '" 4 m.group() + start = "f m. start () * end = " -t m.end()); if(m. lookingAt()) //No reset Cl necessary d.display("lookingAt() start * m.start () M end * H + m.endO Jilt (m. matches ()) // No reset 0 necessary d.display I"matches 0 start = "

m. start () M end *- "

m. end ()) ;

} public static void main (String (] argsI { for (String in : input .split f"\n" M ( print ("input : " in); foriStrinq regex : new String[J("\\w*ere\\w*", H\\w*ever". HT\\w+M, "Never.*?!"}) examine(in, regex);

i i

943 Piensa en Java )

} } /* Output: input : As long as there is Injustice, whenever a \w#ere\w* find() there* start * 11 end * 16 \w*ever findO whenever' start 31 end 39 input : Targathian baby cries out, wherever a distress \w*ere\w* find() 'wherever' start a 27 end * 35 \w*ever findO 'wherever' 9tart 27 end * 35 T\w+ fir.dd 'Targathian'start = lookingAt() start = 0 end input : signal there. \w*ere\w sounds 0 10 the stars ... We'll be end = 10

among

find!) 'there' start =43 end * 48 input : This fine ship, and this fine crew ... T\w+ find() 'This' start = 0 end = 4 *3 lookingAt() start = 0 end \w*ever find() 'Never' start * 0 end * 5 findO Never* start * 15 end = 2 0lookingAt () start = 0 end - 5 Never.? t findO Never give up ' start = 0 end = 14 i i

input : Never give upl Never surrender!

944 Piensa en Java findl) Never surrender!' start - 15 end = 31 lookingAt() start 0 end * 14 matchesU start = 0 end = 31 ///:-

Observe que find() permite localizar la expresin regular en cualquier lugar de la entrada, mientras que lookingAU) y mat- ches() slo tienen xito en la bsqueda si la expresin regular se corresponde desde el principio de la entrada. Mientras que matches( ) solo tiene xito en la bsqueda si toda 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.

Ejercicio 13: (2) Modifique StartEnd.java para que utilice Groups.POEM como entrada, pero siga produciendo resul

tados positivos para find( ). lookingAK ) > matches ). Indicadores de Pattern

i i

945 Piensa en Java Hay un mtodo conipile() alternativo que acepta indicadores que afectan al comportamiento de bsqueda de correspondencias:

Pattern Pattern.compile(String regex. int fiag) donde flag puede ser una de las siguientes constantes de la clase Pattern:

4 No s por que dieran este nombre a dicho mtodo ni a que refiere ese nombre Pero resulta reconfrtame saber que quienquiera que sea que inventa c*o% nombres de i i

946 Piensa en Java mtodos tan poco intuitivos contina empleado en Sun y que su aparente poltica de no revisar los disertos de codigo sigue estando vigente. Perdn por el sa/carmo. pero es que este tipo de cosas empiezan a cansar despus de unos cuantos artos

De especial utilidad entre lodos estos indicadores son Pattera.CASE_INSE.NSrnVE. Pattern.MULTILlNE y Pattera.COMMENTS (que resulta til para mejorar la claridad y/o con propsitos de dcoumentacin). Observe que el comportamiento de la mayor parle de los indicadores puede obtenerse tambin insertando en la expresin regular los caracteres entre parntesis que se muestran en los indicadores de la tabla, justo antes del lugar donde se quiera que ese modo tenga efecto.

Tambin podemos combinar el efecto de estos y otros indicadores mediante una operacin **OR" (*)): //: strings/R eFlags.ja va import j ava.til. regex.; public class ReFlags { public static void main(StringU args) ( Pattem p * Pattern.compile("Ajava", Pattem .CASE_INSENSITI VE | Pattem.MUL7IL1NE) ; Matcher m = p.matcher( "java has regex\nJava has regex\n" JAVA has pretty gooa regular expressions\n" i i

947 Piensa en Java "Regular expressions are in Java"); while(m.find<)) System.out.orintln(m.group());

) ) / * O u t p u t : j a v a J a v a J A V A * / / / : i i

948 Piensa en Java Esto crea un patrn que se corresponder con las lineas que comiencen por java. Java. JAVA." etc., y que intentara buscar una correspondencia con cada linea que forme parte de un conjunto multilinea (correspondencias que comienzan al principio de la secuencia de caracteres y a continuacin de cada terminador de linea contenido dentro de la secuencia de caracteres). Observe que el mtodo group() slo devuelve la porcin con la que se ha establecido la correspondencia. split()

splt( ) divide una cadena de caracteres de entrada en una matriz de objetos String, utilizando como deliniitador la expresin regular. StringU split (CharSequence mput)

String t] split. (CharSequence input, Inc. limit.)

sta es una forma cmoda de descomponer el texto de entrada utilizando una frontera divisoria comn. //: strings/S plitDemo. java import java.til .regex.3 i i

949 Piensa en Java import java.til .*; import static net.mindview.util.Print.*; public class SplitBemo ( public static void main(StringU args) ( String input = "Thisl lunusual useJ lof exclamation! pomts"; print (Arrays. toStnng ( Pattem. compile ("11") .split (input))) ; // Hacer slo las tres primeras: prmt (Arrays. toString ( Pattem.compile(H11M) .solit (input. 3) )) ;

) } / Output: [This, unusual use, of exclamation, points) [This, unusual use, of exclamation! !points]

*///:-

i i

950 Piensa en Java A segunda forma de split( ) limita el nmero de divisiones que pueden tener lugar Ejercicio 14: (l) Escriba de nuevo SpIitDemo utilizando String.spliM ).
l

Operaciones de sustitucin

Las expresiones regulares resultan especialmente tiles para sustituir texto. lie aqui los mtodos disponibles:

replaceFIrst(String replacement) sustituye por replacement la primera pane que se corresponde de la cadena de caracteres.

replaceAII(String replacement) sustituye por replacement todas aquellas partes que se correspondan en la cadena de caracteres de entrada.

i i

951 Piensa en Java appendReplaccment(StnnjBuffer sbuf. String replacement) realiza sustituciones paso a paso en sbuf. en lugar de sustituir solo la primera o todas ellas, como sucede con replaceFrst( ) y replaceAlK ). respectivamente. Este es un mtodo mu\ importante, porque permite invocar mtodos y realizar otros tipos de procesamiento para generar la cadena de sustitucin replacement I replaceFirst( ) y replaceAlK ) slo pueden utilizar cadenas de caracteres fijas para la sustitucin). Con este mtodo, podemos separar los grupos mediante programa y crear potentes rutinas de sustitucin.

append Tall(StrlngBuffer sbuf. String replacement) se invoca despus de una o ms invocaciones del mtodo appendKcplacementt ) para copiar el resto de la cadena de caracteres de entrada.

He aqu El bloque al principio

un ejemplo que muestra el uso de todas las operaciones de sustitucin detexto comentado

del programa es extrado y procesado con expresiones regulares para usarlocomo entrada en el resto del ejemplo: / /: strings/TlieReplaceme nts . java import j ava.ut i1.regex.; import: i i

952 Piensa en Java net.mindview.utii.*; import static net.mindview.til.Pri nt.* ; /IHere'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. !*/ public class TheReplacements ( public static void main(String[] argsl throws Exception ( String s = TextFile . read ( TheReplaceraent . j avaM ) ; // Establecer correspondencia con el bloque de texto con // comentarios especiales mostrado anteriormente: Matcher mlnput * Pattern.compile<**/\\* t (. ) \\*/u. Pattern.DOTALL) . mat che r(s J; if{ mln put .fi nd< )) s = mlnput.group(1l; ff Capturado por parntesis ff Sustituir dos o ms espacios por un nico espacio: s s.replaceAllIM {2,)*, " *); // Eliminar dos o ms espacios al principio de cada // lnea. Hay que habilitar el modo MULTILNEA: s = s.replaceAll I " i i

953 Piensa en Java (?m)A print(s)f s = s . replaceFirst ( " [aeiouj ", '* (VOWEL1) H ) ; StringBuffer sbuf = new StrlngBuf fer () ,* Pattern p Pattern.compile("[aeioujH>; Matcher m = p.matcher(s); // Procesar la informacin de localizacin a medida ff que se realizan las sustituciones: while(m.findt)) m.appendReplaceraent (abuf. m.groupO ,toUpperCase()} ; // Insertar el resto del texto: en. appendTaii (sbuf) ; print(sbuf); "") ,*

] ) / Output ? Heres a block of text to use as input to the regular expression matcher. Note that we'll first extract the block of text by looklng for the special delimiters, then process the extracted block. H(V0WEL11 rE' 9 A blOck Of tExt tO UsE As InpUt tO thE rEgiAr ExprEssIOn mAtchEr. NOtE thAt wEll flrst ExtrAct thE blOck. Of tExt by lOOklng fOr thE spEclAl dElImltErs, thEn prOcEss thE ExtrActEd blOck.

i i

954 Piensa en Java *///*-

I: I archivo se abre y se lee utilizando la clase Text File de la biblioteca net.mndview.util (el cdigo correspondiente se mostrara en el Capitulo IX. E/S). El mtodo esttico read( ) lee el archivo completo y lo devuelve como objeto String mlnput se crea para corresponderse con todo el texto (observe los parntesis de agolpamiento) comprendido entre V*!* y *!*/. Despus, los conjuntos de ms de dos espacios seguidos se reducen a un nico espacio y se eliminan todos los espacios situados al principio de cada lnea (para hacer esto en todas las lineas y no slo al principio de la cadena de entrada, es necesario habilitar el modo multilnea) Estas dos sustituciones se realizan con el mtodo equivalente (pero ms cmodo, en este caso) replacc*All( ) que forma parte de String. 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 forma de un objeto Pattern

replaceFirst( ) slo sustituye la primera correspondencia que encuentre. Adems, las cadenas de sustitucin en replaceFirst( ) y rcplaceAll( ) son simplemente literales, por lo que si queremos realizar algn procesamiento en cada sustitucin. no nos sirven de ninguna ayuda. F:n dicho caso, tendremos que utilizar append Replacement( ), que nos permite escribir cualquier cdigo que queramos para reali/ar 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 construye el objeto sbuf resultante Normalmente, lo que haremos ser recorrer toda la entrada y hacer todas las sustituciones y luego invocar a append'Taih ), pero si queremos simular rcplaccFirst( ) (o una operacin de "sustitucin de n apariciones"), basta con hacer la sustitucin una vez y luego invocar appcndTaiK ) para insertar el resto de la informacin en sbuf

i i

955 Piensa en Java appendRcplacement( ) tambin nos permite hacer referencia directamente en la cadena de sustitucin a los grupos capturados mediante la notacin \ donde 'g* es el nmero de grupo. Sin embargo, este mtodo slo sirve para tareas de procesamiento 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 utilizando los mtodos reset( ): //: strings/Resetting.java import java.til.regex.; public class Resetting { public static void main(String{] args) throws Exception ( Matcher m = Pattern.compile("[frbj [aiu][ax]") .matcherlHfix the rug with bags"),* while (m. f i.nd<) ) System.out.print (m.groupO + M *); System.out.println( i; m.reset ("fix the rig with rags">; while(m.find()f Sy3 tem.0 ut.Drint (m.groupO + " ");

) ) / Output: i i f

956 Piensa en Java x r u g b a g f i x r i g r a o / / / : -

rcscf() sin ningn argumento hace que Matcher se site al principio de la secuencia actual.

i i

957 Piensa en Java Expresiones regulares y E/S en Java

La mayora de los ejemplos vistos hasta ahora mostraban la aplicacin de las expresiones regulares a cadenas de caracteres estticas. El siguiente ejemplo muestra una forma de aplicar expresiones regulares a la bsqueda de correspondencias en un archivo. Inspirada en la Utilidad grep de Unix, JGrep.java toma dos argumentos: un nombre de archivo y la expresin regular con la que se quiere buscar correspondencias. La salida muestra cada lnea en la que se ha detectado una correspondencia y la posicin o posiciones de las correspondencias dentro de la lnea: //: strmgs/JGrep. java // Una versin muy simple del programa "arep" // (Args: JGrep.java "\\b ISsctJ \\w-r" } import java.til.regex.*; import net . mindvew.util. * j public class JGrep ( public static void mam(StringU aras) throws Exception ( if(args.length < 2J ( System.out.printIn("Usage: java JGrep file regex")/ System.exit(0); i Pattern p=Pattern.compile(args[i 1) ; // Iterara travs de las aelarchivo de entrada: me ndex = 0; Matcher tti = p.matcher("") ; for(String line : new TextFilelargsfO]Jl ( m.reset(line); while(ra.findl)) i i

958 Piensa en Java System.out.println(index-f-f ": " -* m.groupf) * " : * ' + m. start O;

) } / Output: (Sample) 0 : s t r i n g e : 4 1 : s i m p l e : 1 0 2 : t h e : 2 i i

959 Piensa en Java 8 3 : S s c t : 2 6 4 : c l a s s : 7 5 s t a t i c : 9 6 : S t r i n g : 2 . 6 i i

960 Piensa en Java 7 : t h r o w s : 4 1 8 : S y s t e m : 6 9 : S y s t e m : 6 1 0 : c o m p i l e : i i

961 Piensa en Java 2 4 1 1 : t h r o u g h : 1 5 1 2 : t h e : 2 3 1 3 : t h e : 3 6 1 4 : S t r i n g i i

962 Piensa en Java : 9 1 5 : S y s t e m : 8 1 6 : s t a r t : 3 1 * / / / : -

El archivo se abre como un objeto net.mindvicw.uril.TextRle (del que hablaremos en el Capitulo 18, E/S). que lee las lineas del archivo en un contenedor tipo ArrayList Esto significa que podemos utilizar la sintaxis foreach para iterar a travs de las lineas almacenadas en el objeto Text Tile i i

963 Piensa en Java Aunque es posible crear un nuevo objeto Mntchcr dentro del bucle for. resulta ligeramente ms ptimo crear un objeto vaco Malcher lucra del bucle y utilizar el mtodo reset() para asignar cada linca de la entrada al objeto Matchcr. F.l resultado se analiza con find( )

Los argumentos de prueba abren el archivo JGrep.java para leerlo como entrada y buscan las palabras que comiencen por |Sscl|.

Puede aprender mucho mas acerca de las expresiones regulares en Masfering Regular Expressions. 2U Edicin, por Jefres F. F Friedl (OReilly, 2002). Hay tambin numerosas introducciones a las expresiones regulares en Internet, y tambin se puede encontrar a menudo informacin til en la documentacin de lenguajes tales como Perl y Python.

Ejercicio 15: (5) Modifique JGrep.java para aceptar indicadores como argumentos (por ejemplo. Pattcrn.CASE_ INSENSITIVE, Pattcrn.MULTII.INE).

i i

964 Piensa en Java Ejercicio 16: (5) Modifique JGrep.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 lodos los archivos de directorio) Consejo: puede generar una lista de nombres de archivo con Filen tiles - new File (" .") .listFiles H ;

Ejercicio 17: (8) Escriba un programa que lea un archivo de cdigo fuente Java (tendr que proporcionar el nombre del

archivo en la lnea de comandos) y muestre todos los comntanos

Ejercicio 18: (8) Escrba un programa que lea un archivo de cdigo fuente Java (tendr que proporcionar el nombre del i i

965 Piensa en Java archivo en la linea 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 iodos los nombres de clases utilizados en un programa concreto. Anlisis de la entrada

Hasta ahora, resultaba relativamente complicado leer datos de un archivo de lexto legible o desde la entrada estndar. La solucin usual consiste en leer una linea de texto, extraer los elementos y luego utilizar los diversos mtodos de anlisis sintctico de Integer. Double, etc.. para analizar los dato>. //i strings/S impleRead .java irnport java.io.* ? i i

966 Piensa en Java public class SimpleRead { public static BufteredReader input - new BufferedReader( new stnngReader {"Sir Robn o camelot\n22 1 .61803}) j public static void mam iString U args) { try ( System.out.printIn("What is your name?"},* String name input. readLmet); System.out.println(name) ; System.out .printin( How old are you? What is your favorite double?*); System.out.println("(input: <age> <double>J"); String numbers =- input.readLine(); System.out.printingnumbers); String[] numArray = numbers.split I" "); lnt age = Integer.parselnt(numA rray01 )/ double favorite = Double.parseDouble(nu mArray[1]*; System.out.format("Hi %s.\n", name.i ; System.cut.format\"In 5 years you will be age > 5}; System.out - format("My favorite double is %f.", favorite / 2)/ ) catch(IOException e> ( System e:r .pnntin i "I/O exception**);

i i

967 Piensa en Java )

) ) /* OUtpUt; What is your name? Sir Robin of Camelot How old are you? What is your favorite double? (input: <age> <doubie>) 22 1.61803 HI Sir Robin of Camelot. In 5 years you will be 27. My favorite double is 0.809015.

*///-

El campo input utiliza clases de java.io. que no vamos a presentar oficialmente hasta el Capitulo 18. E/S. Un objeto SlringReader transforma un dato de tipo String en un flujo de datos legible v este objeto se emplea para crear un objeto Buffered Reader porque Buffered Reader tiene un mtodo readl.ine( ). 11 resultado es que el objeto input puede i i

968 Piensa en Java leerse de linea en linea, como si fuera la entrada estndar procedente de la consola.

readLine< ) se utiliza para obtener la cadena de caracteres correspondiente a cada linea de entrada Resulta bastante cmodo de utilizar 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 linea, las cosas empiezan a complicarse: la linea debe dividirse para poder analizar cada dato de entrada por separado. Aqui. la divisin tiene lugar al crear numArray. pero observe que el mtodo split( ) fue introducido en J2SE1 4. por lo que en las versiones anteriores era necesario hacer alguna otra cosa.

La clase Scanner, aadida en Java SE5. elimina buena parte de la complejidad relacionada con el anlisis de la entrada //: 3trings/Be tterRead.j ava import java.util. *; public class BetterRead { public static void main(Stringti args) ( Scanner stdin = new Scanner (SimpleRead. input I ,* System, out .printing 'What is your name?"),* String name = stdin.nextLine(); System.out.println(namel; System.out.println * "How old are you? What is your favorite double?); i i

969 Piensa en Java System.out.println(w(input: <aae> <double>)"l; int age * stdin.nextInt(J double favorite = otdin.nextDoublei}/ System.out.printlntage); System.out.println(favorite); System.out.format{"Hi ts.\nH, name); System.out. format ("In 5 years you will be t?d.\nM. age 5); System, out. format"My favorite double is *.**, favorite / 2 \/

| / Output: What is your name? Sir Robin of Camelot How old are you? What is your favorite double? (input; <age? <double>) 22 1.61803 Hi Sir Robin of Camelot. In 5 years you will be 27. My favonte double is 0.8Q901S.

*///:-

i i

970 Piensa en Java Fl constructor de Scanner admite casi cualquier tipo de objeto de entrada, incluido el objeto File (que tambin veremos en el C apitulo 18. E/S). un objeto InputStream. un objeto String o. como en este caso un objeto Readable. que es ima nter- faz introducida en Java SE5 para describir algo que dispone de un mtodo read( f El objeto BuTeredReader del ejemplo anterior cae dentro de esta categora.

Con Scanner, los pasos de entrada, extraccin de elementos y anlisis sintctico estn mplementados mediante diferentes tipos de mtodos de "continuacin. Un mtodo nexf( ) devuelve el siguiente elemento String \ existen elementos similares para todos los tipos primitivos (excepto char). asi como pani BigDecimal y Biglnteger Iodos estos mtodos se Moquean, lo que significa que slo terminan despus de que haya un elemento de datos completo disponible como entrada Tambin existen los mtodos hasNext correspondientes que devuelven truc si el siguiente elemento de entrada es del tipo correcto.

Una interesante diferencia entre los dos ejemplos anteriores es la falta de un bloque trv para las excecpiones lOKxception en BetterRead.ja\a. Una de las suposiciones hechas por el objeto Scanner es que lOException marca el final de la entrada. por lo que estas excepciones son capturadas > hechas desaparecer por el objeto Scanner. Sin embargo, la excepcin ms reciente est disponible a travs del mtodo ioException( ). por lo que podemos examinarla en caso necesario.

Ejercicio 20: (2) Cree una clase que contenga campos int, long, float. doubl y String. Cree un constructor para esta i i

971 Piensa en Java clase que tenga un nico argumento String. y analice dicha cadena de caracteres para rellenar los diferentes campos. Aada un mtodo toString( ) y demuestre que la clase funciona correctamente. Delimitadores de Scanner

De manera predeterminada, un objeto Scanner di\ ide los elementos de entrada segn los caracteres de espaciado, pero tambin podemos especificar nuestro patrn delimitador en forma de una expresin regular //: strings/ScannerDelimit er.java import java.til.; public class ScannerDellmiter ( public static void mam (String [J aras) { Scanner scanner = new Scanner("12, 42, 78, 99, 42 M); scanner.useDelimiter("\\s* . Wb") ; whi le{ scanner.hasNexnInt t) ) j System.out.printInlscanner.nextInt(J J;

} / Output: 12 42 78 9 42

i i

972 Piensa en Java ///:

liste ejemplo utiliza comas t rodeadas por cantidades arbitrarias de espacios) como los delimitadores a la hora de leer la cadena de caracteres dada Esta misma tcnica puede utilizarse para leer desde archivos delimitados por comas. Adems del metodo uscDelimiterO. que sirve para fijar el patrn de delimitacin, existe tambin otro mtodo denominado delimiter que devuelve el objeto Pattern que est siendo actualmente utilizado como delimitador Anlisis con expresiones regulares

Adems de analizaren busca de tipos predefinidos, tambin podemos realizar el anlisis empleando nuestros propios patrones definidos por el usuario, lo cual resulta muy til a la hora de analizar datos ms complejos. El siguiente ejemplo analiza un archivo de registro generado por un cortafuegos en busca de datos que revelen potenciales amenazas. //: strings/ThreatAnalyzer .java import java.til,regex.; import java.til.; public class ThreatAnaiy zer ) static String threatData "58.27.82.161#02/10/2005\nM * i i

973 Piensa en Java "204.45.234 . 4 0#02/11/2005\n,f t "58.27.82.161S02/ll/2005\n" + "58.27.B2.161*02/12/2005\nH -58.27.82.16102/12/2005\n, + "[Next log section with different data format)"; public static void main(StringI] args) { Scanner scanner = new Scanner(threatDatai ; String pattern = w(\\d+ [.]\\d+1.J\\d+t.J\\d+)$" 4 H(\\d(2}/\\d{2)/\\d{4))M; while(scanner.hasNext(pattern) ) ( scanner.next(pattern); MatchResult match = scanner matchO; String ip = match.group(1/; String date match.group(2); System.out.format("Threat on %s from %s\n", daterip);

J /* Output: Threat on 02/10/2005 from 58.27.82.161 Threat on 02/11/2005 from 204.45.234.40 Threat on 02/11/2005 from 58.27.82.161 Threat on 02/12/2005 from 5B.27.82.161 Threat on 02/12/2005 from 58.27.82.161 *///:i i

974 Piensa en Java Cuando utilizamos ne\t( ) con un patrn especifico, se trata de hacer corresponder dicho patrn con el siguiente elemento de entrada. El resultado es devuelto por el mtodo match( ) y. como podemos ver en el ejemplo, el mecanismo funciona exactamente igual que las bsquedas con expresiones regulares que hemos visto anteriormente.

Existe un problema a la hora de analizar con expresiones regulares. El patrn se hace corresponder nicamente con el siguiente elemento de entrada, por lo que si el patrn contiene un del imitador nunca se encontrar una correspondencia StringTokenizer

Antes de que hubiera disponibles expresiones regulares (en J2SE1.4) o la clase Scanner (en Java SE5), la forma de dividir una cadena en sus panes componentes era extraer los elementos con StringTokenizer. Pero ahora resulta mucho ms fcil y ms sucinto hacer lo mismo con expresiones regulares o la clase Scanner. He aqui una comparacin simple de StringTokenizer con las otras dos tcnicas //: strings/ReplacingStri ngTokenizer.java import java.til.*; public class ReplacingStringTokemz er ( public static void main(String{] args ( String input * "But I'm not dead yet! 1 feel happyiw; StringTokenizer stoke - new StringTokenizer(input) ; while(stoke.hasMoreElements()} i i

975 Piensa en Java System.out .print (stoke .nextToken () System.out.println(); System.out.println(Arrays.toString(input.split(" ")')); Scanner scanner = new Scanner(input); while(scanner.hasNext()) System.out.print(scanner.next() M ");

) ) / Output: But I'm not dead yeti I feel, happy I [But, Im, not, dead, yetl, I, Ceel. happvr] But I'm not dead yetj I feel happyl ///:-

Con las expresiones regulares o con los objetos Scanuer. tambin puede dividirse una cadena en sus partes componentes utilizando patrones ms complejos, cosa que resulta difcil de realizar con StringTokenizer Podemos decir, sin mucho temor a equivocamos que StringTokenizer est obsoleto

i i

976 Piensa en Java Resumen

En el pasado, el soporte que Java tenia para la manipulacin de cadenas de caracteres era rudimentario, pero en las ediciones recientes del lenguaje hemos podido ver cmo se han ido adoptando funcionalidades ms sofisticadas, copiadas de otros lenguajes. Actualmente, el soporte para cadenas de caracteres es razonablemente completo, aunque en ocasiones es necesario prestar algo de atencin a los detalles de eficiencia, como por ejemplo haciendo un uso apropiado de StringBuilder Puede encontrar la> soluciones j lo& ejercicios seleccionado* en el documento electrnico Th? Ihinking in Jovti Annot.n,\i Sn!uiton (hiti. disponihlc para la vana en MlmHiev.nc

i i

tInformacin de tipos

1 4 El mecanismo RTTI (Runtimr tvpe infonnaticnu informacin de tipos en tiempo de ejecucin) nos permite averiguar y utilizar la informacin acerca de los tipos de los datos mientras un programa se est ejecutando.

Este mecanismo nos libera de la restriccin de efectuar operaciones orientadas a tipo* nicamente en tiempo de compilacin. y permite construir algunos programas de gran potencia. La necesidad de RTTI permite descubrir una gran cantidad de cuestiones del diseo 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 capitulo se examinan las formas en que Java permite averiguar la informacin acerca de los objetos y las clases en tiempo de ejecucin. Estos mecanismos adoptan dos formas diferentes- RTTI tradicional, que supone que tenemos todos los tipos disponibles en tiempo de compilacin y el mecanismos de reflexin que permite 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 > Triangle

Se trata de un diagrama de jerarqua de clases tpico, con la clase base en la parte superior y las clases derivadas distribuyndose en sentido descendente. El objetivo normal de la programacin orientada a objetos es que el cdigo manipule referencias i tipo base ten este caso. Shape). de modo que si decidimos ampliar el programa aadiendo una nueva clase (como por ejemplo Rhomhoid. derivada de Shape), el grueso del cdigo no se vea afectado. En este ejemplo, el mtodo de acoplamiento dinmico de la interfaz Shape es draw( }. por lo que la intencin es que el programador de clientes invoque a drau( ) a travs de una referencia genrica a Shape. En todas las clases derivadas, el mtodo drau( ) se sustituye y, puesto que es un mtodo con acoplamiento dinmico, obtendremos el comportamiento adecuado an cuando se invoque al mtodo sustituto a travs de una referencia genrica a Shape Esto es, ni ms ni menos, polimorfismo

Por lano, lo que hacemos en general es crear un objeto especifico (Circle. Square o Triangle). generalizarlo a Shape (olvidando el tipo especifico de objeto) y utilizar esa referencia annima a Shape en el resto del programa

Podemos codificar la jerarqua Shape de la siguiente manera

//: typeinfo/Shapes.java import java.util.; aJbstract class Shape ( void draw() ( System.out.println Ithis H.draw()")? ) abstract public String toStringO; i class Circle extends Shape ( pubiic String toStringt) ( return Circle*; )

) class Square extends Shape ( public String toStringO { return "Square"; )

} class Triangle extends Shape ( public String toStringO | return "Triangle"; f

) public class Shapes ( public static void raain(String H argsJ { List<Shape> shapeList * Arrays.asList(

new Circle(), new Square (), new Triangle)

); for(Shape shape : shapeListi shape.draw );

1 ) / Output; Circle.draw(1 Square. draw(J Triangle.draw0 ///:-

La clase base contiene un mtodo draw( ) que utiliza indirectamente toString( ) para imprimir un tdcntifcador de la clase, pasando chis a System.out.println( ) (observe que toStringt ) se declara como abstracto para obligar a las clases herederas a sustituirlo, y para impedir la instantacin de un objeto Shape simple). Si un objeto aparece en una expresin de concatenacin de cadenas (donde estn involucrados *+ y objetos String). se invoca automticamente el mtodo toStringt ) para general una representacin de tipo String de dicho objeto Cada una de las clases derivadas sustituye el mtodo toStringt ) (de Object) de modo que draw( ) termine (polimrficamente) imprimiendo algo distinto en cada caso.

n este ejemplo, la generalizacin tiene lugar cuando se coloca la forma geomtrica en el contenedor Lfot<Shape> Durante la generalizacin a Shape. el hecho de que los objetos sean tipos especficos de Shape se pierde Para la matriz se trata simplemente de objetos Shape
I

En el momento en que se extrae un elemento de la matriz, el contenedor (que en la prctica 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 correccin. Eso es lo que RTTI significa: el tipo de los objetos se identifica en tiempo de ejecucin.

En este caso, la proyeccin RTTI 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 L8t<Shapc> est lleno de objetos Shape. En tiempo de compilacin, esto se impone mediante el contenedor y el sistema genrico de Java, pero en tiempo de ejecucin es la proyeccin la que garantiza que esto sea as.

Ahora es cuando entra en accin el polimorfismo y se determina el cdigo exacto que ejecutar el objeto Shape viendo si la referencia corresponde a un objeto Circle. 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 familia de objetos (en este caso. Shape) Como resultado, el cdigo ser ms fcil de escribir, de leer y de mantener, y los diseos sern ms sencillos de implementar. comprender y modificar Por ello, el polimosrfismo es uno 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 permitir a nuestros usuarios que resalten todas las formas geomtricas de un cierto tipo concreto, asignandolas un color especial, de esta forma, pueden localizar todos los tringulos de la pantalla resaltndolos. O. por ejemplo, imagine que nuestro mtodo necesita "rutar una lista de formas geomtricas. pero que no tiene sentido rotar un circulo, por lo que preferimos saltamos los circuios al implementar la rotacin de todas las formas. Con RTTI. podemos preguntar a una referencia de tipo Shape cul es el tipo exacto al que est apuntando. lo que nos permite seleccionar y aislar los casos especiales. El objeto Class

Para comprender cmo funciona el mecanismo RTTI en Java, primero tenemos que saber cmo se representa la informacin de tipos en tiempo de ejecucin. Esto se lleva a cabo mediante un lipo de objeto especial denominado objeto Class que contiene informacin acerca de la clase De hecho, el objeto Class se utiliza para crear todos los objetos "normales de una clase. Java implcmenta el mecanismo RTTI utilizando el objeto Class. incluso si lo que estamos haciendo es algo como una proyeccin de tipos. La clase Class tambin permite otra serie de formas de utilizacin de RTTI.

Existe un objeto Class para cada clase que forme parte del programa. En otras palabras, cada vez que escribimos y compilamos una nueva clase, tambin se crea un determinado objeto Class (y ese objeto se almacena en un archivo .class de nombre 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 pane de la implementacin de la JVM. El cargador de clases primordial carga las que se denominan clases de confianza, que incluyen las clases de las interfaces API de Java, y esa carga se realiza normalmente desde el disco local.

Usualmeme no es necesario tener cargadores de clases adicionales en la cadena, pero si tenemos necesidades especiales (como por ejemplo, cargar clases de alguna manera especial para dar soporte a aplicaciones de servidor web. o descargar clases a iravs de una red), entonces disponemos de una manera de enlazar cargadores de clases adicionales.

Todas las clases se cargan en la JVM dinmicamente, cuando se utiliza la clase por primera vez. Esto sucede cuando el programa 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 .static para el constructor. Por tanto, el crear un nuevo objeto de dicha clase utilizando el operador non tambin cuenta como una referencia a un miembro esttico de la clase.

Por tanto, tus programas Java no se cargan por completo antes de comenzar la ejecucin, sino que se van cargando los distintos fragmentos del programa a medida que son necesarios. Esto difiere de muchos lenguajes tradicionales El mecanismos de carga permite conseguir un tipo de comportamiento que resulta muy difcil, o incluso imposible, de obtener con un lenguaje esttico de carga como pueda ser GH*.

El cargador de clases comprueba primero si el objeto Class de dicho tipo est cargado. Si no lo est, el cargador de clases predeterminado localiza el archivo .class con dicho nombre (un cargador de clases adicional podra, por ejemplo, extraer el cdigo intermedio de una base de dalos en lugar de hacerlo de un archivo). A medida que se carga el cdigo intermedio correspondiente a la clase, dicho cdigo se verifica paru garantizar que no est corrompido y que no incluya cdigo Java mal formado (sta es una de las lineas 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 forma de actuar: //: typeinfo/SweetShop.j ava // Examen de la Corma en que funciona el cargador de clases, import static

net.mindview.til.Print.*;

class Candy j static { pnnt ("Loading Candy); ) clasa Gum { static ( pranr("Loadinq Gum"); )

i class Cookie ( static | print ("Loading Cookie"); } J public class SweetShop ( public static void main(StringEl args) ( print (H inai.de main) ; new Candy(); print("After creating Candy"); try { Class.forName("GumM); ) catch(ClacsNotFoundxce ption e) { printI"Couldn't find Gum");

) printl"After Class.forNamel\nGumV)"); new Cookie ();

print("After creating Cookie");

t ) / Output: inside main Loading Candy After creating Candy Loading Gum After Clas6. forName (Gum1*) Loading Cookie After creating Cookie ///:

Cada una de las clases de Candy, Gum y Cookie tiene una clusula static que se ejecuta cuando se carga la clase por primera vez. Se imprimir la informacin para decimos cundo tiene lugar la carga de esa clase. En niain ). las creaciones de objetos estn mezcladas con instrucciones de impresin, como ayuda para determinar el instante de la carga.

Podemos ver. a panir de la salida, que cada objeio Class slo se carga cuando es necesario, y que la uncializacin de tipo static se realiza durante la carga de la clase. Una linea particularmente interesante es: Ciaos. forName("Gum " ) f

Todos los objetos C'las pertenecen a In clase Class. Un objeto Class es como cualquier otro objeto, por lo que se puede obtener y manipular una referencia a l (esto es lo que hace el cargador). Una de

las formas de obtener una referencia al objeto Class es el mtodo esttico for\ame( ). que toma un argumento de tipo String que contiene el nombre textual (tema cuidado con la ortografa y el uso de maysculas!) de la clase concreta de la cual se quiera obtener una referencia. El mtodo de\ uelve una referencia de tipo Class. que en este ejemplo se ignora, la llamada a for\ame() se realiza debido a su efecto secundario que consiste en cargar la clase Guni si no esta ya cargada. Durante el proceso de carga se ejecuta la clusula static de Gum

tn el ejemplo anterior, si Class.furNamet ) falla porque no puede encontrar la clase que estemos intentando cargar, generar una excepcin ClassNutFoundExceplion. Aqui. simplemente nos limitamos a informar del problema y a continuar, pero en otros programas ms sofisticados podramos intentar resolver el problema dentro de la rutina de tratamiento de excepciones.

14 lnformaci6n de tipos 987 Siempre que queramos utilizar la informacin de tipos en tiempo de ejecucin, debemos primero obtener una referencia al objeto Class apropiado. Class.forNamet ) es una forma cmoda de hacer esto, porque no necesitamos un objeto de dicho tipo para obtener la referencia Class Sin embargo, si ya disponemos de un objeto del tipo que nos interesa, podemos extraer la referencia Class invocando un mtodo que forma parte de la clase raz Object etClass(). Este mecanismo devuelve la referencia Class que representa el tipo concreto de ohjeto. Class dispone de muchos mtodos interesantes, el siguiente es un ejemplo que ilustra algunos de ellos//: cypeinfo/toys/ToyTest.java // Prueba de la clase Class, package typeinfo.toys; import static net.mindview.util.Print. ; interface HasBatteries () interface Waterproof (} interface Shoots {) class Toy ( // Desactive con un comentario el constructor predeterminadc // siguiente para ver NoSuchMethodError de (*1*) ToyO {) Toy tint ij () i class FancyToy extends Toy implements HasBatteries, Waterproof, Shoots ( FancyToy() { super{1) ; ) ) public class ToyTest ( static void printlnfo(Class cc) ( print ("Class name: M - cc.getNameO * * is interface? [" cc.islnterface() "I")/ print(Simple name: M * cc .getsimpleName()); print ("Canonical name : " -* cc.getCanonicalName());

) public static void main(String[1 args) { Class c a null; try (

14 lnformaci6n de tipos 988 c Class.fcrName(M typeinf o.toys.FancyToy"); ) catch(ClassNotFoundException e) { print("Can't find FancyToy"); System.exit IX);

) printlnfofc>; for(Class face : c.getlnterfaces()) printlnfo(face); Class up * c.getSuperclassIJ; Object obj - null; try ( // Requiere un constructor predetenuinado: obj * up.newInstance(); ) catch(Ih3tantiationException et { print("Cannot instantiate"I; System.exit(117 } catch<IliegalAccessException e; ( print(Cannot access"); i ) System.exit(IJ;

printlnfo fob}.getClass{ ) ) j

] / Output: Class name: typeinfo.toys.FancyToy is interface? (false] Simple name: FancyToy Canonical name : typeinfo.toy3.FancyToy Class name- typeinfo.toys.HasBatteries i n interface? (true] Simple name: HasBatterie sCanonical ame : typeinfo.toys.HasEatteries Ciass ame: type m o.toys.Waterproof is interface? (truel Simple ame: Waterproof Canonical ame : typeinfo.toys.Waterproof Class ame: typeinfo.toys.Shoots ls interface? ttruel Simple ame: Shoots Canonical ame : typeinfo.toys-Shoots

14 lnformaci6n de tipos 989 Class ame: typeinfo.toys.Toy is interface? Ifalse] Simple ame: Toy Canonical ame t typeinfo.toys.Toy *///:-

FancyToy hereda de Toy c mplcmenta las interfaces HasBatteries. Waterproof y Shoots. En main( ). se crea una referencia Class y se la iniciali/a para que apunte al objeto Class FancyToy Class utilizando forNanu*( ) dentro de un bloque try apropiado. Observe que hay que utilizar el nombre completamente cualificado (incluyendo el nombre del paquete) en la cadena de caracteres que se pasa a for.\ame( )

printlnfo( ) utiliza getNamc( > para generar el nombre de clase completamente cualificado, y getSimple\ame() y getCanonicaIName( ) (introducidos en Java SE5) para generar el nombre sin el paquete y el nombre completamente cualificado. respectivamente. Como su propio nombre indica, islnerface( ) nos dice si este objeto Class representa un interfaz. Por tanto, con el objeto Class podemos averiguar casi todo lo que necesitemos saber acerca de un determinado tipo.

El mtodo Class.getlnterfaces( ) invocado en mam( ) devuelve una matriz de objetos Class que representa las interfaces contenidas en el objeto Class de inters.

14 lnformaci6n de tipos 990 Si tenemos un objeto Class. tambin podemos preguntarle cul es su clase base directa utilizando getSuperclass(). Este mtodo dev uelve una referencia Class que podemos, a su vez. consultar. Por tanto, podemos determinar la jerarqua de clases completa de un objeto en tiempo de ejecucin.

El mtodo newlnstancc( ) de Class constituye una forma de implcmcntar un constructor virtual** que nos permite decir: No s exactamente de que tipo eres, pero crea una instancia de ti mismo de todas formas*'. En el ejemplo anterior, up es simplemente una referencia Class de la cual no se conoce ninguna informacin de tipos adicional en tiempo de compilacin. Y cuando creamos una nueva instancia, obtenemos como resultado una referencia Object. Pero dicha referencia apunta a un objeto Toy. Por supuesto, antes de poder enviar a ese objeto ningn mensaje diferente de los que admite Object. es necesario investigar un poco acerca del objeto y efectuar algunas proyecciones de tipos. Ademas, la clase que se crea con newlnstance() debe disponer de un constructor predeterminado. Posteriormente en este capitulo, veremos cmo crear objetos dinmicamente de una cierta clase utilizando cualquier constructor, empleando para ello la API de reflexin de Java.

Ejercicio 1: (I) Fn ToyTcsf.java, desactive mediante un comentario el constructor predeterminado de Toy y explique

lo que sucede.

14 lnformaci6n de tipos 991 Ejercicio 2: (2) Incorpore un nuevo upo de interfaz en TovTest.java y verifique que dicha interfaz se detecta y se

muestra adecuadamente.

Ejercicio 3: (2) Aada Khomhoid a Shapes.java. Cree un objeto Khomhoid y gcncralicelo a Shape. y vuelva a espe

cializarlo a Khomhoid. Trate de especializarlo a un objeto Circle y vea lo que sucede.

Ejercicio 4: (2) Modifique el ejercicio anterior para que utilice instanceof con el fin de comprobar el tipo, antes de

14 lnformaci6n de tipos 992 efectuar la espccializacin.

Ejercicio 5: (3) Implemente un mtodo rotate(Shape) en Shapes.java. que compruebe si esta girando un circulo (y.

en caso afirmativo, no realice la operacin).

Ejercicio 6: (4) Modifique Shapes.java para que permita resaltar" (activando 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*.

14 lnformaci6n de tipos 993 Ejercicio 7: (3) Modifique SweetShop.java para que la creacin de cada tipo de objeto est controlada por un argu

mento de la linea de comandos. En otras palabras, si la linea de comandos es java SweetShop Candy. entonces slo se crear el objeto Candy. Observe cmo se pueden controlar los objetos Class que se cargan. utilizando argumentos de la linea de comandos.

Ejercicio 8: (5)Escrbaunmtodo todas las clases presentes en la

que lome un objeto e imprima de manera recursiva

jerarqua de ese objeto.

Ejercicio 9: (5)Modifique el ejercicio anterior Clas$.getDoclaredFields( ) con el fin de mostrar

de

modo

que

utilice

14 lnformaci6n de tipos 994 tambin informacin acerca de los campos contenidos en cada clase.

Ejercicio 10: (3) Escriba un programa para determinar si una matriz de char es un tipo primitivo o un verdadero obje

to. Literales de clase

Java proporciona una secunda forma de generar la referencia al objeto C lass el literal 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 mtodo forNamc( ). es tambin ms eficiente.

14 lnformaci6n de tipos 995 Los literales de clase funcionan tanto con las clases normales como con las interfaces, matrices y tipos primitivos. Adems, existe un campo estandar denominado TYPE en cada una de las clases envoltorio de los tipos primitivos. El campo TV PE produce una referencia al objeto Class correspondiente al tipo primitivo asociado, de modo que

Fn mi opinin, es mejor utilizar las versiones .class" siempre que se pueda, ya que son ms coherentes con las clases normales.

Es interesante observar que al crear una referencia a un objeto Class utilizando .tiass" 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 intermedio (que usualmente se encuentra en el disco, dentro de la ruta de clases, aunque no tiene porqu ser necesariamente asi) y crea un objeto Class a partir de dicho cdigo intermedio.

14 lnformaci6n de tipos 996


2.

Montaje. La fase de montaje verifica el cdigo intermedio de la clase, asigna el campo de almacenamiento para los campos estticos v. en caso necesario, resuelve todas las referencias que esta clase haga a otras ciases.

3.

Inicializacin Si hay una superclase, es preciso metalizarla. ejecutando los micializadorcs de tipo static y los bloques de inicializacin de tipo static

La inicializacin se retarda hasta que produce la primera referencia a un mtodo esttico (el constructor es implcitamente de lipo static) o a un campo esttico no constante: //: typeinfo/ClassInitialization.java import 3ava.ueil**; class Initable { 9tatic final int staticFinal = 47; static final tnt staticFinal2 = ClasBlnitialization.ran.nex tInt(1000); static { System.out.println("Initializing Initable);

14 lnformaci6n de tipos 997 ! class Initable2 { static int staticNonFinal = 147; static ( j System.out .prmtln( "Initializing Initable2") ;

) class Initable3 { static int 3taticNonFinal 74; static ( System.out.printlnfInitializing Initable3");

) public class Classlnitialization { public static Random rand = new Random( 47); public static void main(String[] argsi throws Exception ( Class initable * Initable.class; System.out print In ( "After creating Initable reft;

14 lnformaci6n de tipos 998 // No provoca la inlciallzacin: System.out.println(Initable.staticFinal); // Provoca la inicializacin: System.out.printIn(Initable.staticFinal2); // Provoca la inicializacin: System.out.println(Initable2.staticNonFinal); Class initable3 = Class.forName ( "Initable3M ) ; System.out.println("After creating Initable3 ref"); System.out.println(Initable3.staticNonFinal);

) } / Output: After creating Initable ref 47 Initializing Initable 25a Initializing Initable? 147 Initializing Initable3 After creating Initable3 ref 74 ///:-

Rn la prctica, lu inicializacin es lo ms tarda posible. Analizando la creacin de la referencia initable, podemos ver que usar simplemente In sintaxis .class para obtener una referencia a la clase no provoca la inicializacin. Sin embargo, Class.forName ) inicialza la clase inmediatamente para generar la referencia C lass, como puede ver analizando la creacin de nitableJ

14 lnformaci6n de tipos 999 Si un valor final esttico es una 'constante de tiempo de compilacin, tal como Initable.staticHnal. dicho valor puede leerse sin que ello haga que la clase Initable se inicialice. Sin embargo, definir un campo como esttico y final no garanti- zn este comportamiento; al acceder a Initablc.statlcFinal2 se fuerza a la inicializacin de clase, porque dicho campo no puede ser una constante de nempo de compilacin.

Si un campo esttico no es de tipo final, 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 tnicializar dicho espacio de almacenamiento) antes de que el valor pueda ser ledo, como puede ver analizando el acceso a lnitable2.staticNonFinal. Referencias de clase genricas

l na referencia Class apunta a un objeto Class. que genera instancias de las clases y contiene todo el cdigo de los mtodos 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 diseadores de Java SE5 vieron la oportunidad de hacer esto un poco ms especifico, permitiendo restringir el tipo de objeto Class al que la referencia Class apunta, utilizando para ello la sintaxis genrica. En el siguiente ejemplo. ambos tipos de sintaxis son correctos: //: typeinf o/GenencClassReferences.java public class GenericClassReferences (

14 lnformaci6n de tipos 1000 public static void main (StringH args) ( Class intClass = int, class,- Class<Integer> genericIntClass = int.class, genericIntCiass = Xnteger.class; // Le mismo intClass * double.class? // genericIntClass = double.class; // Ilegal

) ///:-

La referencia de clase normal no genera ninguna advertencia de compilacin. Sin embargo, puede ver que la referencia de clase normal puede reasignarse a cualquier otro objeto Class, mientras que la referencia de clase genrica slo puede asignarse a su tipo declarado. Utilizando la sintaxis genrica, permitimos que el compilador imponga comprobaciones adicionales de los tipos.

14 lnformaci6n de tipos 1001 Que sucede si queremos relajar un poco las restricciones? Inicialmente, parece que deberamos ser capaces de hacer algo como lo siguiente: ClassNumber> genencNumberClass = int.class;

Esto parece tener sentido, porque Integer hereda de Numher. Sin embargo, este mtodo no funciona, porque el objeto Class Intcgcr no es una subclase del objeto Class Numher (puede parecer que esta distincin resulta demasiado sutil; la analizaremos con ms detalle en el Captulo 15, Genricas)

Para relajar las restricciones al utilizar referencias Class genricas, yo personalmente empleo el comodn* que forma parte de los genricos de Java. El smbolo del comodn es e indica cualquier cosa Por tanto, podemos aadir comodines a la referencia C lass del ejemplo anterior y generar los mismos resultados: //,* typenfo/WildcardCiassReferences.java public class WildcardClassReferences { public static void main(String[1 args) { CLass<?> intClass = int.class; intClass =double.class;

] I //A-

14 lnformaci6n de tipos 1002 En Java SE5. se prefiere utilizar C lass<?> en lugar de C lass. an cuando ambos son equivalentes y la referencia Class normal. como hemos visto, no genera ninguna advertencia del compilador. La ventaja de Class<?> es que indica que no estamos pasando una referencia de clase no especifica simplemente por accidente o por ignorancia, sino que hemos elegido la versin no especifica.

Para crear una referencia Class que est restringida a un determinado tipo o a cualquiera de sus subtipos, podemos combinar un comodn con la palabra clave extends para crear un limite. Por tanto, en lugar de decir simplemente Class<Numl>er>, lo que diriamos seria. //: typemfo/BoundedClassReferences.java pubiic class BoundedClassP.eferences ( public static void mam(StringU argel { Class<? bounded bounded bounded extends Nurnber* * int.class; = double.class; a Number.class;

// O cualquier otra cosa derivada de Number.

} ///*-

14 lnformaci6n de tipos 1003 La razn de aadir la sintaxis genrica a las referencias Class estriba nicamente, en realizar 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 destructivo 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 sintaxis de clases genricas. El ejemplo almacena una referencia de clase y luego genera un contenedor List relleno con objetos generados mediante nenlnstaneef ): //: typeinfo/FilledList.java import java .til. * rclass Countedlnteger ( prvate static long counter: private final long id counter+*; public String toStringO { retum Long. toString(id); ) i public class FilledList<T> ( prvate Class<T> type; public FllledList(Class<T> type) { this.type = type; ) public List<T> creatednt nElements) ( List<T> result = new ArrayList<TO ; try ( forfint i - 0; i < nElemencs; 1++1 result. add (type. newlnstance ( ) ) ; } catch'.Exception e) ( i throw new RuntimeException(e);

retum result;

14 lnformaci6n de tipos 1004 ) public static void main (String [] argsl { PilledList<CountedInteger> fl new FiiledList<Countedlnteger>(Countedlnteger.class?; System.out.println(fl.create(15));

} } /* Output: (0, 1. 2. 3. 4, 5, 6 , 7, 8, 9, 10. 11. 12. 13. 141 *///:

Observe que esta clase debe asumir que cualquier tipo con el que trabaje dispondr de un constructor predeterminado (uno que no tenga argumentos), obtenindose una excepcin si no es ste el caso. F.I compilador no genera ningn tipo de advertencia para este programa. Cuando utilizamos la sintaxis genrica para los objetos Class sucede algo interesante: IH\N Insta ncc( ) devolver el tipo exacto del objeto, en lugar de simplemente un objeto bsico Objcct como vimos en ToyTesLjava. Esto resulta un tanto limitado

14 Informacin de tipos 1005 :: typeinfo/toys/GenercToyTest.java // Prueba de la clase Class. package typeinfo. toys ,* public class GenericToyTest { pubiic static void roaintStringH args) throws Exception | C1ass<FancyToy ftClass = FancyToy.class; // Produce el tipo exacto: FancyToy fancyToy = ftClass.newlnstance{); Class<? super FancyToy> up = ftClass.getSuperclass0; // Esto no se compilar: // Class<Toy> up2 = ftClass.getSuperclass(); t ! Slo produce Object: Object obj * up.newInstanceO ;

i i ///-

Si obtenemos la superelase. el compilador slo nos permitir decir que la referencia a la superclase es alguna clase que es supere lase de FancyToy*', como podemos ver en la expresin Class<? super FancyToy>. No aceptar una declaracin de C luss<Toy>. Esto parece un poco extrao, porque get$uperclass( ) devuelve la cluse base (no una interfaz) y el compilador conoce en tiempo de compilacin lo que esa clase es: en este caso, Toy.class, no simplemente "alguna superclase de FancyToy*. En cualquier caso, debido a la vaguedad, el valor de retomo de up.ne\\lnstance( ) no es de un tipo preciso, sino slo de tipo Object Nueva sintaxis de proyeccin

14 Informacin de tipos 1006 Java SE5 tambin ha aadido una sintaxis de proyeccin para utilizarla con las referencias Class, nos referimos al mtodo cast( ): //: typeinfo/ClassCasts.java class Suilding {} class House extends Bulding {) public class ClassCasts ( public static void main{StringP args) { Suilding b = new House f 1 ; Class<House> houseType = House.class; House h * houseType. cast (b) ,* h = (House)b; // ... o haga simplemente esto. I i ///.-

El mtodo cast( ) toma el objeto proporcionado como argumento y lo provecta sobre el upo de la referencia Class Por supuesto, si examinamos el cdigo anterior parece que es demasiado trabajo adicional, si lo comparamos con la ltima linca de niainf). que hace exactamente lo mismo.

La nueva sintaxis de proyeccin resulta til en aquellas situaciones en las que na piulemos utilizar una proyeccin ordinaria. Esto sucede, usualmente, cuando estamos escribiendo cdigo genrico (de lo que hablaremos en el Capitulo 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 east( ) se use dentro de la biblioteca de Java SE5 (concretamente en com.sun.mirrorutil.DeclarationFilter)

14 Informacin de tipos 1007 Hay otra nueva funcionalidad que no se utiliza en absoluto en la biblioteca Java SE5: Class.asSubclass( ). Este mtodo permite proyectar el objeto de clase sobre un tipo mas especifico Comprobacin antes de una proyeccin

Hasta ahora, hemos visto varias formas de RUI. incluyendo:

1.

La proyeccin clsica, por ejemplo, (Shape). que utiliza RTTI para asegurarse de que la proyeccin es correcta. Esto generar ClassCaslException si se ha realizado una proyeccin incorrecta.

2.

Ll objeto Class representativo del objeto. Podemos consultar el objeto Class para obtener informacin til en tiempo de ejecucin.

En C4--*-. la proyeccin clsica (Shape) no utiliza mecanismos RTTI. Simplemente le dice al compilador que trate el objeto como si fuera del tipo indicado. En Java, si realiza la comprobacin de tipos, esta proyeccin se denomina a menudo "espeeializacin segura en lo que respecta a tipos". La razn de utilizar el trmino espeeializacin se basa en la disposicin histricamente utilizada en los diagramas de jerarquas de clases. Si la proyeccin de Circle sobre Shape es una generalizacin, entonces la proyeccin de Shape sobre Circle es una espeeializacin. Sin embargo, puesto que el

14 Informacin de tipos 1008 compilador sabe que un objeto Circle es tambin de tipo Shape. permite que se realicen libremente asignaciones de generalizacin, sin que sea obligatorio incluir una sintaxis de proyeccin especifica. F.l compilador rio puede saber, dado un objeto Shape. de qu tipo concreto es ese objeto: podria ser exactamente de Shape. o podra ser un subtipo de Shape, como Circle. Square, Triangle o algn otro tipo. En tiempo de compilacin, el compilador slo ve un objeto Shape. Por tanto, no nos permitir que realicemos una asignacin de espeeializacin sin utilizar una proyeccin especifica, con la que le decimos al compilador que disponemos de informacin adicional que nos permite saber que se trata de un tipo concreto (el compilador comprobar si dicha espeeializacin es razonable, por lo que no nos permitir efectuar especializaciones sobre un tipo que no sea realmente una subclase del anterior).

Existe un tercer mecanismo de RTTI en Java. Se trata de la palabra clave instaneeof. que nos dice si un objeto es una instancia de un tipo concreto. Devuelve un valor de tipo hoolean, asi que esta palabra clave se utiliza en forma de pregunta, como en el fragmento siguiente: ifIx instaneeof Dogl ((Dog)x).bark();

La instruccin if comprueba si el objeto x pertenece a la clase Dog antes de proyectar x sobre Dog. Es importante utilizar instaneeof antes de una espeeializacin cuando no dispongamos de otra informacin que nos indique el tipo del objeto; en caso contrario, obtendremos una excepcin ClassCastException.

Normalmente, lo que estaremos tratando de localizar es un determinado tipo (por ejemplo, para pintar de prpura todos los tringulos), pero podemos fcilmente seleccionar todos los objetos utilizando instaneeof. Por ejemplo, suponga que disponemos de una familia de clases para describir mascotas, Pet. (y sus propietarios, una caracterstica que nos ser til en un ejemplo posterior). Cada individuo (Individual) de la jerarqua 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 dicho codigo en el Capitulo 17. Anlisis detallado de los contenedores. En realidad, no es imprescindible analizar el cdigo de Individual en este momento, lo nico que

14 Informacin de tipos 1009 necesitamos saber es que podemos cicai un individuo con o sin nombre, y que cada objeto Individual tiene un mtodo id( ) que devuelve un identificador univoco (creado mediante un simple recuento de los objetos). Tambin hay un mtodo toString( ); si no se proporciona un nombre para un objeto Individual. toString( ) slo genera el nombre simple del tipo.

He aqu la jerarqua de clases que hereda de Individual: //: typeinfo/pete/Perscn.^ava package typeinfo.pets; public class Person extends Individual { public Person(Strina ame) { super!ame); } ) fth//: typeinfo/pets/Pet.java package typeinfo.pets; public class Pee extends Individual ( public Pet(String ame | superIame); \ public Pet() { super0/ }

) ///://: typeinfo/pets/Dog. javapackage typeinfo.pets; public class Dog extends Pet { public Dog{String name) public DogO ( superO; i ///=) { super(name}; }

//; typemfo/pets/Mutt. }ava package typeinfo.pets; public class Mutt extends Dog { public MuttiString name) ( super(name); ) public MuttO ( superO: )

14 Informacin de tipos 1010 ) ///:J /: typeinfo/pets/Pug.java package typeinfo.pets; public class Pug extends Dog ( public Pug{String name) public PugO { super(); } { super (name); )

) ///://: typeinfo/pets/Cat.java package typeinfo.pets; public class Cat extends Pet ( public Cat(String name) ( super(name); } public Cat{) ( super 0 ; )

} ///://: typeinfo/pets/EgyptianMau.java package typeinfo.pets; public class EgyptianMau extends Cat { public EgyptianMau(String ianMau () ( super 0/ } name; ( superiname); } public Egypt

) ///://: typeinfo/pets/Manx.java package typeinfo.pets;

14 Informacin de tipos 1011 public class Manx extends Cat { public Manx(String name) ( super(name); ) public ManxO ( superO; ) I ///://: typeinfo/pets/Cymric.]ava package typeinfo.pets; public class Cymric extends Manx ( public Cymric (String name) { super (name) ) public CymricO { superO; } ) III://: typeinfo/pets/Rodent.java package typeinfo.pets ;public Rodent(String name) ( super(name); ) public Rodent() { super OJ )

} ///=//: typeinfo/pets/Rat.java package typeinfo.pets; public class Rat extends Rodent j public Rat(String name1 { super(name); ) public RatO { super(}; }

} ///://: typeinfo/pets/Mous e.java package typeinfo.pets; public class Mouse extends Rodent ( public Mouse(String name) ( super(name); )

14 Informacin de tipos 1012 public ) ///:Mouse i) ( superO; )

//: typeinfo/pets/Hamste r.lava package typeinfo.pets; public class Hamster extends Rodent ( public Hamster(String name) ( super(name); ) public HamsterO ( superO; )

} ///:-

A continuacin, necesitamos una forma de crear aleatoriamente diferentes tipos de mascotas, y por comodidad, vamos a crear matrices y listas de mascotas. Para permitir que esta herramienta evolucione a travs de vanas implementaciones diferentes, vamos a definir dicha herramienta como una clase abstracta: //t typeinfo/pets/PetCreator.java // Crea secuencias aleatorias de objetos Pet. package typeinfo.pets; import java.util.*; public abstract class PetCreator ( private Random rand * new Random(47); // La lista de loe diferentes tipos de Pet que hay que crear: public abstract List<Ciasa<? extends Pet types 0 i public Pet randomPet0 { // Crear un objeto Pet aleatorio int n = rand.nextInt(types 0 size) J ; try ( return types0.get(n).newlnstance!);

14 Informacin de tipos 1013 } catch(InstantiationException e) ( throw new RuntimeException(e)? ) catch(IIlegalAccessException e) ( throw new RuntlmeException(e);

i public PetU createArray(int size) { Pet U result new Pet[size] ; for(int i * 0; i < size; i+*) result [ i] = randomPet O; retum result;

) public ArrayList<Pet> arrayList(int size) ( ArrayList<Pet> result = new ArrayList<Pet>0; Collections.addAll(result, createArray(size))

1014 Piensa en Java

;retum result ,*

} ///f-

bl mtodo abstracto gelTvpes() deja para las clases derivadas la tarea de obtener la lista de objetos C lass (esto es una variante del patrn de diseo basado en el mtodo de las plantillas). Observe que el tipo de clase se especifica como "cualquier cosa derivada de Pef. por lo que newlnsiance( ) produce un objeto Pet sin requerir ninguna proyeccin. randomPet( ) realiza una indcxacin aleatoria en el contenedor de tipo List y utiliza el objeto C lass seleccionado para generar una nueva instancia de dicha clase con Class.ncwlnstancc( ). El mtodo creati*Array( ) utiliza randnmPet() para rellenar una matriz y arrayUsl( ) emplea a su vez creaieArray().

Podemos obtener dos tipos de excepciones al llamar a newlnstance( ) Analizando el ejemplo, podr ver que estas excepciones 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 (HlegalAccessException est relacionado con una violacin del mecanismo de seguridad de Java, en este caso si el constructor predeterminado es de tipo prvale).

14 Informacin de tipos 1015

Cuando derivamos una subclase de PctCreator, lo nico que necesitamos suministrar es el contenedor List de todos los tipos de mascotas que queremos crear mediante randomPet( ) y los otros mtodos El mtodo getTvpes< ) normalmente devolver, simplemente, una rerencia a un lisia esttica. He aqu una implementacin utilizando for.Name ): //: typeinfo/pets/ForNameCreator.java oackaae typeinfo.peto; import java.utl.*; public class ForNameCreator extends PetCreatar | prvate static List<Ciass<? extends Pet>> types = new ArrayList<Class<? extends Pet>>(i; // Tipos que queremos crear aleatoriamente: private static StringU typeNames = ( "typeinfo.pets.Mutt". "typeinfo.pets.Pug". 'typeinf o .pets. EgyptianMau", Mtypeinfo.pets.Manxw, Mtypeinfo.pets.Cymricw. Mtypeinfo.pets.Rat", "typeinfo.pets.Mcuse". "typeinfo.peta.Hmster*

b SuppressWamlngs I "unchecked" i private static void loadert) { try ( forIString ame : typeNames) types.add ( (Class<? extends Pet>)Class.forNamemame)); ) catch(CassNotFoundException e) { throw new RuntimeException(e)

1016 Piensa en Java

) static { loader 0 j J public List<Class<? extends Pet>> types() (retum types;) ) tth~

El mtodo loader( ) crea la lista de objetos Class utilizando Class. for.\ame( ). Esto puede generar la excepcin ClassNotFoundExceplion. lo cual tiene bastante sentido, ya que estamos pasndole un objeto String que no puede validarse en tiempo de compilacin. Puesto que los objetos Pet se encuentran en el paquete typeinfo. es necesario utilizar el nombre del paquete al hacer referencia a las clases.

Para generar una lista con tipos de objetos Class. se necesita una proyeccin, 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 esttica, porque la anotacin < a SuppressWarnings no puede insertarse directamente en la clusula de inicializacin esttica.

Para recontar el numero 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 forma, podemos preguntar cosas como Cuntos objetos Hmster hay?". Podemos utilizar Instanceof para recontar las mascotas:

14 Informacin de tipos 1017

//: typeinfo/PetCount.java // Utilizacin de instanceof. import typeinfo.pees. ; import java.util.*; impere static net.mindview.til.Print; public class PetCount ( static class PetCounter extends HashMap^String, Integer> f public void count(String type) ( Integer quantity = get(type); if (quantity == nuil) put(type, 1); else i put I type, quantity 1) ;

) public static void countPets(PetCreator creator) ( PetCounter counter= new PetCounterO; forfPet pet : creator.createArray2Q)) { // Enumerar las mascotas individuales: printnb (pet .getClass <) .getSmpleName () - " if(pet instanceof Pet) counter.count("Pet") / iflpet instanceof Dogl counter.count("Dog" ) ; if (pet instanceof MuttJ counter.count("Mutt") ; if(pet instanceof Pug) counter.countt"Pug"); if(pet instanceof Cat) counter.count("Cat"); if(pet instanceof Manx) counter.count("Egypt ianMau"); if(pet instanceof Manx} counter. count (*' Manx *) j if i'pet instanceof Manx) counter.count ( "Cymric") ,* iflpet instanceof Rodent)

1018 Piensa en Java

counter.count{"Rodent * ) ; iflpet instanceof Rat) counter.count("Rat" ) ; if (pet instanceof Mouse) counter.count(Mouse" ); if(pet instanceof Hmster) counter.count("Hmste r");

// Mostrar las cantidades: print < ) print(counter);

) public static void main(String(] args) { countPets(new ForNameCreator());) / Output: Rat Manx cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptanMau Hmster EgyptanMau Mutt Mutt Cymric Mou.se Pug Mouse Cymric (Pug=3# Cat=9, Hamster-1, Cymric*7, Mouse=2, Mutt*3, Rodent=5, Pet=2Q, Manx=7, EgyptanMau7, Dog=6, Rat=2)

///:-

hn countPcls( ). se rellena aleatoriamente una matriz con objetos Pet utilizando un objeto PetCreator. Despus, cada objeto Pet de la matriz se comprueba > se recuenta utilizando instanceof

14 Informacin de tipos 1019

Existe una pequea restriccin en la utilizacin de instanccof: podemos comparar nicamente con un tipo nominado, y no con un objeto Class En el ejemplo anterior, podra parecer que resulta tedioso escribir todas esas expresiones instanceof. % efectivamente lo es. Pero no hay ninguna forma inteligente de automatizar instanceof creando una matriz de objetos Class y realizando la comparacin de dichos objetos (aunque, si sigue leyendo, ver que existe una alternativa). Sin embargo, esta restriccin no es tan grave como pudiera parecer, porque ms adelante veremos que si un diseo nos exige escribir una gran cantidad de expresiones instanceof probablemente eso signifique que el diseo no est bien hecho. Utilizacin de literales de clase

Si reimplementamos la clase PetCreator usando literales de clase, el resultado es mucho ms limpio en muchos aspectos. //: typeinfo/pets/LiteralPetCreator.java // Utilizacin de literales de clase, package typeinfo.pets; mport j ava.ut i1.*; public class LiteralPetCreator extends PetCreator | // No hace falta bloque try. SuppressWarnings ("unchecked* ) public static final List<Class<? extends Pet allTypes * Collections.unmodiflableLst Arrays.asLxst( Pet.class, Dog.class, Cat.class, Rodent.class, Mutt.class, Pug.class, EgyptanMau.class, Manx.class, Cymric.class, Rat.class, Mouse.clase,Hamster.clas3)); // Tipos para la creacin aleatoria: private static final List<Class<? extends Pet types = allTypes.subL3t(allTypes.indexOf tMutt.class), allTypes.si2e( ) ) public List<Class<? extends Pet types(I ( retum types; i public static void inain (StringI] argsl [ ) System.cut.Drintln(types,

) /* Output: [class typeinfo.pets.Mutt, class typeinfe.pets.Pug, class typeinfo.pets.EgyptanMau, class typeinfo.pets.Manx, class typeinfo.pets.Cymric. class typeinfo.pet3.Rat, claso typeinfo.pets.Mouse, class typeinfo.pets-Hmster! *///:-

1020 Piensa en Java

En el ejemplo PetCount3.java. que veremos ms adelante, necesitamos precargar un contenedor Map con todos los tipos de Pet (no slo con aquellos que hayan de ser generados aleatoriamente), por lo que es necesaria la lista allTypes List, que incluye todos los tipos. La lista types es la parte de allTypes (creada mediante List.subListt )) que incluye los tipos exactos de mascota, por lo que es la que se utiliza para la generacin aleatoria de objetos Pet.

Esta vez. la creacin de types no necesita estar rodeada por un bloque Irv, puesto que se evala en tiempo de compilacin y no generar ninguna excepcin a diferencia de Class.forName( ). Ahora tenemos dos implcmentacioncs de PetCreator en la biblioteca typeinfo.pets Para definir la segunda como imple- mentacin predeterminada, podemos crear un envoltorio que utilice LiteralPetCreator

1021 Piensa en Java :/ / : typeinfo/pets/Pets.java // Envoltorio para generar un PetCreator predeterminado, packaae typeinfo.pees; import java.til. ,* public class Pets ( public static final PetCreator creator new LiteralPetCreatort); public static Pet randomPet() ( return creator.randomPet{J;

) public static Pet[] createArray{int sizel { retum creator.createArray(size);

) public static ArrayList<Pet> arrayList{int sizel { return creator.arrayList(size);

) ///:public String toStringO {

1022 Piensa en Java Esto proporciona tambin una mdireccin para acceder a randomPett ), createArray( ) y arrayLJst( ).

Puesto que PetCount.countPets ) toma como argumento PetCreator. podemos probar fcilmente la clase de Literal PetCreator (mediante el envoltorio anteriormente definido): //: typeinfo/PetCount2.java import typeinfo.pets.*; public class PetCount2 { public static void main{String[] args) ( i PetCount.countPets(Pets.creatorJ;

} /* (Execute to see output) *///'*- La salida es igual que la de PetCount.juva. Instanceof dinmico

El mtodo Class.islnstance( ) proporciona una forma para probar dinmicamente el tipo de un objeto. Por tanto, podemos eliminar todas esas tediosas instrucciones instanceof de PetCount.java: //: typeinfo/PetCount3.java // Utilizacin de islnstance() import typeinfo.pets.*; import java.Util.; import net.mindview.util.; import static net.mindview.til.Print.*; public class PetCount3 { static class PetCounter extends LinkedHashMap<Classc? extends public String toStringO {

1023 Piensa en Java Pet>,Integer> ( public PetCounter() { super(MapData.map{LiteralPetCreator.allTypes, 0));

} public void count(Pet petj ( // Class.islnstance() elimina instrucciones instanceof: for{Map.Entry<Class<? extends Pet>,Integer> pair *, entrySet ()) if (pair.getKey (1 .islnstance {pet)) put{pair.getKey, pair.getValue0 + 1);

) StrmgBuilder result = new StringBurlder (" {H) ,- for (Map. Entry<Ciass<? extends Peo, Integer> pair : entrySetOJ ( result.append(pair.getKey ! ) .getSimpleName{) ) result.append(); result.append Ipair.getvalue l l ) , result.append(". ");

} result.delete(result.lengthO -2, result.lengthO) ,* public String toStringO {

1024 Piensa en Java result.append("}"); retum result. toString ()

i )

public static void main(String[J args) { PetCounter petCount new PetCounter(); for (Pet pet Pets. createArray (20)) ( pri.ntnb(pet.getClass 0 .getSimpleName O " "); petCount.count(pet);

) print( ) ; print(petCount);

) ) / * Output: Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptanMau Hmster EgyptanMau 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, Hmster-1}

public String toStringO {

1025 Piensa en Java *///:-

Para contar todos los tipos diferentes de objetos Pet. se precarga el mapa PetCounter Map con los tipos de LiteralPetCreator.alITypes. Esto utiliza la clase net.mindview.util.MapData, que toma un objeto Iterablc (la lista 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 seria contar los tipos que se generan aleatoriamente y no los tipos base como Pet y Cal

C omo puede ver. el mtodo islnstance( ) ha eliminado la necesidad de utilizar expresiones instanceof. Adems, esto significa que podemos aadir nuevos tipos de Pet simplemente cambiando la matriz LiteralPetC'reator.t>pes; el resto del programa no necesita modificacin (al revs de lo que suceda al utilizar expresiones instanceof).

fcl mtodo toString ) ha sido sobrecargado para obtener una salida ms legible que siga correspondiendo con la salida tpica que podemos ver a la hora de imprimir un contenedor de tipo Map Recuento recursivo

Rl mapa en PetCount3.PetCounter estaba precargado con todas las diferentes clases de objetos Pet. En public String toStringO {

1026 Piensa en Java lugar de sobrecargar el mapa, podemos utilizar Class.isAssignabIeKrom() y crear una herramienta de propsito general que no est limitada a recontar objetos Pet: //: net/mindview/util/TypeCounter.java J Recuenta instancias de una familia de tipos, package net.mindview.util; import java.til.*; public class TypeCounter extends HashMap<Class<? >,Integer>( private Class<?> baseType? public TypeCounter(Class<?> baseType) ( this.baseType * baseType;

) public void count(Object obj) { Class<?> type * obj.getClassO; if(:baseType.isAssignableFrom(type)) throw new RuntimeException fob} + H incorrect type:

type M, should be type or subtype of "

baseType ); countClass(t ype) .

i private void countClass(Class<?> type J { Integer quantity get(type); put(type, quantity *-* null ? 1 : quantity + 1); Class<?> superclass =* type .getSuperclass O ; if(superclass null && public String toStringO {

1027 Piensa en Java baseType.isAssignableFrom*su perclass)) countClass(superclass)i

) public String toStringO ( StrlngBuilder result = new StringBusIder("("); for\Map.Entry<Class<?>,Integer> pair : entrySetO) ( result.append(pair.getKey ().getSimpleName())j result .append ("") ; result.append(pair.getValue0); result. append f *, H);

} result.delete(result.length!)-2, result.length()); result. append (H retum result. toStrmg {) ;

public String toStringO {

1028 Piensa en Java ) ///.-

El mtodo count() obtiene el objeto Class de su argumento y utiliza isAssignableFromf) para realizar una comprobacin en tiempo de ejecucin con el fin de verificar que el objeto que se le haya pasado pertenece verdaderamente a la jerarqua de clases que nos interesa. countC'lass( ) 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. //: typeinfo/PetCount4.java import typeinfo.pets.*; import net .mmdview.util. ; import static net .mmdview.util .Print. *; public class PetCount4 { public static void main(StringU argel { TypeCounter counter = new TypeCounterIPet.class); Cor(Pet pet : Peta.createArray(20) I ( printnbfpet .getClass O .getSimpleName() counter.count{oet); I print( ) i print(counter); " ");

) } / * Output:(Sample) Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric (Mouse=2, Dog=6, Manx=7, EgyptianMau=2, Rodent=5. Pug=3. public String toStringO {

1029 Piensa en Java Mutt*3. Cymric=5, Cat9, Hamsteral. Pet20# Rat*2}

*///;-

Como puede ver analizando la salida, se cuentan ambos tipos base as como los tipos exactos.

Ejercicio 11: (2) Aada Gerbil a la biblioteca typeinfo.pets \ modifique todos los ejemplos del capitulo para adaptar

los a esta nueva clase.

public String toStringO {

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1030 Ejercicio 12: (3) Utilice TypeCounter con la clase CofTeeGenerator.java del Capitulo 15. Genricos.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 tipo de objeto Pet a la jerarqua tenemos que acordamos de aadirlo a las entradas de LiteralPetCreator.java En aquellos sistemas donde tengamos que aadir un gran nmero de clases de forma 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, asi 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, asi que la clase no se cargar y no podr ser incluida en la lista

Bsicamente, podemos obligamos a crear la lista nosotros mismos de manera manual (a menos que queramos escribir una herramienta que analice el cdigo fuente y luego genere y compile la lista). Por tanto, lo mejor que podemos hacer, probablemente, 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.

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1031 El otro cambio que vamos a hacer aqu es diferir la creacin del objeto, dejndoselo a la propia clase, utilizando el patrn de diseo denominado mtodo de factora 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 interfaz Factory: / / *. typeinfo/factory/Factory.java package typemfo. factory; public interface Factory<T> { 7 createO; } / / / 1

El parmetro genrico T permite a create( ) devolver un tipo diferente por cada implementacion de i*actory Esto 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 tipos que deben generarse mediante el mtodo createKandom( ) se registran ante la clase base aadindolos a la lista part Faetones. / /: typeinfo/RegisteredFactories .java / / Registro de factoras de clases en la clase base import typeinfo.factory.; import java.til.*; class Part ( public String toStringO { retum getClass O .qetSimpleName() /

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1032 static List<Factory<;? extends Part partFactories * new ArrayList<Factory<? extends Part>>(); static ( // Collections.addAll() genera una advertencia Hunchecked generic // array creation ... for vararg9 parameter*4. partFactories.add(new FuelFilter.Factory()); partFactories.add(new AirFilter.Factory0); partFactories.add(new CabinAirFilter.Factory( ) ) i partFactories.add(new OilFilter.Factory()); partFactories.add(new FanBelt.Factory()); partFactories.add(new PowerSteeringBelt.Factory() ) ; partFactories.add(new GeneratorBelt.Factory());

} prvate static Random rand = new Random(47); public static Part createRandora() { int n = rand.nextlnt(partFactories.sizel)) ; retum partFactories.get(n).create();)

) class Filter extends Part (} class FuelFilter extends Filter ( // Crear una factoria de clases para cada tipo especifico public static class Factory implements typeinfo.factory.Factory<FuelFilter> |

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1033 public FuelFilter create0 { return new FuelFilter d; }

) i class AirFilter extends Filter ( public static class Factory implements typeinfo.factory.Factory<AirFilter> ( public AirFilter created ( return new AirFilter d# )

} i class CabinAirFilter extends Filter ( public static class Factory implements typeinfo.factory.Factory<CabinAirFilter> { public CabinAirFilter create() { return new CabinAirFilter();

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1034 ) class OilFilter extends Filter { public static class Factory implements typeinfo.factory.Factory<OilFilter> ( public OilFilter created ( return new OilFilterd; |

> ) class 3elt extends Part () clas3 FanBelt extends Belt { public static class Factory implements typeinfo.factory.FactoryFanBelt> ( public FanBelt created ( return new FanBeltO; )

] class GeneratorBelt extends Belt { public 3tatic class Factory implements typeinfo.factory.Factory<GeneratorBelt> ( public GeneratorBelt created ( return new GeneratorBelt ()

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1035 )

} class PowerSteeringBelt extends Belt j public static class Factory implements typeinfo.factory.Factory<?owerSteeringBelt> ( public PowerSteeringBelt created { return new PowerSteeringBelt d . *} ) public class ReaisteredFactories ( public static void mainlStringH args) { for<int i * 0; i < 10; !--) System.out.printlnlPart.createRandom() J;

} } /* Output: GeneratorBelt CabnAirFilter GeneratorBelt AirFilter Power S t ee r mgBe 11 CabnAirFilter FuelFilter PowerSteerinaBelt PowerSteeringBelt FuelFilter ///:-

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1036 No todas las clases de la jerarqua deben instanciarse; en este caso. Filter y Bell son simplemente 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 createRandomf). contendr una clase Faetn interna. La nica forma de reutilizarel nombre Factory. como liemos visto antes, es mediante la cualificacin typcinfo.factory.Factory.

Aunque podemos utilizar Collections.flddAll() para aadir las factoras a la lista, el compilador se quejar, generando una advertencia relativa a la creacin de una matriz genrica (lo que se supone que es imposible, como veremos en el Capitulo 15. Genricos), por lo que hemos preferido invocar add(). El mtodo crcateRandomt ) selecciona aleatoriamente un objeto factora de partFaetones c invoca su mtodo create() para generar un nuevo objeto Fart

Ejercicio 14: (4 Un constructor es un tipo de mtodo de factora. Modifique RegisteredFactories.java para que en

lugar de utilizar una factora explcita, el objeto clase se almacene en el contenedor List, utilizndose ne\>instance( ) para crear cada objeto.

Ejercicio 15: (4) Implemente un nuevo PetCreator utilizando factoras registradas y modifique el

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1037 mtodo envoltorio de

la seccin Utilizacin 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 utilicen Pets.java sigan funcionando correctamente.

Ejercicio 16: (4) Modifique la jerarquia Coffee del Captulo 15. Genricos, para utilizar jerarquas registradas instanceof y equivalencia de clases

Cuando tratamos de extraer informacin sobre los tipos, existe una diferencia importante entre ambas formas de Instance- of es decir, instanceof o islnstaneet ). que produce resultados equivalentes) y la comparacin directa de los objetos Class He aqu un ejemplo que ilustra la diferencia: //: typeinfo/FamilyVsExactType.java //La diferencia entre instanceof y los objetos clase package typemfo; import atatic net.mindview.util.Print. class Base {) class Derived extends Base (} public class FamilyVsExactType ( static void testObject x) {

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1038 print ("Testing x of type " + x. getClass ()) ; print(x instanceof Base M - (x instanceof Base)); print(Mx instanceof Derived * + (x instanceof Derived)); print("Base.islnstance(x) Base.class.islnstanceIx)); print("Derived.islnstance(x) " 4 Derived.class.islnstance(x)) 1 print ("x.getClass O = Base.class K 4 (x.getClass () =** Base .class)) ; print("x.getClassO =Derived.class M * (x.getClassO == Derived.class)); print("x getClass(J .equals(Base.classJ J (x.getClass 1) .equals (Base.class) ) ) print Cx.getCla9s () .equals(Derived.class)) - r (x.getClass().equals(Derived.class) ));

public static void main(String[J args) { test(new Base ) ; test (new DerivedO);

) ) / Output: Testing x of type class typemfo x instanceof Base tru x instanceof Derived false Base.islnstance(x) true Derived.islnstance(x) false x.getClass() == Base.class true x.getClass O =* Derived.class false Base

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1039 x.getClass().equals(Base.class) ) true x.getClass().equals(Derived.cla ss)) false Testing x of type classtypemf o.Derived x instanceof Base true x instanceof Derived true Base.islnstance(x) true Derived.islnstance(x) true x.getClassO 3 = Base.class false x.getClassO *= Derived.class true x.getClass O.equals(Base.class)) false x.getClassO .equals (Derived. class) ) true *///:-

El nictodo test( ) realiza una comprobacin de tipos con su argumento, utilizando ambas formas de instanceof Despus, obtiene la referencia al objeto Class y emplea = y equalsf ) para comprobar la igualdad de los objetos Class. Como cabria

sperur, Instanceof e lslnstance( ) producen exactamente los mismos resultados, al igual que equals() y ==. Pero las pruebas 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 enjuego los conceptos de herencia: o son tipos exactamente iguales o no lo son.
e.

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1040 Reflexin: informacin de clases en tiempo de ejecucin

Si no conocemos el tipo concreto de un objeto, el mecanismo RTT1 nos los dir. Sin embargo, existe una limitacin; el tipo debe ser conocido en tiempo de compilacin, para poder detectarlo utilizando RTTI y para poder hacer algo til con la informacin. 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 objeto que no se encuentra en nuestro espacio de programa. De hecho, suponga que la clase del objeto no est ni siquiera disponible para nuestro programa en tiempo de compilacin. Por ejemplo, suponga que extraemos una serie de bytcs 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 desplazamos hacia un mundo de programacin ms amplio, aparecen casos de gran importancia en los que lo que sucede es precisamente esto. Hl primero de esos casos es la programacin basada en componentes, en la que construimos los proyectos utilizando herramientas RAD {fiapidApplication Develupment, desarrollo rpido de aplicaciones) dentro de un entumo IDE (ntegrated Oeve/opment Envirvnment. entorno integrado de desarrollo), que forma 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 formulario una serie de iconos que representan componentes. Estos componentes *e 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 si mismos y que permitan que sus propiedades se lean y se modifiquen. Adems, los componentes que gestionan sucesos GUI [Graphieal User Interinen) deben exponer la informacin acerca de los mtodos apropiados, de modo que el entorno IDE pueda ayudar al

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1041 programador a la hora de sustituir dichos mtodos de tratamiento de sucesos. La reflexin proporciona el mecanismo para detectar los mtodos disponibles y generar los nombres de los mtodos. Java proporciona una estructura para la programacin basada en componentes mediante JavaBeans (este tema se describe en el Capitulo 22. Interfaces grficas de usuario).

Otra razn importante para descubrir la informacin de clases en tiempo de ejecucin es tener la posibilidad de crear y ejecutar objetos en plataformas remotas, a travs de una red. Esto se denomina invocacin remota de mtodos iRMl, Rentte Mediad lnvocation) y permite a un programa Java tener objetos distribuidos entre muchas maquinas. Esta distribucin puede tener lugar pur diversas razones. Por ejemplo, quiz estemos realizando una tarea que requiera clculos intensivos y. para acelerar las cosas, podemos intentar descomponerla y asignar partes del trabajo a las mquinas que estn inactivas. En otras situaciones, puede que queramos colocar el cdigo que gestiona tipos concretos de tareas (por ejemplo, reglas de negocio en una arquitectura eltente/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 fcilmente 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 modificacin del software). En la misma linea, la informtica distribuida tambin soporta la utilizacin de hardware especializado 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 soporta el concepto de reflexin, junto con la biblioteca java.lang.reflect que contiene las clases Field. Method 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 lnvoke( ) para invocar un mtodo asociado con un objeto Method. Adems, podemos invocar los mtodos de utilidad tetFields( ). uetMctliodM ). gotConstructors( ). etc., con el fin de obtener como resultado matrices de objetos que representen los campos, mtodos y constructores (puede averiguar mas detalles examinando la clase Class en la documentacin del JDK). Asi. la informacin de clase para objetos anommos puede determinarse completamente en tiempo de ejecucin y no es necesario tener ninguna informacin en tiempo de compilacin.

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1042 Es importante comprender que no hay ninguna especie de mecanismo mgico en la reflexin. C uando se utiliza la reflexin para mteractuar con un objeto de un tipo desconocido, la maquina JVM simplemente examinar el objeto y comprobar que pertenece a una clase concreta (al igual que con el mecanismo RTT1 normal). Antes de poder hacer nada con l. es necesario cargar el objeto Class. Por tantu. 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 RTTl y la reflexin es que. con la RTTl. el compilador abre y examina el archivo .class en tiempo de compilacin. Dicho de otra forma, podemos invocar todos los mtodos de un objeto de la forma normal**, Con el mecanismo de reflexin, el archivo .class no esta 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

Normalmente, no vamos a necesitar utilizar las herramientas de reflexin directamente, pero si que pueden resultar tiles cuando necesitemos crear cdigo ms dinmico La reflexin se ha incluido en el lenguaje para soportar otras caractersticas de Java, como la sealizacin de objetos y JavaBeans (ambos temas se tratan postenormente en el libro). Sin embargo, hay ocasiones en las que resulta muy til extraer dinmicamente la informacin 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 sustituidos 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

y requiere mucho tiempo27 Afortunadamente, el mecanismo de reflexin proporciona una forma de 27 Especialmente cn cl pasado. Sin embargo. Sun ha mejorado ciiormeraenic su documentaci^ HTML sobie Java, por lo que ahum es mi fcil cousu lu los metodo* de las clases base

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1043 escribir una herramientas simple que nos muestre automticamente la interfaz completa, lie aqu la forma en que funciona. //: typemfo/ShowMethods.java // Utilizacin de la reflexin para mostrar todos los mtodos de una clase, // incluso aunque los mtodos estn definidos en la clase base. // {Args: ShowMethods} import 3 ava.lang.reflect.*; import java. ut i 1 .regex.; import static net.mindview.util.Print.*; public class ShowMethods ( private static String usage = "usage:\n" 1 "ShowMethods qualified.class.name\n" + "To show all methods in class or:\n" "ShowMethods qualified.class.name word\n + "To search for methods involving word'"; private static Pattern p * Pattern.compileC\\w*\\."J; public static void mam (String tl args) ( if(args.length < 1 ) ( print(usage); System.exit(0);

) int lines = 0 ; try ( Class<?> c = Class.forName(args[03 ) ; MethodJ methods * c.aetMethods() ; Constructor!] ctors = c.getConstructorsI); if(args.length =-1 ) ( Cor(Method method :

Ejercicio 13:(.VUtiliceTypeCountercon el ejemplo Uegstered Faetones, ja va de este capitulo. 14 Informacin de tipos 1044 methods) print I p.matcher(method.toString()).re placeAll("")); for(Constructor ctor : ctors) print (p.matcher(ctor.toStringOJ.replaceAl l("")); lines = methods.length ctors.length; } else { for(Method method : methods) if (method. toStringO .indexOf (args flj) t* -1 ) { print < i p.matcher (method. toStnng 0 ) .replaceAll ("")); lines---*-;

for(Constructor ctor : ctors) if ictor. toString I) . mdexOf (args [1]) 1= -1J ( print(p.matcher( ctor.toString()).replace All("*)); lines +;

) ) catch(ClassNotFoundException e) { print ("No such class: MM + e)

14 Informacin de tioos 1045 ; ) / Output: public static void main(String(]) public native int hashCodeft public final native Class getClass O public final void wait(lcng,int} throws InterruptedKxception public final void wait() throws InterruptedException void wait(long) throws public finalnative InterruptedException public boolean egual3(Object) public String toStringO public public finalnative finalnative void notifyO void notifyAlll)

public ShowMethods()

*///:-

Los mtodos get\1cthods( ) y gctConstructors( ) de Cluss devuelven una matriz de tipo Method y una matriz de tipo Constructor, respectivamente. Cada una de estas clases tiene mtodos adicionales para diseccionar los nombres, argumentos y valores de retomo de los mtodos que representan. Pero tambin podemos utilizar toString( ). como se hace en el ejemplo, para producir una cadena de caracteres con toda la signatura del mtodo. 1:1 resto del cdigo extrae la informacin de la linea de comandos, determina si una signatura concreta se corresponde con la cadena buscada (utilizando inde*Of( )) y elimina los cualificadores de los nombres utilizando expresiones regularos (presentadas en el Capitulo 13. Cadenas de caracteres).

14 Informacin de tioos 1046 El resultado producido por Class.forName ) no puede ser conocido en tiempo de compilacin, y por tanto toda la informacin de signaturas de mtodos se est extrayendo en tiempo de ejecucin. Si analiza la documentacin 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 tiempo 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 mecanismo 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 predeterminado pblico, an cuando no se haya definido ningn constructor. El constructor que vemos es el que el compilador sintetiza de forma automtica. Si luego ejecutamos ShowMethods con una clase no pblica (es decir, acceso de paquete), el constructor predeterminado sintetizado no aparecer en la salida, tl constructor predeterminado sintetizado recibe automticamente el mismo acceso que la clase

Otro experimento interesante consiste en invocar java ShowMethods java.lang.String con un argumento adicional de tipo chr. 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 documentacin del JDK. o bien si no sabemos, por ejemplo, si dicha clase

14 Informacin de tioos 1047 puede hacer algo con. por ejemplo, objetos de tipo Color.

El Capitulo 22. Interfaces grfteas de usuario, contiene una versin GUI de este programa (personalizada para extraer informacin para componentes Swing). por lo que puede dejar ese programa ejecutndose mientras est escribiendo 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: (!) Defina ShowMethods como una clase no pblica y verifique que el constructor predeterminado sinte

tizado no aparece a la salida.

14 Informacin de tioos 1048 Ejercicio 19: (4) En ToyTestjava, utilice la reflexin para crear un objeto Toy utilizando el constructor no predetermi

nado.

Ejercicio 20: (5) Examine la interfaz de java.lang.Class en la documentacin del JDK que podr encontrar en

http://java.sun.com. Escriba un programa que tome el nombre de una clase como un argumento de la linea de comandos, y luego utilice los mtodos Class para volcar toda la informacin disponible para esa clase. Compruebe el programa con una clase de la biblioteca estndar y con una clase que usted mismo defina. Proxies dinmicos

El patrn de diseo Proxv es uno de los patrones de diseo bsicos. Se trata de un objeto que insertamos en lugar del objeto 'real* para proporcionar operaciones adicionales o diferentes, estos objetos normalmente se comunican con un objeto real*, de manera que un proxv acta tpicamente como un intermediario. He aqu un ejemplo trivial para mostrar la estructura de un proxv f/: typeinfo/SimpleProxyDemo.java import static net

14 Informacin de tioos 1049 .mmdview.util. Print. * / interface Interface { void doSomething0; void somethingElseiSt ring arg)

} class RealObject implements Interface { public void doSomething0 ( print("doSomething" ) } public void somethingElse(String arg) { print("somethingElse " arg);

) class SimpleProxy implements Interface { private Interface proxied; pubiic SimpleProxy(Interface proxied) ( this.proxied * proxied;

14 Informacin de tioos 1050 ) public void doSomething() { print(SimpleProxy doSomething ) ; proxied.doSomethinq( ) ;

) public void somethingElseIString arg) ( print ("SimpleProxy somethingElse H arg); proxied.somethingElse(arg);

} class SimpleProxyDcmo { public static void consumerfInterface iface) { iface.doSomething(); iface.somethingElse("bonobo"); 1 public static void mam(String[] args) ( consumer(new RealODject )); consumer(new SimpleProxy tnew RealObject())/;

14 Informacin de tioos 1051 ) ) /* 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 RealObject o un Proxv. porque ambos implcmentan Interface. Pero el Proxy. que se ha insertado entre el cliente y el objeto RealObject. realiza operaciones y luego invoca el mtodo idntico de RealObject

Un proxy puede ser til siempre que queramos incluir operaciones adicionales en un lugar distinto que el propio objeto real", 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, asi que slo st se tienen que efectuar modificaciones para justificar el uso de un patrn). Por ejemplo, que sucede 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 proxv nos permite aadirlo y eliminarlo fcilmente.

El concepto pmxy dinmico de Java lleva el concepto de pmxy un paso ms all, tanto porque crea el objeto dinmicamente cuanto porque gestiona dinmicamente las llamadas a los mtodos para los cuales hemos insertado un proxv. Todas las llamadas realizadas a un proxv dinmico se redirigen a un nico gestor de invocaciones, cuya tarea consiste en descubrir que es cada llamada y en decidir qu hacer con ella. He aqui el programa SimpleProxyDemo.java reescrito para utilizar un pmxy dinmico: //: typeinfo/SimpleDynamlcProxy. lava import java.lang.reflect. , class DynamicProxyHandler implements

14 Informacin de tioos 1052 InvocacionHandler { private Object proxied; public DynamicProxyHandler(Object proxied) { this.proxied = proxied/ i public Object inveke IObject proxy. Method method, Object IJ args) throws Throwable { System, out. print ln (**** proxy: " + proxy - ge t Class 0
H. method: " + method ^ ", args: " - args) j if(args t- nuil)

for(Object arg : args) System.out.println ( " M arg); return method.invoke(proxied, args);

} 1 class SimpleDynamicProxy ( public static void consumer<Interface iface) { i face.doSomething( ) iface.somethmqElse (''bonobo'*) j

) public static void main(StringLJ args { RealObject real = new RealObject(1 ; consumer(real); / t Insertar un proxy y llamar de nuevo: Interface proxy = [ Interface) Proxy. newProxylnstar.ee ( Interface-class.getClassLoaderI ) , new Clase (]{ Interface.class },

14 Informacin de tioos 1053 new DynamicProxyHandler(realf) consumerIproxvi;

) ) *Output:(95%match) doSomething somethingElse bonobo proxy: class SProxyO, method: public abatract void Interface.doSomething t), args: nuil doSomething proxy: class SProxyQ, method: public abstract void Interface.somethingElse(java.lang.String, args: Ljava.lang.Object;.v*2e816 bonobo aomethingElse bonobo ///:-

Para crear un proxy dinmico se invoca el mtodo esttico Proxy.neH Pro\\instance(), que requiere un cargador de clases (generalmente, podemos pasarle un cargador de clases de un objeto que ya haya sido cargado), una lista de interfaces (no clases ni clases abstractas) que queramos que el proxy implemente y una implementacin de la interfaz Invocationllandler (gestor de invocaciones). El proxy dinmico redirigir todas las llamadas al gestor de invocaciones, de modo que al constructor para el gestor de invocaciones usualmente se le entrega la referencia al objeto real" para que pueda redirigirle las solicitudes una vez que haya terminado de llevar a cabo su tarea intermediaria.

Al mtodo invoke( ) se le pasa el objeto proxy. en caso de que necesitemos distinguir de dnde viene la solicitud, (aunque en muchos casos esto no nos preocupar). Sin embargo, tenga cuidado cuando invoque mtodos del proxy dentro de invoket ). porque las llamadas a travs de la interfaz se

14 Informacin de tioos 1054 redirigen a travs del proxv.

n general, lo que haremos ser realizar la operacin intermediario y luego usar Method.invoke() para redirigir la solici- id hacia el objeto real pasndole los argumentos 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: //: typeinfo/SelectmgMethods. java // Bsqueda de mtodos concretos en un proxy dinmico. import j ava.lang.reflect.j import static net.mindview.til.Print; class MethodSelector implements InvocationHandler { private Object proxied; public MethodSelector(Object proxied) ( this.proxied * proxied; 1 public Cbject invoke(Qbject proxy, Method method, ObjectU args) throws Throwable { if (method.getName () .equalsi"interesting"I ) print("Proxy detected the lnterestlng method"): return method.invoke(proxied, args);

14 Informacin de tioos 1055 interface SomeMethods { void bormgl () j void borng2()/ void interesting(String arg); void boring3()/

i class SelectingMethods ( public static void mainiStringU args) ( SomeMethods proxy* (SomeMethods) Proxy .newproxylnotar.ee ( SomeMethods.class.getClassLoader(J, new Class[]{ SomeMethods.class }, new MethodSelector(new Implementation())); proxy.bor ingl(); proxy.bonng2(i ; proxy.interesting(*bo nobo"); proxy.baring3(); ) / Output: boringl boring2 Proxy detected the interestmg method interestmg bonobo boring3 *///'-

14 Informacin de tioos 1056 Aqu, simplemente examinamos los nombres de los mtodos, pero tambin podramos examinar los aspectos de la signatura del mtodo, incluso podramos buscar valores concretos de los argumentos.

t i pwx} dinmico ni es una herramienta para utilizarla todos los dias, pero permite resolver ciertos tipos de problemas muy

elegantemente. Puede obtener mas detalles acerca del patrn de diseo Proxy y de otros patrones de diseo en Thinking in Patrerns (vasewww. MindWew.net) y Pesign Pattems, de l-lnch Gamma e t al. (Addison-Wesley, I995).

Ejercicio 21:(3) Modifique SimpleProxyDemo.java para que mida los tiempos de llamada a los mtodos.

Ejercicio 22:(3) Modifique SimpleDynamicProxy.java para que mida los tiempos de llamada a los mtodos.

14 Informacin de tioos 1057 Ejercicio 23:(3) Dentro de invoke( ) en SimpleDynamicProxy.java. trate de imprimir el argumento proxy y explique

lo que sucede.

Proyecto: Escriba un sistema utilizandopruxies dinmicos para implementar transacciones, donde el proxy se encar

gue de confirmar la transaccin si la llamada realizada al objeto real tiene xito (no genera ninguna excepcin), debiendo anular lu transaccin si la llamada talla. La confirmacin y anulacin deben funcionar como un archivo de texto extemo, que se encuentra fuera del control de las excepciones Java. Tendr que prestar atencin a la atomicidad de las operaciones. Objetos nulos

Cuando se utiliza el valor predeterminado nuil para indicar la ausencia de un objeto, es preciso comprobar si las referencias son iguales a nuil cada ve/ que se utiliza. Esta labor puede llegar a ser muy tediosa y el cdigo resultante es muy complejo I I problema es que nuil no tiene ningn comportamiento propio, salvo generar una excepcin NullPointcrException si se intenta hacer algo con el valor Algunas veces, resulta til introducir la idea de un objeto nulo\ que aceptar mensajes en lugar del objeto al cual "representa", pero que devolver valores indicando que no existe ah ningn

14 Informacin de tioos 1058 objeto real. De esta forma, podemos asumir que todos los objetos son vlidos y no tenemos porqu desperdiciar tiempo de programacin comprobando la igualdad con nuil (y leyendo el cdigo resultante).

Aunque resulta divertido imaginarse un lenguaje de programacin que cree automticamente objetos nulos por nosotros, en la prctica no tiene sentido usarlos en todas partes; en ocasiones, sera adecuado realizar las comprobaciones de valor nuil, en otros casos podremos asumir razonablemente que no vamos a encontramos con el valor nuil, e incluso, en otras ocasiones. .ser perfectamente aceptable detectar las aberraciones a travs de NullPointcrException. El lugar donde los objetos nulos parecen ser ms tiles es en el lugar ms prximo a los datos, con objetos que representen entidades en el espacio del problema. Como ejemplo simple, muchos sistemas dispondrn de una clase Person, y hay situaciones en el cdigo en las que no disponemos de una persona real (o si disponemos de ella, pero no tenemos todava toda la informacin acerca de dicha persona), por lo que tradicionalmente utilizaramos una referencia nuil y comprobaramos si las referencias son nulas. En lugar de ello, podemos crear un objeto nulo, pero an cuando el objeto nulo responder a todos los mensajes t.itt proyectos son sugerencias que pueden utilizarse, por ejemplo, corno trabajos de clase. Los soluciones a los proyectos no se incluyen en la guia de eluciones. Descubierto por Bobby Woolf y Bruce Andcrson Puede serse como un cuso especial del patrn de diseo basado en estrategia. Uno variante del Objeto Sato e> el patrn de diserto de tteidor \ufo. que hace que la interaccin a trav s de los nodos en una jerarqua compuesta sea transparente para et cliente el cliente puede entonces utilizar la mssnin lgica pora iterar a travs de la jerurquia compuesta y a travs de los nodos hoja).

a los que el objeto TcaP respondera, sigue siendo necesario disponer de una manera de comprobar si hay valores nulos. La forma mas simple de hacer c>to consiste en crear una interfaz de marcado: //: net/mindview/util/Nuil.java package net.mindview.til; public mterface Nuil () ///i-

14 Informacin de tioos 1059 F.sto permite a instanceof detectar el objeto nulo, y lo ms importante, no requiere que aadamos un mtodo isNullt ) a todas nuestras clases to cual seria, despus de todo, simplemente otra forma de utilizar mecanismos RTTI, por qu no utilizar la funcionalidad integrada en su lugar?). //: typeinfo/Peraon.java // Una ciase con un objeto Nuil. import net.mindview.til. class Person ( public final String first; public final String last; public final String address; // etc. public Person(String first, String last, String address){ this.first * first; this.last = last; this.address = address;

) public String toStringO ( retum "Person; " firat " " -f last " " address;

) public static class NuilPerson extends Perscn implemento Nuil { private NullPersonO [ super{ "None", MNoneM# None"); } public String toStringO { retum "NullPerson"; )

14 Informacin de tioos 1060 ) public static final Person NULL new NullPerson(); J Mi-

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 Person es inmutable, slo podemos lijar los valores en el constructor, y luego leer dichos valores, pero no modificarlos (porque los propios String son inherentemente inmutables) Si desea modificar un objeto Nuil Person. slo puede sustituirle con un nuevo objeto Person. Observe que disponemos de la opcin de detectar el genrico Nuil o el ms especifico NullPerson utilizando instanceof. pero como se trata de un valor simple tambin podemos utilizar equals( ) o incluso = para comparar con Person,Nt i.L

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 maravillosa idea que hubiramos tenido Imagine que estamos listos para reclutar personal pero que. mientras esperamos a que las vacantes sean cubiertas, podemos utilizar objetos nulos Person para asignarlos a cada puesto de trabajo (Position); //: typemfo/Position. java class Position { prvate String title; prvate Person person; public Position(String jobTitle, Person employee) { title *= jobTitle; person employee; if(person =* nuil) peraon * Person.NULL;

i 14 Informacin de tipos 1061 public Pcsition(String jobTitle) { title jobTitle;person = Person.NULL; public String getTitle() { retum title; } public void setTitle(String newTitle) { title = newTitle;

) public Person getPersonO { retum person; ) public void setPerson(Person newPerson) ( person * newPerson; t (person == nuil) person = Person.NULL;

) public String toString() { retum "Position: ** t title r * u -t person;

) j ///-

i 14 Informacin de tipos 1062 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 si 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 diseo, > esperar a que algn aspecto del programa requiera que aadamos la caracterstica adicional, en lugar de asumir desde el principio que esa caracterstica es necesaria),28

La clase Staff puede ahora buscar los objetos nulos a la hora de rellenar las vacantes: //: typeinfo/Staff.ja va import java.til.*; public cla3s Staff extends ArrayList<Position> { public void add{String title, Person person) ( add(new Position(title, person)); l public void add(String... titles { for(String title : titles) addnew Position(title) l ;

} public Staff (String. . . titles( add (titiles); } public boclean positionAvailable(String titlel ( for(Position position : this) if(pasicion.getTitle() .equalft 11 iti 1 position.getPersonO -= Person.NULL) retum true; retum false;

fcsa ictnlctu u u Implcmcuiar Ij solucin mik simple posible cv utw de las nxomcnduciono de Exorm Pro^ranmlng |XP).
28

i 14 Informacin de tipos 1063 } public void fillPositioniString title. Person hire) ( for (Position position .* this) if (position .getTitle O .equals |title) t& oosition.qetPerson(l == Person.NULL) { position.setPerson<hre); retum; I throw new RuntimeExceptlon( "Position " * title - " not available")j

) public static void main{String(1 aras) ( Staff staff new Staffi "President", "CTO, Marketing Manager, "Product Manager'*, "Project Lead", "Software Engineer"f 'Software Engineer", "Software Engineer*, "Software Engineer", "Test Engineer", "Technical Writer"); staff -fillPosition("President", new Person("Me". "Last", "The Top. Lonely At")); staff , fillPosition ("Project Lead**, new Person ("Janet", "Planner", "The Burns"! > ,* if H1 staff.positionAvailable("Software Engineer")) staff.fillPosition("Software Engineer", new Person i"Bob", "Coder", "Bright Light City")!; System.out.println(staff);

i 14 Informacin de tipos 1064 } } / 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 existencia de objetos nulos en algunos lugares, lo cual no difiere mucho de comprobar la igualdad con el valor nuil, pero en otros lugares (como en las conversiones toString( ) , en este caso), no es necesario realizar comprobaciones adicionales; podemos limitamos a asumir que todas las referencias a objetos son vlidas.

Si estamos trabajando con interlaces en lugar de con clases concretas, es posible utilizar un objeto Dynamic Proxy para crear automticamente los objetos nulos. Suponga que tenemos una interfaz Robot que define un nombre, un modelo y una lista 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 diseo basado en comandos); //: typeinfo/Operation.java public interface Operation { String description{), void command () j ) ///:-

i 14 Informacin de tipos 1065 Podemos acceder a los sen icios de un objeto Robot invocando operation^ ) //: typeinfo/Robot.java import java.util.*; import net.mindvlew.util.#; public interface Robot ( String name()/ String model() ; Li st<Operation* operations() ; class Teat ( public static void test(Robot r) { iftr instanceof Null) System.out.printin("[Null Robot]; System.out.printIn("Robot name: " * r.name()|; System.out.println("Robot model: r.modelt)),* for(Operation operation : r.operations()) ( System.out.printIn(operation.descript ion()) ; operation.command();

i 14 Informacin de tipos 1066 ) ///:-

Esto incorpora tambin una clase anidada para realizar las pruebas.

Ahora podemos crear un objeto Robo! especializado. //! rypeinfo/SnowRemovalRobot.java import java.util,#; putoiic class SnowRerr.ova 1 Robot implements Robot ( private String name; public SnowRemovalRobot(String name) { this.name = name;} public String n3me() { return name; ) public String model() { return "SnowBot Series 11"; } public List<Operation> operations 0 { return Arrays.asList( new Operation I) { public String descriptionO [ return name *- M can shovel snow";

) public void commando ( System.out .println (name * M shoveling snow"};

i 14 Informacin de tipos 1067 I. )

new OperationO { public String description!) { retum name 4 " can chip ice*'/

} public void commando ( System, out .prmtln (name 4 " chipDing ice*); ). new Operation0 { public String description 0 { i retum name " can clear the roof";

public void command() ( System, out .println (name " clearing roof"};

i 14 Informacin de tipos 1068 );

) public static void main(String[J args) { Robot.Test.test(new SnowRemovalRobot("Slusher*));

) } /* Output: Robot name: Siusher Robot model: SnowBot Series 11 Slusher can shovel snow Slusher shoveling snow Slusher can chip ice Slusher chipping ice Slusher can clear the roof Slusher clearing roof ///:-

Existirn presumiblemente muchos tipos diferentes de objetos Robot, y lo que nos gustaru es que cada objeto nulo hiciera algo especial para cada tipo de Robot: en este caso, incorporar informacin acerca del tipo exacto de Robot que el objeto nulo representa. Esta informacin ser capturada por el pwxy dinmico: //: typeinfo/NuiIRobot.j ava // Utilizacin de un proxy dinmico para crear un objeto nuloimport } ava.lang.reflect.*; import java.util.*; import net.mindview.util; class NullRobotProxyHandler implements

i 14 Informacin de tipos 1069 InvocationHandler { private String nullName; private Robot proxied * new NRobotf); ) NullRobotProxyHandler(Class<? extends Robot> typel { nullName = type.getSimpleName() * M NullRobot";

private class NRobot implements Null, Robot ) public String nameO ( return nullName,- ) public String modelO ( return nullName; ) public List<Operation> operations 0 ( return Collections.emptyList();

) public Object invoke(Object proxy. Method method, Object[] args) throws Throwable ( return method.invoke(oroxied. args);

i 14 Informacin de tipos 1070 } public class NullRobot ( public static Robot newNuilRobot(Class<? extends Robot typei { return I Robot)Proxy.newProxylnstance( NullRobot.class.getClassLoader(), new Class[]( Null.class. Robot.class ), new NullRobotProxyHandler(typeJ J;

) public static void main(String H args? ( Robot IJ bots * { new SnowRemovalRobot(HSnowBeeHI, newNuilRobot(SnowRemovalRobot.class I for(Rcbot bot : bots) Robot.Test.test(bot);

> J / Output: Robot name: SnowBee Robot model: SnowBot Series 11 SnowBee can shovel snow SnowBee shoveling snow SnowBee can chip ice SnowBee chipping ice SnowBee car* clear the roof SnowBee clearing roof [Null Robotl Robot name: SnowRemovalEobot NullRobot Robot model : SnowRemovaiRobot NullRobot

14 Informacin de tipos 1071 Cuando se necesita un objeto Robot nulo, simplemente se invoca nouNullRobot( ), pasndole al mtodo el tipo de Robot para el que queremos que actu como proxy. HI proxy* satisface los requisitos de las interlaces Robot y Null, v proporciona el nombre especfico para el que acta como proxy.Objetos maqueta y stubs

May do tipos variantes del objeto nulo: el objeto maqueta y el xtub. Al igual que el objeto nulo, ambos tipos de objetos se utilizan en lugar del objeto real que empicara el programa terminado. Sin embargo, tanto el objeto maqueta como el srub pretenden ser objetos vivos que entregan informacin real en lugar de ser un sustituto un poco ms inteligente de nuil, como es el caso del objeto nulo.

La diferencia entre el objeto maqueta y un stub es bastante sutil Los objetos maqueta tienden a ser ligeros (poco complejos) y tienen capacidad de auto-comprobacin,, y usualmente se crean muchos de ellos para gestionar distintas situaciones de prueba. Los stubs son tpicamente ms pesados y a menudo se reutilizan entre uno prueba y otra Los stubs pueden configurarse para cambiar de comportamiento, dependiendo de cmo se los invoque. Por tanto, un srub es un objeto sofisticado que lleva a cabo una de esas tareas; mientra que para hacer esas mismas tareas con objetos maqueta lo que normalmente haramos es crear muchos objetos maqueta pequeos y simples Ejercicio 24: (4)Aada objetos nulos a RegisteredFactories.java.

Interfaces e informacin de tipos

Un objetivo clave de la palabra clave interfacc os permitir al programador aislar componentes, reduciendo asi el acoplamiento. Si escribimos el cdigo basndonos en interfaces, conseguimos este objetivo, pero con la informacin de tipos es posible saltarse los controle; las interfaces no son una garanta de desacopainiento. He aqui un ejemplo comenzado con una interfaz: / ': typemfo/interfacea/A. java package typemfo.interfaceaj pubLic infer a ce h { void f O

14 Informacin de tipos 1072 /

) ///:

Bsta interfaz se implemento a continuacin y podemos ver fcilmente cmo saltamos los controles para obtener el tipo real de implementacton: //* typeinfo/XnterfaceViolatior*. java // Sorteando una interfaz, import typeinfo.interfacea,, elass B impiements { public void f() {) public void gO () l public class InterfaceVioIation { public etatic void main{StringiJ args) { A a * new BO ; a.f Os // a.gOj ff Error de compilacin System.out.println(a.getClass O. ge t amef]); ifla instanceof B\ ( 3 b = (B; a b.g O ;

14 Informacin de tipos 1073 > ) / Output:

*///:

Utilizando RTTl, descubrimos que u ha sido implemeniado como B. Proyectando sobre B. podemos invocar un mtodo que no se encuentre en A.

Esto es perfectamente legal y aceptable, pero puede que no queramos que los programadores de clientes hagan esto, ya que esto les da la oportunidad 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 asi. y el hecho de que utilicemos B para implemcntar A en este caso es algo de dominio pblico.29

EJ caso ms famoso es cl sistemo operativo Windows, que tenia una API publica con la que se uponia que haba que desarrollar programas y un conjunto no publicado pao visible de funciones que podame descubrir e invocar Par resolver los problemas, lo\ programndore utilizaban las funcione* oculta de la API, lo que lori a Microsoft a mantenerla* como si fueran pane de la API pblica. Esto *e convirti en una fuente de grande* costes y de ciuimir trabajo pan la empresa
29

14 Informacin de tipos 1074 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- \ ene aplicar otros controles ms estrictos.

La tcnica ms sencilla consiste en utilizar acceso de paquete para la implementacin. de modo que los clientes situados fuera del paquete no puedan verla:

//: typeinfo/packageacces3/HiddenC.java package typeinfc.packageaccesa,- import typeinfo.interfacea.; import static net .mindview.til.Print. * ,* ciass C implements A ( public void f() ( print "public C.fUMy ) publie void gO j print("public C.g()*l ) void u) ( print(package C.U()") ) protected void vil { print("protected C.v()"|; } prvate void w<) { print(private C.W(I") }

) public class KiddenC { public static A makeAM ( retum new C / )

14 Informacin de tipos 1075 ) ///:-

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 makcA( ). seguiramos sin poder utilizar ninguna otra cosa distinta de A desde fuera del paquete, ya que no podemos nombrar C fuera del paquete.

Ahora, si tratamos de efectuar una especiahzacin sobre C\ no podemos hacerlo, porque no hay ningn tipo *C* disponible fuera del paquete: //: typeinfo/Hiddenlmplementation.java // Sorteando el acceso de paquete, import typeinfo.interfacea.; import typeinfo.packageaccess.*; import java.lang.reflect. public class Hlddenlmplementation ( public static void mair. (String U args) throws Kxception { A a * HiddenC.makeA() a.f (); System.out.printIn(a.aetClass{).getName()>; // Error de compilacin: no se puede encontrar el smbolo C: /* ifa lnstanceof Cl { Ce* (C)aj c . g O ) / // |Caramba 1 La reflexin nos permite invocar glJ: callHiddenMethod (a, ** g NMI; // iE incluso mtodos que son menos accesibles!callHiddenMethoda4 callHiddenMethod(a, "v" J ; callHiddenMethod (a, "w1); l

14 Informacin de tipos 1076 static void callHiddenMethod(Object a, String methodName) throws Exception ( Method q = a .getClass O.. getBeclaredMethod(methodName) ; g.setAccessible(true); g.invcke(a); )' | /* Output: public C.fO typeinto.packageaccess.C public C,g() package C.uO protected C.vO prvate C.w()

V//s-

Corno puedo ver. sigue siendo posible meterse en las entraas e invocar iodos los mtodos utilizando el mecanismo de reflexin ; Incluso ios mtodos privados! Si se conoce el nombre del mtodo, so puede invocar setAccessible(true) sobre el objeto Method para hacerlo invcatele, como podemos ver en callIIddcnNlethod( ).

Podramos pensar que es posible impedir esto distribuyendo slo el cdigo compilado, pero no es una solucin. Basta con ejecutar javap. que es el descompilador incluido en el JDK He aqu la linea de comandos necesaria: javap -privte C

14 Informacin de tipos 1077 El indicador -prvate especifica que deben mostrarse iodos los miembros, incluso los privados. He aqu la salida que se obtiene: class typeinfo.packageaccess.C extends java.lang.Object implements typeinfo.nterCacea.A ( typeinfo.packageaccess.CO ; public void fO; public void g O; void u{) i protected void vil; prvate void wO*

Por tanto, cualquiera puede obtener los nombres y signaturas de los mtodos ms privados e invocarlos Qu sucede si implementamos la interfaz con una clase interna privada? lie aqu un ejemplo: //: typeinfo/Innerlmplementation.java // Las clases interna privadas no pueden ocultarse del mecanismo de // reflexin. import typeinfo.interfaces.; import static net.mindview.util.Print.* ; class InnerA ( prvate static class C implements A ( public void fO ( print(public C.f()"); } public void g() ( print ("public C.gO")/ } void uO ( print (''package C.uO"); ) protected void v() { print ( protected C.vO"l; } private void w(} ( printI"privte C.w()"); )

14 Informacin de tipos 1078 )

}public class Innerlmplementation [ public static void main(StringU args) throws Exception ( A a * InnerA. makeA 1) / a..U } Syscetn.out. print IR'a. get Class () .getName1 // La reflexin sigue permitiendo entrar en la clase privada: Hlddenlmpletnentaton.callHidenMethoda. "g"); Hiddenlmplementation.callHiddenMet'nod <a, "u") ; Hiddenlmpiementation.callHiddenMethodla. V) / ) HiddenImplementation.callHiddenMethod(a. "w");

) /* Output: public C.fU InnerA$C public C.gO package C.uO protecte d C.vO private C.w)

*///:-

14 Informacin de tipos 1079 Esta solucin no nos ha permitido ocultar nada a ojos del mecanismo de reflexin. Que sucedera con una clase annima? // : typeinfo/AnonymousImplementation. java // Las clases internas annimas no pueden ocultarse del mecanismo de // reflexin. import typeinfo.interfacea.*; import static net.mindview.til.Print. *; class AnonymousA { public static A makeAO ( retum new A() { public void t (> ( print("public C.f()")j } public void g() ( print"public C.gO1*); ) void u() ( print("package C.u()"); ) protected void v() { print ("protected C.vO"); ) private void w() { print("prvate C.wO"); )

} public class Anonymouslmplementation { public static void main(StringU args! throws Exception { A a AnonymousA.makeA()/ a.f(); System.out.println(a.getClass().getName)); //La reflexin sigue pudiendo entrar en la clase annima: HiddenImplementation.callHiddenMethod{a "g") Hiddenlmplementation.callHiddenMethodia, nun); Hiddenlmplementation.callHiddenMethod(a, "v"i; Hiddenlmplementation.callHiddenMethodU, "w"> ;

14 Informacin de tipos 1080 i } /* Output: public C.f() Anonymou sA$l public C.gO package C.uO protecte d C.vO private C.w()

///:

14 Informacin de lipos 1081 -parece que no existe ninguna forma de impedir que el mecanismo de reflexin entre e invoque los mtodos que no tienen acceso pblico. Esto tambin se cumple pan los campos, incluso para los campos privados: //: typemfo/ModifyingPnvateFields .java nport java. lang. reflect. *; clas3 WithPrivateFinalField ( prvate me 1 = 1 ; prvate final String a = "I'm totally safe; prvate String s2 = "Am 1 safe?"; public String coStringO ) retum "i * " i ", " s ", " + s2

public class ModifyingPrivateFields { public static void main(StringU args) throws Exception { WithPrivateFmal Field pf = new WithPrivateFinalField (} ? System.out.println(pfI; Field f = pf.getClass().getDeclaredField*"i"); f.setAccessible(true); System.out.printIn("f.getInt (pf) : H * f.getInt(pf)); f.setlnt(pf, 47); System, out .println (pf) f = pf.getClassO.getDeclaredField("s"); f.setAccessible(true); System.out.println("f.get(pf) : " f.get(pf)); f.set(pf, "No, you're not i*'); System.out.println(pf); f = pf .getClassO .getDeclaredField("s2") ; f.setAccessible(true); System.out.printlnI"f.get(pf): " * f.get(pf)); f.set(pf, "No, you're not!); System.out.println(pf); i ) /* Output:
1

= 1, I'm totally safe. Am I

14 Informacin de lipos 1082 safe? f.getlnt(pf): 1 i * 47, I'm totally safe. Am I safe? f.get(pf): I'm totally safe i - 47, I'm totally safe, Am I safe?
f.

get(pf): Am I safe?

i = 47, I'm totally safe, No, you're not!

///:-

Sin embargo, los campos de tipo final si que estn protegidos frente a los cambios. El sistema de tiempo de ejecucin acepta 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 ditlcil que esas personas puedan quejarse si decidimos cambiar posteriormente algunos aspectos de esos mtodos. Por otro lado, el hecho de que siempre exista una puerta trasera para entrar en una clase nos permite resolver ciertos tipos de problemas que en otro caso seran difciles o imposibles, y los beneficios del mecanismo de reflexin son, por regla general, incuestionables.

14 Informacin de lipos 1083 Ejercicio 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. Resumen

RTT1 nos permite descubrir 1a informacin 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 llamadas polimrficas a mtodos. Para las personas que tienen experiencia previa en lenguajes procedimentales, resulta difcil organizar los programas en conjuntos de instrucciones switch. liste tipo de estructura puede impleinentarse fcilmente con RTTI perdindose as el importante valor que el polimorfismo aade al desarrollo y el mantenimiento del cdigo. 1 a intencin de la programacin orientada a objetos es utilizar llamadas polimrficas a mtodos siempre que se pueda y RTTI slo cuando no haya mas 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 descubrir que la clase base no incluye el mtodo que necesitamos. Si la clase base proviene de una biblioteca desarrollada por algn otro programador, una solucin es RTTI: podemos heredar un nuevo tipo y aadir el mtodo adicional que necesitamos. hn el resto del cdigo podemos entonces detectar ese tipo concreto que hemos aadido y llamar a ese mtodo especial. Esto no destruye el polimorfismo y la extensibilidad del programa, porque el aadir un nuevo tipo no requiere que andemos a la caza de instrucciones switch en nuestro programa. Sin embargo, cuando aadamos cdigo que dependa de la nueva funcionalidad aadida, nos veremos obligados a utilizar RTTI para detectar el tipo concreto que hayamos definido

14 Informacin de lipos 1084 Aadir una funcionalidad en una dase base puede implicar que. a cambio de obtener exclusivamente un beneficio en esa clase concreta, todas las dems clases derivadas de la misma debern cargar con un esqueleto de mtodo completamente carente de significado. Esto hace que la interfaz sea menos clara y resulta bastante molesto para aquellos que se ven obligados a sustituir mtodos abstractos cuando derivan otra clase a partir de esa clase base. Por ejemplo, considere una jerarqua de clases que representa instrumentos musicales. Suponga que desea limpiar las vlvulas de las boquillas de lodos los instrumentos apropiados de su orquesta. Una opcin es incluir un mtodo linipiarVaIvola() en la clase base Instrumento, pero esto resulta confuso, porque implicara que los instrumentos de Percusin. Cuerda y Electrnicos tambin tienen boquillas y vlvulas. RTTI proporciona una solucin mucho ms razonable, porque nos permite colocar el mtodo en la clase especifica donde resulta apropiado (Viento, en este caso). Al mismo tiempo, podemos descubrir que existe una solucin ms lgica, que en este caso consistira en incluir un mtodo prepararlnstrumento( ) Sin embargo, puede que no veamos esa solucin cuando estemos tratando por primera vez de resolver el problema y. como consecuencia, podramos asumir errneamente que es necesario utilizar RTTI.

Finalmente. RTI'I permite en ocasiones resolver problemas de eficiencia. Suponga que nuestro cdigo utiliza apropiadamente el polimorfismo, pero resulta que uno de los objetos reacciona a este cdigo de propsito general de una manera terriblemente poco eficiente. Podemos detectar ese tipo concreto de objeto utilizando RTTI > escribir cdigo especfico para mejorar la eficiencia. No caiga en la tentacin, sm embargo, de estructurar sus programas demasiado pronto pensando en la eficiencia Se traa de una liampa bastante tentadura. Lo mejot o conseguir primen que el programa funcione y luego decidir si esta funcionando lo suficientemente rpido. Slo entonces deberemos abordar los problemas de eficiencia con una herramienta de perfilado (consulte el suplemento en http: MinilView.net'fouks BelterJava),

Tambin hemos visto que el mecanismo de reflexin abre un nuevo mundo de posibilidades de programacin, permitiendo un estilo de programacin mucho ms dinmico. Existen programadores para los que la naturaleza dinmica del mecanismo de reflexin resulta bastante perturbadora. El hecho de que podamos hacer cosas que slo pueden comprobarse en tiempo 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 personas 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 segundad 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 contenga ningn bloque trv ni ninguna especificacin de excepcin. En lugar de ello, en mi opinin, la existencia de un mode

14 Informacin de lipos 1085 lo coherente de informacin de errores nos permite escribir 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-H-.

Ejercicio 26: (3) Implcmentc un mtodo UmptorValvula( ) como el descrito en este resumen Puede encontrar las Menciones a los ejercicio aleccionado* en el documento electrnico 77/r TUinkmg in Java AmwtunulSolunon (Juitk. disponible para la >enta en wmv, MndMe^'.nei

14 Informacin de lipos 1086

Genricos

1 5

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.1

Una de las formas en que los lenguajes orientados a objetos permiten la generalizacin es a travs de! 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 especifico, un tipo base proporcionar mayor flexibilidad. Por supuesto, podemos extender todas las clases salvo aquellas que hayan sido definidas como finales2, por lo que esta flexibilidad se obtiene de manera automtica la mayor parte de las veces

14 Informacin de lipos 1087 Hn 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. F.sto proporciona al programador Je clientes la opcin de implementar una interfaz para adaptarse a nuestra clase o mtodo. Con esto, las interfaces nos permiten establecer un vinculo entre jerarquas de clases, siempre y cuando tengamos la opcin de crear una nueva clase para implementar ese vinculo.

Algunas veces, incluso una interfaz resulta demasiado restrictiva. Las interfaces siguen requiriendo que nuestro cdigo funcione 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.

Fn esto se basa el concepto de genricos, un cambio de los ms significativos en Java SL5. Los genricos implemcntan el concepto de tipos parame trizados, que permiten crear componentes (especialmente contenedores) que resultan fciles de utilizar con mltiples tipos. El trmino "genrico significa perteneciente o apropiado para grandes grupos de clases. La intencin original de los genricos en los lenguajes de programacin era dolar al programador de la mayor capacidad expresiva posible a la hora de escribir clases o mtodos, relajando las restricciones que afectan a los tipos con los que esas clases

omtodos pueden funcionar. Como veremos en este captulo. la implementacin de los genricos en Java no tiene un alcance tan grande; de hecho, podramos cuestionamos si el trmino genrico resulta siquiera apropiado para esta funcionalidad de Java.

14 Informacin de lipos 1088 Si no ha visto antes ningn mecanismo de tipos parametnzados. los genricos de Java le parecern, probablemente, una mejora sustancial del lenguaje. Cuando se crea una instancia de un tipo parametrizado. 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.

1089 Piensa en Java 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 gencricos nosencontraremos con diversas sorpresas. Uno de los aspectos que trataremos de explicar en este capitulo son los motivos por los que la funcionalidad 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 lector ha utilizado anteriormente algn lenguaje donde est implementada una v ersin ms pura de los genricos, puede que la solucin de Java le desilusione. En este capitulo, v amos 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 manera 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 O-, v en este libro hemos intentado hacerlo as, salvo en aquellos casos en los que la comparacin puede facilitar entender mejor el lenguaje.

public class Holder2 (

1090 Piensa en Java Los genencos requieren que realicemos una comparacin mas detallada con C++ por dos razones. En primer lugar, comprender cienos aspectos de las plantillas C^4- (la pnncipal inspiracin de los genricos, incluyendo su sintaxis bsica) nos pemiitir entender los fundamentos del concepto, asi como (y esto es particularmente importante) las limitaciones que afectan a lo que se puede hacer con los genricos de Java, y los motivos subyacentes de la existencia de esas limitaciones. El objetivo ltimo es que el lector comprenda claramente dnde estn los limites, porque entendiendo esos limites se puede llegar a ser un programador ms eficiente. Sabiendo lo que no puede hacerse, podemos emplear mejor aquellas cosas que si podemos hacer (en parte porque no nos vemos obligados 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 0+, y estos conceptos errneos pueden aumentar nuestra confusin acerca del objetivo de los genricos.

Por tanto, vamos a introducir unos cuantos ejemplos de plantillas 0+ en este captulo, aunque tratando siempre de limitar al mximo las explicaciones acerca del lenguaje O4-. 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 Capitulo 11. Almacenamiento de objetos (hablaremos ms acerca de estas clases en el Capitulo 17. Anlisis detallado de los contenedores). lln public class Holder2 (

1091 Piensa en Java 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 flexibles \ sus caractersticas son distintas a las de las matrices simples. Casi todos los programas requieren que almacenemos un gmpo de objetos mientras los utilizamos, por lo que los contenedores son una de las bibliotecas de clases ms inherentemente icutilizables

Examinemos una clase que almacena un nico objeto. Por supuesto, la clase podra especificar el tipo exacto del objeto de la fonna siguiente: //: generies/Holderl.java class Automobile {) public class Holderl { prvate Automobile a; public Holderl(Automobile a) { this.a * a; ) Automobile get O ( return a,* )

) ///:-

Pero esta herramienta no es muy reutilizable, 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. public class Holder2 (

1092 Piensa en Java Antes de Java SE5. lo que haramos simplemente es hacer que la clase almacenara un objeto de tipo Object: //: qenencs/Holder2. javaprivate Object a; public Holder2(Object a) | tbis.a * a; } public void setiObject a) ( this.a * a; ) public Ob^ect getI) ( return a; ) public static void main (Stringi! args) { Holder2 h2 = new Holder2lnew Automobilel)); Automobile a * (Automobile! h2 .get 0 .* h2.seti"Not an Automobile'); String s - (String)h2 .get () f* h2.set(l); // Se transtorma automticamente en Integer Integer x * (Inteqer)h2.aet( ) ;

] ///:-

public class Holder2 (

1093 Piensa en Java Ahora, la clase Holder2 puede almacenar cualquier cosa v, en este ejemplo, un nico objeto Holder2 almacena tres tipos distintos 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 compilador.

Por tanto, en lugar de emplear Objcct, lo que querramos es poder utilizar un tipo no especificado, lo que podremos decidir en algn momento posterior. Para hacer esto, incluimos un parmetro de upo entre corchetes angulares despus del nombre de la clase y luego, al utilizar la clase, sustituimos esc parmetro por un tipo real. Para nuestra clase contenedora anterior, la tcnica consistira en lo siguiente, donde T es el parmetro de tipo: //t aenerics/Holder3.java public clasa Holder3<T> { private T a; public public public static Holder3(7 a) ( this.a = a; } void set T a) { thi9.a = a; } T get0 ( return a; ) public void main(String[] args) {

Holder3<Automoblle> h3 new Holder3<Automobile>Inew AutomobilelI); Automobile a h3.get): // No hace falta proyeccin // h3.set(*Not an Automobile11); // Error // h3.setll)j // Error i public class Holder2 (

1094 Piensa en Java ) ///:-

Ahora, cuando creemos un objeto Holder3. deberemos especificar el tipo que queramos almacenar en el mismo, utilizando la sintaxis de corchetes angulares, como puede verse en main( ). Slo podemos introducir en el contenedor objetos de dicho upo (o de alguno de sus subtipos, ya que el principio de sustitucin sigue funcionando con los genricos). Y al extraer del contenedor un valor, dicho valor tendr automticamente el tipo correcto

lista es la idea fundamental de los genricos de Java: le decimos al compilador qu tipo queremos usar y el compilador se encaTga de los detalles.

En general, podemos tratar los genricos como si fueran otro tipo ms que en lo nico que se diferencia de los tipos normales es en que tiene parmetros de tipo. Pero, como veremos en breve, podemos utilizar los genricos simplemente nombrndolos junto con su lista de argumentos de tipo.

Ejercicio 1: ( l ) Utilice Holdcr3 con la biblioteca typelnfo.pets para demostrar que un objeto public class Holder2 (

1095 Piensa en Java Holder3 que se haya

especificado para almacenar un tipo base, tambin puede almacenar un tipo derivado.

Ejercicio 2: (l) 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. Una biblioteca de tupias

Una de lus cosas que a menudo hace falla hacer es devolver mltiples objetos de una llamada a mtodo. La instruccin relurn slo permite especificar un nico objeto, por lo que la respuesta consiste en crear otro objeto que almacene los mltiples objetos que queramos devolver. Por supuesto, podemos escribir una clase especial cada ve/ que nos encontremos con esta situacin, pero con los public class Holder2 (

1096 Piensa en Java genricos es posible resolver el problema de una vez y ahorramos un montn de esfuerzo en el futuro. Al mismo tiempo estaremos garantizando la seguridad de los tipos en tiempo de compilacin.

F.ste concepto se denomina lupia, y consiste simplemente en un grupo de objetos que se envuelven juntos dentro de otro

objeto nico. El receptor del objeto estar autorizado a leer los elementos, pero no a introducir otros nuevos leste concepto tambin se conoce como Objeto Je transferencia tic Jatos o Mensajero).

Las tupias pueden tener, normalmente, cualquier longitud, y cada objeto de la rupia puede tener un tipo distinto. Sin embargo. lo que nos interesa es especificar el tipo de cada objeto y garantizar que. cuando el receptor lea los valores, obtenga el tipo correcto. Para tratar con el problema de las mltiples longitudes, podemos crear mltiples tupias diferentes. Me aqui una que almacena dos objetos: //: net/mindview/ut il/TwoTuple. j ava package netmindview.til; public class TwoTuple<A, public class Holder2 (

1097 Piensa en Java B> ( public final A first; public final B second public TwoTuple (A a, B b) ( first = a; second * b; ) public String toString() ( I retum *M" f first ", H * second * ) " ;

} ///:-

El constructor captura el objeto que hay que almacenar y toString() es una funcin de utilidad que permite mostrar los valores de una lista. Observe que una tupia conserva implcitamente sus elementos en orden.

Al examinar el ejemplo por primera vez. podra pensarse que viola los principios comunes de seguridad en la programacin Java. No deberan ser first y second privados, y no debera accederse a ellos nicamente con los mtodos denominados getFirst ) y gctSecond( )? Considere la seguridad que se obtendra en dicho caso: los clientes podran seguir leyendo los objetos y hacer lo que quisieran con los mismos, pero no podran asignar first o second a ninguna otra cosa. La declaracin final nos proporciona esa misma seguridad, pero la forma empleada en el ejemplo es ms corta y ms simple

public class Holder2 (

1098 Piensa en Java Otra observacin de diseo importante 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 mas seguro dejar el ejemplo tal cual est, y limitarse a obligar al usuario a crear un nuevo objeto TwoTuple si desea disponer de uno que tenga diferentes elementos.

Las tupias de mayor longitud puede crearse mediante herencia. Como podemos ver. aadir ms parmetros de tipo resulta bastante simple: //: net/mindview/util/ThreeTuple.java package net.mindview.til; public class ThreeTuplecA,B,C> extends TwoTuple<A,B> { public final C third; public ThreeTupie (A a. B b, C o) { superU, b) ? third c;

} public String toStringO ( retum " {" + first + ", M second , H * third -)**;

} i ///=// : net/mmdview/util/FourTuple. java package net.mindview.til; public class Holder2 (

1099 Piensa en Java public class Four7uple<A,B,C,D> extends ThreeTuple<A,B,C> ( public final D fourth; public FourTupletA a, B b. C c, D d) { super(a, b, c); fourth a d;

} public String toStringO ( return* first * ", " second + " , M third * ", M -f fourth * M) M;

| ///://; net/mindview/util/FiveTuple.java package net.mindview.util; public class FiveTupIe<A,B,C,D,E extends FourTuple<A.B.C.D { public final E fifth; public FiveTupletA a, B b, Cc, D d, Ee) { super(a, b, c, d) ; fifth = e;

public class Holder2 (

1100 Piensa en Java ) public String toStringl) { return * (" + first + ", " 4second + *, " + third - ", " fourth -r ", " fifth *

) I ///:Para utilizar una tupia, simplemente definimos la tupia de la longitud adecuada como valor de retomo para nuestra funcin y luego la creamos y la devolvemos en la instruccin return: //: generics/TupleTest.java import net.mindview.util.; class Amphibian {) class Vehicle {) public class TupleTest { static TwoTupiecStrmg, Intger> f|) { / / El mecanismo de autoboxing convierte int en Integer: return new TwoTuple<String, Integer ("hi*', 47); static ThreeTuple<Amphibian,String,Integer> g<) return new ThreeTuple<Amphibian, String, Integer>( new AmphibianO, "hi", 47); {

) public class Holder2 (

1101 Piensa en Java static FourTuple<Vehicle,Amphibian,String,Integ ers h<> { return new FourTuplecVehicle,Amphibian,String ,Integer( new VehicleO, new Amphibian() , "hi", 47);

} static FiveTuple<Vehicle,Amphibian,String,Integ er,Double k [ ) { return new FiveTuple<Vehicle,Amphibian,String,In teger,Double t new Vehicle!), new AmphiblanO, "hi". 47, 11.1);

public static void main(String{) args) J TwoTuple<String,Integer ttsi = f() public class Holder2 (

1102 Piensa en Java ;System.out.println(ttsi); // ttsi.first * "there"; // Error de compilacin: final System.out.println <g< ) } ; System.out .prmtln (h( ) ) ; System.out.println lk()J ;

) } / Output: {80% match)

fhi, 47J lAmphibianlf6a7b9, hi, 47) |Vehicle#35ce36, Amphibianfr757aef, hi, 47) {Vehicle29cabl6, Amphibian'jSla46e30, h, 47, 11.1)

///:-

1103 Piensa en Java Gracias a los genricos, podemos crear fcilmente cualquier tupia 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. Posteriormente en el captulo veremos cmo simplificarlas utilizando mtodos genricos.

Ejercicio 3:(I) Crce y pruebe un genrico Six Tupie con seis elementos Ejercicio 4:(3) Recscriba innerclasscs/Sequence.java utilizando genricos. Una clase que implementa una pila

1104 Piensa en Java Examinemos algo ligeramente ms complicado: la tpica estructura de pila. En el Capitulo 11, Almacenamiento de objetos, vimos cmo implementar una pila utilizando un contenedor LinkedList en la clase net.mindview.utll.Stack. En dicho ejemplo, podemos ver que Linkedl.ist ya dispone de los mtodosnecesarios para crear una pila. La clase Stack que imple-

mentaba la pila se construy componiendo una clase genrica (Stack<T>)con clase genrica (LinkedlJst<T>)

otra En

dicho ejemplo, observe que (con unas cuantas excepciones que posteriormente realizaremos) un tipo genrico no es otra cosa que un tipo normal.

En lugar de utilizar LinkedList. podemos implementar nuestro propio mecanismo interno de almacenamiento enlazado. //: generics/LinkedStack.java // tina pila implementada con una estructura enlazada interna. public class LinkedStack<T> ( prvate static class Node<U> ( U itera; Node<U> next; NodeO { item nuil; nexr *= nuil; ) i

1105 Piensa en Java Node item, Node<U> next) ( this. item * item; this.next = next r i boolean end() ( retum item nuil && next == nuil; )

) private Node<T> top = new Node<T>(); // Indicador de fin public veid push(T item) ( top = new Node<T>(item, top);

) public T pop() {

T result * top.item; if ( !top.endI)) top = top.next; retum result ;public static void mam (String [I args) ( LinXedStack<String> las neW LinkedStack<String>(J ; fo? (String s : "Phasers on stuni".splt{" M>> lss.push ts) i String s; i

1106 Piensa en Java whileUs ^ Iss.oopOI 1= nuil) System.out .prmtln(s) j

) } i Output: stun! on Phasers

W/s~

La clase interna Node tambin es un genrico y tiene su propio parmetro de tipo.

Este ejemplo hace uso de un indicador de fin para determinar cundo la pila est vacia. El indicador de fin se crea en el momento de construir el contenedor Linked.Stack. y i

1107 Piensa en Java 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 descana el objeto Node<T> actual y nos desplazamos al siguiente, excepto cuando encontramos el indicador de fin. en cuyo caso no nos desplazamos De esa forma, si el cliente contina invocando pop( ). obtendr como respuesta valores nuil para indicar que la pila est vaca.

Ejercicio 5: (2) Elimine el parametro de tipo en la clase Node y modifique el resto del cdigo en l.inkedStack.java

para demostrar que una clase interna tiene acceso a los parmetros de tipo genrico de su clase externa RandomList

Como ejemplo adicional de contenedor, suponga que queremos disponer de un tipo especial de lista que seleccione aleatoriamente uno de sus elementos cada vez que invoquemos selcct( ). Al hacer esto, podemos construir una herramienta que funcione para todos los objetos, asi que utilizamos genricos: //: generics/RandomList.java mport java.til.#; public class RandomList<T ( private ArrayList<T> storage i

1108 Piensa en Java new ArrayList<T>(); private Random rand = new Random(47); public void add(T tem) { storage.add(itcm) ; public T selectO ( i recum storage.get(rand.nextlnt(storage.size(J));

public static void main(Stringf] args) { RandomListcString> rs new RandomList<String>O; for(String s: ("The quick brown fox jumped over Mthe lazy brown dogM).split(" *)) rs.add(s); forUnt 1 0; i < 11; i++) System.out .prmt (rs.select O * " ;

) } /* Output: brown over fox quick quick dog brown The brown lazy brown

*///-

1109 Piensa en Java Ejercicio 6: (I) Utilice RandomList 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 prctica. una especializaein del patrn de diseo basado en el mtodo de factora. pero cuando pedimos a un generador que cree 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 sin ninguna informacin 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: // : net/mindview/util/Generator.ja va // Una interfaz genrica, package net .mo.ndview.util; pufalic interface Generator<T> | T nextl); ) ///:El tipo de retomo de next() se parametriza como T. Como puede ver. la utilizacin de genricos con interfaces no es diferente de la utilizacin de genricos con clases.

1110 Piensa en Java Para ilustrar la miplementacin de un objeto Generator. necesitaremos algunas clases. He aqu una jerarqua de ejemplo //: generics/coffee/Coffee.]ava package generics.coffee; public class Coffee ( private sratic long counter = 0; private final long id = counter-*-*; public String toStrlngO { retum getClass () .getSimpleName {) -* '* M + id; i } m-.t i : generics/coffee/Latte.java package generics.coffee; public class Latte extends Coffee () ///://: generics/cof fee./Mocha. lava package generics.coffee; public class Mocha extends Coffee (J ///://: generics/coffee/Cappuccino.jav a package generics.coffee; public class Cappuccmo extends Cc-ffee () ///://: generics/coffee/Americano.java package generics.coffee; public class Americano extends Coffee () ///;//: generics/coffee/Breve.java package generics.coffee;

1111 Piensa en Java public class Breve extends Coffee () ///:- Ahora, podemos implementar un objeto Gcnerator<Coffcc> que genera aleatoriamente diferentes tipos de objetos Coffee. //: generics/coffee/CoffeeGenerator.java // Generar diferentes tipos de objetos Coffee: package generics.coffee; import java.util,*; import net.mindview.ut11.*; public class CoffeeGenerator implements Generator<Coffee>, IterablecCoffee> { private ClassH types { Latte.class. Mocha.class, Cappuccino.class, Americano.class. Breve.class, ); private static Random rand = new Randotn(47); public CoffeeGenerator() () // Para iteracin: private int size = 0; public CoffeeGenerator(int sz) { size - sz; } public Coffee next() { try { return (Coffee) types[rand, next Int(types.length)}.newTnstance(); I f Informar de errores del programador en tiempo de ejecucin: ) catch(Exception el | ii throw new RuntimeExceotion(e)/

class Coffeelterator implements Iterator<Coffee> ( int count size; public boolean hasNext() ( return count >0; } public Coffee nextO { count - -; i

1112 Piensa en Java return C o t feeGenerator.this.next<);

) public void remove()( // No implementado throw new UnsupportedOperaticnException{);

) 1; public Iterator<Coffee> iterator() ( return new Coffeelterator(); ) public static void main (String IJ args) { CoffeeGenerator gen = new CoffeeGenerator(>; for(int i = 0; i < 5; i+ +) System.out.printIn(gen.next()); for(Coffe c new CoffeeGenerator(5)| System.out.printIn(c);

1113 Piensa en Java } } / Output: Americano 0 Latte 1 Americano 2 Mocha 3 Mocha 4 Breve 5 Americano 6 Latte 7 Cappuccin o 6 Cappuccin o 9 ///:-

La interlay Generator parametrizada garantiza que next( ) devuelva el tipo definido en el parmetro. CoffceCenerator tambin implementa la interfaz Itcrablc. por lo que se le puede usar en una instruccin famich. Sin embargo, requiere un indicador de fin" para saber cundo parar, y esto se crea utilizando el segundo constructor.

He aqui una segunda implemcntacin de enerator<T>. que esta vez se utiliza para generar nmeros de Fibonacci: //; generics /Fibonacci .java Generar una secuencia de Fibonacci, import net.mindview.util .;
II

public class Fibonacci implements Ger.erator<Integer> ( private int count * 0; i

1114 Piensa en Java public Integer nextO ( return fib (count ) ; } private mt fib (int n) { if(n < 2) return 1; return fib(n*2) 4 fibfn11;

) public static vold rnain(String(J args) { Fibcnacci gen = new Fibonacci( ) formt i 0; i < 18; i*+) System.out.print (gen.next O *- " ");

) ) /* Output: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2SB4 *///:-

Aunque estamos trabajando con valores int tanto dentro como fuera de la clase, el parmetro de upo es Intcger. Esto nos plantea una de las limitaciones de los genricos de Java. No se pueden utilizar primitivas como parmetros de tipo. Sin embargo. Java SE5 i

1115 Piensa en Java ha aadido, afortunadamente, la funcionalidad de conversin automtica entre primitivas y tipos envoltorio. para poder efectuar las conversiones fcilmente. Podemos ver el efecto en este ejemplo porque los valores int se utilizan. 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 Iterable. 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 anteriormente en el libro.

Los adaptadores pueden implcmcntase de mltiples formas. Por ejemplo, podemos utilizar el mecanismo de herencia para generar la clase adaptada: //: generics/IterableFibonacci.java // Adaptar la clase Fibonacci para hacerla de tipo Iterable. import java.util.*; public class IterableFibonacci extends Fibonacci implemants Iterable<Integer> ( prvate int n; public IterableFibonacci(nt count) ( n <= count; ) public Iterator<Integer> iteratorO { recum new lterator<lnteger> () { public boolean hasNextO ( retum n > 0; ) public Integer nextO { n--; i

1116 Piensa en Java return IterableFibonacci.this.next();

) public vold remove() { f f No intplementado throw new UnsupDortedOperacionEx ception();

)<

} public statlc vold main(StringI] args) { fordnt i : new i

1117 Piensa en Java IterableFibonacci (18) > Systera.out.print(i + w )/

) ) /* Output: 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 ///i1

Para utilizar IterableFibonacci en una instruccin foreach, hay que proporcionar al constructor un limite para que has!Next() sepa cundo devolver false

Ejercicio 7: (2) Utilice el mecanismo de composicin en lugar del mecanismo de herencia para adaptar Fibonacci con

1118 Piensa en Java el fin de hacerla de tipo Iterable

Ejercicio 8: (2) Siguiendo la forma del ejemplo Coffee. cree una jerarqua de personajes (StoryCharacter) de su pel

cula favorita, divindolos en buenos (GoodGuys) y malos (BadGuys). Cree un generador para StoryCharacter. siguiendo la forma de CoffeeGenerato

15 Genreos 1119 rMtodos genricos

Hasta ahora, hemos estado analizando la parametri/aein de clases enteras, pero tambin podemos parametri zar mtodos de una clase. La propia clase puede ser o no genrica; esto no influye en la posibilidad de disponer de mtodos genricos.

Un mtodo genrico permite que el mtodo vare independientemente de la clase. Como directriz, deberemos usar los mtodos genricos "siempre que podamos". En otras palabras: si es posible hacer que un mtodo sea genrico, en luyar de que lo sea la clase completa, probablemente el programa sea ms claro si hacemos genrico el mtodo. Adems, si tin mtodo es estatico, no tiene acceso a los parmetros genricos de tipo de la clase, por lo que si esa gencnctdad es necesaria en el mtodo, deberemos definirlo como un mtodo genrico.

Para definir un mtodo genrico, simplemente colocamos una lista de parmetros genricos delante del valor retomo del modo siguiente: / / .* gener ics /Generi cMethods .java public class GenericMethods ( public <T> void f(T x) {

System.out.println(x.getClass().getName i)); i public static void main(Stringi] args) {

15 Genreos 1120 GenencMethods gm = new GenericMethods ( ) ; gm.flH M ) ; gm.f t1>;

gm.f 11.0); gm.f(l.OF);

gm.f t ' c ) i gm.f(gm);

) } / Output : java.lang.String 3 ava.lang.Integer java.lang.Doubl java.lang.Float java.lang.Character Jener icMe t hods *///:-

La clase GenericMcthods no est parametrizada, aunque es perfectamente posible parametrizar simultneamente tanto una clase como sus mtodos. Pero en este caso, solo el mtodo f( ) nene un parmetro de tipo, indicado por la lista de parmetros antes del tipo de retomo del mtodo.

15 Genreos 1121 Observe que con una clase genrica es preciso 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 compilador puede determinar esos tipos por nosotros. Este mecanismo se denomina inferencia del argumento de tipo. Por tanto, las llamadas a f'( ) parecen llamadas a metodo normales, y en la prctica f( ) se compona como si estuviera infinitamente sobrecargado. El mtodo admitir incluso un argumento del tipo GenericMethods.

Para las llamadas a ( ) que usen tipos primitivos entra en accin el mecanismo de conversin de tipos automtica, envolviendo de manera transparente los tipos primitivos en sus objetos asociados De hecho, los mtodos genricos y el mecanismo de conversin automtica de tipos permiten eliminar parte del cdigo que anteriormente requera utilizar conversiones de tipos manuales.

Ejercicio 9: 11 ) 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: (I) Modifique el ejercicio anterior de modo que uno de los argumentos de f( ) no sea parametrizado Aprovechamiento de la inferencia del argumento de tipo

15 Genreos 1122 Una de las quejas acerca de los genricos es que aaden todava ms texto a nuestro cdigo. Considere el programa lioldinji-Map()fl .st.ja\a del Captulo 11. Almacenamiento de objetos. La creacin del contenedor Map de List tiene el aspecto siguiente: Mapcperson, List<? extends Pet>> petPeople = new HashMap<Person, List<? extends Pet();

(Esta utilizacin de extends y los signos de interrogacin se explicarn posteriormente en el capitulo). Parece, por el ejemplo, que nos estamos repitiendo y que el compilador debera deducir una de las listas de argumentos genricos a partir de la otra. En realidad, no puede deducirla, pero la inferencia del argumento de tipo en un mtodo genrico permite realizar algunas simplificaciones Por ejemplo, podemos crear una utilidad que contenga varios mtodos estticos y con la que se generen las implemcntaciones de los diversos contenedores mas comnmente utilizadas: //: net/mindview/util/New.java // Utilidades para simplificar la creacin de contenedores // genricos empleando la inferencia del argumento de tipo, package net.mindview.til; import j ava.ut i 1.*; public class New { public static <K.V> Map<K,V> map() | retum new HashMap<K. V>() ;

) public static <T> List<T> llstO { retum new ArrayList<T> (J ;

} public static <T> LinkedList<T> IList0

15 Genreos 1123 { retum new LinkedList<T>();

) public static <T> Set<T> set() { retum new HashSet<T> () ;

) public static <T> Queue<T> queueU ( retum new LinkedList<T> < J ;

) // Ejemplos: public static void rr.ainiString N args) { Map<String, List<Strmg>> sis = New.mapO; List<String> ls = New.list); LinkedList<String> lis New. IList O; Set<String> ss * New.set); Queue<5tring> qs = New.queuet);

15 Genreos 1124 } ///:-

En main() podemos ver ejemplos de cmo se emplea esta herramienta: la inferencia del argumento de tipo elimina la necesidad de repetir la lista de parmetros genricos. Podemos aplicar esto a holding/MapOfList.java: //: generics/SimplerPets.java import typenfo.pets.*; smport java.util.*; import net.mindview.til; public class SimplerPets ( public static void mainString[] args) ( Map<Person, List<? extends Pet>> petPeople = New.mapO; // Eli resto del cdigo es igual...

) t m-.-

Aunque se iraia de un ejemplo imcresantc del mecanismo de inferencia del argumento de tipo, resulta difcil determinar las ventajas de este mecanismo. A la persona que lea el cdigo la obligamos a anali/ar y a comprender esta biblioteca adicional y sus implicaciones, por lo que sera igual de productivo dejar la definicin original (que es bastante repetitiva) precisamente para simplificar. Sin embargo, si la biblioteca estndar de Java incluyera algo similar a la utilidad New.java que hemos presentado, tendra bastante sentido utilizarla.

15 Genreos 1125 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.map( ). como argumento a otro mtodo, el compilador 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 Objcct. He aqu un ejemplo con el que se genera un error de compilacin: , /: generi.es/LimitsOfInference. java import typeinfo.pets.*; import j ava.Util.; public class LimitsOfInference ( static void f (Map<Person. Liste? extends Pet petPeople) {} public static void main(String[] args) ( ) // (New.map()); // No se compila

) ///*-

Ejercicio 11: ( l ) Pruebe New.java creando sus propias clases y verificando que New funcione adecuadamente con las

mismas. Especificacin explcita de tipos

15 Genreos 1126 Es posible especificar explcitamente el tipo de un mtodo generico. aunque esta sintaxis 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 misma clase, hay que utilizar tltis antes del punto; y cuando se trabaje con mtodos estticos hay que emplear el nombre de la clase antes del punto. El problema mostrado en LimitsOf)nference.java puede resolverse utilizando esta sintaxis: //: generics/ExplicitTypeSpecification.java import typeinfo.pets.*; import java.til. import net .mindview. til. , public class ExplicitiypeSpecification ( static void f(Map<Person, List<Pet>> petPeople) {} public static void main(StringU args) ( f(New.<Person, List<Pet>>map()};

} ) M*

Por supuesto, esto elimina la ventaja de utilizar la clase New para reducir la cantidad de texto tecleado, pero esta sintaxis adicional slo ser necesaria cuando no estemos escribiendo una instruccin de asignacin.

Ejercicio 12: (1) Repita el ejercicio anterior utilizando la especificacin explcita de tipos. Varargs y mtodos genricos

15 Genreos 1127 Los mtodos genricos y las listas de argumentos variables pueden coexistir perfectamente. //: aenerics/GenericVarargs.java import j ava.ut il.; public class GenericVarargs ( public static <T> List<T> makeList(T... args) { Llst<T resille = new ArrayList<7>! > ; for(T Item : args) result.addlitemj; return result;

) public static void main(String[I aras) ( LiBt<String Is * makeList ("A") ; System.out.printIn(Is) j Is * makeList ("A . B". * C H ) : System.out .printlnCls); Is = makeList ! *'ABODEFFHIJKLKNCPQRSTUWXY2 . spl 11 ( " ") ) ; i System.out.println(Is);

) / Output: lAl A, B, CJ I. A. B. C, D, E, F. P. H, I. J, K. L, M, N, 0, P. 0, R, S. T, Uf V. W, X. Y. Z]

V//s-

15 Genreos 1128 F1 mtodo niakeList( ) mostrado aqui tiene la misma funcionalidad que cl mtodo java.util.Arrays.asListf ) de la biblioteca estndar. Un mtodo genrico para utilizar con generadores

Resulta bastante cmodo utilizar un generador para rellenar un objeto Collection, y tambin tiene bastante sentido hacer genrica esta operacin: //: generics/Generators.java // Una utilidad para utilizar con generadores. import generics.coffee.; import java.util; import net.mindview.util; public class Generators ( public static <T> Coliection<T> fill(Collection<T> coll. Generator<T> gen. int n) ( for (int i = 0 ; i < n; i>*') coll.add(gen.next0 ) / return coll;

) public static void main(String 11 args) { Collection<Cofee> coffee * fill( new ArrayList<Coffee>(), new CoffeeGeneracor(), 4); for(Coffee c : coffee) System.out.println(c) ; Collection<Integer fnumbers fillt new ArrayList<Integer(), new Fibonacci( ) , 1 2 ) ; for(int i : t numbers) System.out .print (i **, *)?

15 Genreos 1129 ) ) / Output: Americano 0 Latte 1 Americano 2 Mocha 3


1.

1. 2. 3, 5. 8. 13. 21. 34. 55. 89. 144,

*///-

Observe que el mtodo genrico Fill( ) puede aplicarse de forma transparente a contenedores y generadores de objetos Coffee e Integer.

Ejercicio 13: (4)Sobrecargueelmtodofill( ) los subtipos espec

de modo que los argumentos y tipos de retomo sean

ficos de Collection List. Queue y Set. De esta forma, no perdemos el tipo de contenedor. Podemos utilizar el mecanismo de sobrecarga para distinguir entre List y LinkedList? Un generador de propsito general

15 Genreos 1130 He aqu una clase que produce un objeto Generator para cualquier clase que disponga de un constructor predeterminado Para reducir la cantidad de texto tecleado, tambin incluye un mtodo genrico para crear un objeto BasicGenerator: /: net/mindview/util/BasicGenerator.java // Crear automticamente un generador, dada una clase con un // constructor predeterminado (sin argumentos). package net.mindview.til; public class BasicGenerator<T> implements Generator<T> ( private Class<T> type; public BasicGenerator(Class<T> typel this.type <= type; ) public T next() ( try { // Asume que el tipo es una clase pblica: retum type. newlnstance () ; } catch(Exception e) ( throw new P.untimeException(e) ;

) // Producir un generador predeterminado dado un indicador de tipo: public static T> Generator<T> create(Class<T> type) ( retum new Ba3cGenerator<T>(type);

15 Genreos 1131 l ///:-

lista clase proporciona una mplementacin basica que producir objetos de una clase que (1 1 sea publica (va 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 predeterminado (uno que no tome ningn argumento). Para crear uno de estos objetos BasicGenerator. invocamos el mtodo create( ) y pasamos el indicador de tipo para el tipo que queramos generar. El mtodo genrico create( ) permite escribir BasieGenerator.create(MyType.class) en lugar de la instruccin new BasicGenerator<\l>Type>(MyTy|>e,class) que es ms complicada.

Por ejemplo, he aqu una clase simple que dispone de un constructor predeterminado //: generics/CountedObject.java public class CountedObject { private static long counter = 0; private final long id = counter-*-*-; public long d() ( retum id; } public String toStringC' ( retum CountedObject ** id;} } ///:-

La clase CountedObject lleva la cuenta de cuntas instancias de si misma se han creado e informa de la cantidad total mediante toString( ).

15 Genreos 1132 Utilizando BasicGenerator. podemos crear fcilmente un objeto Generator para CountedObject: //: generics/BasicGeneratorDemo.java import net.mindview.til; public class BasicGeneratorDemo { public static void main(String[] args) { Generator<CountedObject> gen = BasicGenerator.create(CountedObject.class); forint 1 * 0 ; i < 5; ++J System.out.println(gen.next()) ;

) } / Output: CountedObie ct 0 CountedObje ct 1 CountedObje ct 2 CountedObje ct 3 CountedObje ct 4

Podemos ver cmo el meiodo genrico reduce la cantidad de texto necesaria para crear el objeto Generator. Los genricos de Java nos fuerzan a pasar de todos modos el objeto Class. por lo que tambin podramos utilizarlo para la inferencia de tipos en el mtodo creale( ).

15 Genreos 1133 Ejercicio 14: (I) Modifique BasicGencratorDenio.java para utilizar la forma explcita de creacin del objeto

Generator (es decir, utilice el constructor explcito en lugar del mtodo genrico create( )) Simplificacin del uso de las tupias

El mecanismo de inferencia del argumento de tipo, junto con las importaciones de tipo static. nos permite reescribir las tupias que hemos presentado anteriormente, para obtener una biblioteca de propsito ms general. Aqu, las tupias pueden crearse utilizando un mtodo esttico sobrecargado: //: net/mindview/utll/Tuple.java // Biblioteca de tupias utilizando el mecanismo de // inferencia del argumento de tipo, package net.mindview.til; public class Tupie ( public static <A,B> TwoTuplecA,B> tupie(A a, B b) { return new TwoTuple<A.B>(a. b) ;

} public static <A,B,C> ThreeTuple<A,B.C> tupie (A a, B b. Ce) { return new ThreeTuple<A,B,C>(a, b, c);

15 Genreos 1134 ) public static <A.B,C,D> FourTuple<A,B,C,D> tupleA a. B b. C c, D d) ( return new FourTuple<A,B,C,D>(a, b, c, d) ;

) public static <A,B,CD,E> FiveTuple<A, B,C, D, E> tupie IA a, B b, C c, D d, E e) j return new FiveTuple<A,B,C,D,E>(a, b. c, d, e) ;

) \ n h .-

He aqui una modificacin de TupleTest.java para probar Tuple.java: //: generics/TupleTest2.java impert net .mindview.til. * ,* import static net .mindview. til .Tupie. ,* public class TupleTest2 { static TwoTuple<String,Integer> f() ( return tuDle("hin, 471;

15 Genreos 1135 ) static TwoTuple f2() { return tupie("hi", 47); } static ThreeTupie<Amphibian,Strlng,Integer> g() ( return tuplenew AmphibianO, "hi", 47);

15 Genricos 1136 )atatic FourTuple<Vehicle,Amphibian,String.Integer> h() ( retum tuple{new Vehlcle() , new AmDhibianO . "hi", 47) r i static FiveTuplecVehicle,Amphibian,String,Integer ,Oouble> kO ( retum tupiefnew VehicleO# new Amphibian(), i *hin, 47, 11.1);

public static void main(String[J args) ( TwoTuple<String,Integer> ttsi = f U ; System.out.println(ttsi); System.out.println(f2()); System.out.println(g()); System.out.println(h()); System.out.orintln(k(>);

) ) / Output: (B0% match) (hi, 47) (hi, 47) (Amphibian37d772e, hi, 47) (VehicleS757aef, Amphibianfcd99c3, hi , 47) (Vehiclela46e30, Amphibian*3e2 5a5, hi. 47. 11.1)

*///:-

15 Genricos 1137 Observe que f( ) devuelve un objeto parametrizado TwoTuple. mientras que f2( ) devuelve un objeto TwoTuple no para- metn/ado. El compilador no proporciona ninguna advertencia acerca de f2( ) en este caso porque el valor de retomo no est siendo utilizado de forma parametrizada; 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: (I) Verifique la afirmacin anterior. Ejercicio 16; (2) Aada una tupia SKTuple a Tuplc.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 utilizando conjuntos. Estos conjuntos pueden definirse de forma cmoda como mtodo genrico, paru utilizarlos con todos los diferentes tipos: //: net/mindview/util/Sets. java package net.mindview.til; import java.til.*; public class Sets ( public static <7> Set<T> unin(Set<T> a, Set<T> b) ( Set<T> result = new HashSet<T>(a); result.addAli(b); retum result;

} public static <T>

15 Genricos 1138 Set<T> intersection(Set<T> a, Set<T> b> { Set<T> result new HashSet<T>la) ; result.retainAll(b); retum result;

} // Restar subconjunto de un superconjunto: public static <T> Set<T> difference\Set<T> superset, Set<T> subset) | SetcT result = new HashSetcT(superaet)/ result. removeAll subset) j return result;

) // Reflexivo--todo lo que no est en la interseccin: public static <T> Set<rT> coraplement {Set<T a. Set<T> b) ( return difference unin (a, b) , intersection (a, b)) ,*

} /// = -

15 Genricos 1139 Los primeros tre* mtodos duplican el primer argumento copiando sus referencias en un nuevo objeto HashSet. de modo que los conjuntos utilizados como argumentos no se modifican directamente. El valor de retomo ser, por tanto, un nuevo objeto Sel.

Los cuatro mtodos representan las operaciones matemticas de conjuntos union( ) devuelve un objeto Set que contiene la combinacin de los dos argumentos, ntersection ) devuelve un objeto Set que contiene los elementos comunes a los dos argumentos. difference( ) resta los elementos subset de superset y complemente ) devuelve un objeto Sel con todos los elementos que no formen parte de la interseccin. Para crear un ejemplo simple que muestre los electos de estos mtodo*, he aqu una enumeracin que contiene diferentes nombres de acuarelas: //: generics/watercolors/Watercolors.java package generics.watercolors; public enum Watercolors { ZINC, LEMON_YELLOW, MEDIUM_YELLOW, DEEP_YELLQW, GRANGE, BRILLIANT_RED, CRIMSON, MAGENTA, ROSE_MADDER, VIOLET, CERULEA!d_BLUE_HUE, PHTHALO_8LUE, ULTRAMAR INE. COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE, SAP_GREEN, YELLOW_OCHRE, 3URNT_SIENNA, RAW_UMBER BURNT_UMBER, PAYNHS JGRAY, IVORY_BLACK ) / / / : -

Por comodidad (para no tener que cualificar todos los nombres) importamos esta enumeracin estticamente en el ejemplo siguiente. Este ejemplo utiliza EnumSet, que es una herramienta de Java SE5 que permite crear conjuntos fcilmente a partir de enumeraciones (aprenderemos ms de EnumSet en el Captulo 19, Tipos enumerados). Aqu, al mtodo esttico EnumSet.range( ) se le pasan el primer y el ltimo elemento del rango que hay que utilizar para crear el objeto Set resultante: //: generics/WatercolorSets.java imeort generics.watercolors.*; import j ava.ut i1.; import static net.mindview.til.Print v mport static net.mindview.utii.Sets. * ,* import static generics.watercolors.Watercolors ;

15 Genricos 1140 public class WatercolorSets ( public static void main<String[] args) { Set<Watercolors> secl EnumSet.range(BRILLIANT^RED, VIR1DIAN_HUE)/ Set<Watercolors> set2 = EnumSet.range(CERULEAN_SLUE_HUE. BURKT_UMBER); print("setl: " + setlj; print("set2: M t set2); print("unin(setl, set2): + unin(setl, set2)); Set<Watercolors> subset = intersectiontsetl, set2); print("intersectionsetl, set2J: " subset); print("difference(setl, subset): * di fference(set1, subset1); print(Mdifference(set2, subset): " difference(set2, subset)); print(complement(setl, set2): + cotnplenient \seti. set2i)/ J Output: /Sample] get 1: fBRILLIANT_RED, CERULEAN_5LUE HUE, ?HTHALC_BLU, VIRIDIANHUEJ CRIMSON, MAGENTA, ROSE_MADDER, VIOLET.

ULTRAMARINE.

COBALT_BLUE_HUE, PHTHALC BLUE.

PERMANENT_GREEN, COBALT

sell : [CERULEAN_BLUE HUE, BLUE_HUE, PERMANENT,GREEN. V:RIDIAN_HUE, SAP_GREEN. BURNT_ LIMBER]

ULTRAMARINE,

VELL0N_0CHKE.

BURNT_SIENNA, YELLOW

RAW_UM8ER, OCHRE,

union set 1. aet2):[SAP GREEN, PERMANENT_GP.EEN, SURNT_UMBR, COBALT_BLUE_HUE, VIOLET, 3URNT_SIENNA, CRIMSON.

ROSE_MADDER. RED.

BRILLIANT

RAW_UMBER,

ULTRAMARINE.

CERULEAN_3LUE_HUE. PHTHALO_BLUE, MAGENTA, VIRIDIANJUEJ intersection(ietl. Bet2) : {.ULTRAMARINE, BLtTE HUE, PHTHALO_BLUE, CERULEAN_BLUE_HUE. V1RIDIAN_HUE] difference3etl, subset); !ROSE_MADDER. CRIMSON. VIOLET. MAGENTA. BRILLIANT_KED] difference(set2, subset): IRAW_UMBER, SAP_GREEN, YELLOW_OCKRE. BURNT_SIENNA. BURNT_UMBSR] complement(set1. set2) : BURNTJJMBER, VIOLET, BRI iSAPjSREEN, ROSE_MADDER, YELLOW_OCHRE, PERMANENT_GREEN. COBALT

LLIANT RED, RAW UMBER. BURNT SIENNA. CRIMSON. MAGENTA]

15 Genricos 1141 Analizando la salida, puede ver el resultado de cada una de las operaciones.

El siguiente ejemplo utiliza Sets.di(Tc*rencc( ) para mostrar las diferencias de mtodos entre diversas clases Collection y

Map de java.utll //: net/mindv:ew/til/ContainerMethodDiffer ences.java package net.mindview.ut11; import java.lang.reflect.i import java.util.; public class ContainerMethodDifferences | atatic Setc5tnng> methodSet Class<?> type { Set<String> result * new TreeSet<String>(); for(Method m : type.getMethodsO) result. add (m.getName > ) return result;

static void interfaces (Class?- type! ( System.cut.printfInterfaces in ** + type . getSimpl eName (J " r ") ; List<String> result = new ArrayList<String>I); for(Class<?> c : type.getInterfaces<) result.add(c.getSimpleNams ill; \ System, out. print In (result)

static Set<Stnng> object methodSet (Object .class] ; static ( object .add("clone1) ; ) static void difference(Class<?> superset. Class<?> subset] ( System.out.print(superset.getsimpletamelj +

15 Genricos 1142 " extends * subset. getSimpl eName I) ", adds: "I,* Set<String> comp = Sets.difference* methodSetI superset), methodSetlsubset I); comp.removeAll(object)j // No mostrar mtodos 'Object' System.out.println(comp); interfaces(suerset);

) public static void mam (String U args) {

1143 Piensa en Java System.out.println(Collection: M +methodSet(Collection.class))/ interfaces(Collection.class); difference(Set.class, Collection.class)j difference(Ka6hSet.class, Set.classi;dif ference'.LinkedHashSet .class, HashSet.class); difference(TreeSet.class, Set.class); differenceLiBt.class, Collection.class); difference (ArrayList .class, List, class i ; difference(LinkedList.class, List.class); difference(Queue.class, Collection.class); difference(PriorityQueue.class, Queue.class); System.out.printlni"Map: " * methodSet'Map.classm ; difference(HashMap,class, Map.class); difference I LmkedHashMap. class, HashMap. class) ; difference(SortedMap.class, Map.class); difference(TreeMap.class, Map.class/;

i ///t-

public class BankTeller (

1144 Piensa en Java La salida de este programa fue utilizada en la seccin "Resumen del Captulo l {.Almacenamiento Je 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 clonacion a partir de la referencia a la interfaz Set que se pasa en Sets.java. Podra modificar Sets.javu para tratar tanto el caso general de una interfaz Set. tal como se muestra, como el caso especial de un objeto EnumSet. utilizando clone( ) en lugar de crcar un nuevo objeto HashSet'' Clases internas annimas

Los genricos tambin pueden utilizarse con las clases internas v con las clases internas annimas. Me aqui un ejemplo que

public class BankTeller (

1145 Piensa en Java implementa la interfaz Generator utilizando clases internas annimas: //: generics/BankTeller.^ava if Una simulacin muy simple de un cajero automtico. import java.til.*; import net.mindview.til.*; class Customer ( private static long counter = 1; private final long id counter-*-*; private Customer 0 () public String toStringO ( return "Customer w id; ) // A method to produce Generator objects: public static GeneratorCustomer generator 0 { return new Generator<Customer>() f public Customer next (l ( return new Customer M; }

);

public class BankTeller (

1146 Piensa en Java ) class feller ( private static long counter = 1; private final long id = counter**; private Teller() (} public String toStringO ( return Teller " + id; ) // Un nico objeto Generator: public static Generator<Teller> generator = new Generator<Teller>() { public Teller next | return new Teller!); ) } public static void serve(Teller t, Customer c) ( System.out.printIn(t + " serves " c);

) public Btatic void main(String[] args) ( Random rand = new Random (47) ,* Queue<Customer> line = new Linkedlast<Cust.cmer > () ; Generators.fill(line. Customer.generator(), 15); l*ist<Teller> tellers = new ArrayList<Teller>(); Generators.fill(tellers. Teller.generator, 4); for(Customer c : line) serve(tellers.get(rand.nextlntI tellers.size 0)1 c); public class BankTeller (

1147 Piensa en Java )

Tanto C'ustomer como Teller tienen constructores privados, lo que nos obliga a utilizar ohjctos Generator Customer tiene un mtodo generator( ) que genera un nuevo mtodo Generator<Customer> cada ve/ que lo invocamos. Puede que no necesitemos mltiples objetos Generator, y Teller crea un nico objeto generator pblico. Si analiza main() ver que ambas tcnicas se utilizan en los mtodos flll( ).

Puesto que tanto el mtodo gcnerator() de Customer como el objeto Generator de Teller son estticos, no pueden formar parte de una interfaz, asi que no hay forma de hacer genrica esta funcin concreta. A pesar de ello funciona razonablemente bien con el mtodo fill( ) Examinaremos otras versiones de esle problema de versiones de colas en el Capitulo 21, Concurrencia.

Ejercicio 18: (3) Siguiendo la forma de BankTeller.java. cree un ejemplo donde el pez grande (BigFish) se coma al public class BankTeller (

1148 Piensa en Java chico (I .ittleFish) en el ocano (Occan) Construccin de modelos complejos

Una ventajo importante de los genricos es la capacidad de crear modelos complejos de forma simple y segura Por ejemplo. podemos crear fcilmente una lista de tupias: //: genencs/TupieList. }ava // Combinacin de tipos genricos para crear tipos genricos complejos. import java.til.*; mport net.minaview.util.; public class TupleList<A,B,C,D> extends ArrayList<FourTuple<A,B,C,D>> { public static void main(String(] args) (

TupleList<Vehide, Amphibian, String, Integer> TupleList<Vehicle, Amphibian, String, t1.add(TupleTest.h()) ;tl.addiTupleTest.hH ) j

ti = new Integer>();

Cor iFourTuplecVehicle, Amphibian,String, Integer* ir ti) System.out.println(i);

public class BankTeller (

1149 Piensa en Java } | /* Output: (75% match! (Vehicle-51ib86e7. Amphibian?35ce36. hi, 47) (Vehicle*757aef. AmphibianSd9f9cl. hi. 47j ///:-

Aunque hacc falta bastante texto (especialmente en la creacin del aerador), obtenemos una estructura 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 v la solucin total tiene mltiples panes. En este caso, el modelo corresponde a un comercio de venta al por menor con pasillos, estanteras y productos: //: generics/Store.java // Construccin de un modelo complejo utilizando contenedores genricos. import java.til.*; import net.mindview.til.; class Product ( private final int id; private String description; private double price; public Product(int IDnumber, String aescr, double price){ id = IDnumber; description * descr; this.price = price; System.out .println(toStringO ) ; public class BankTeller (

1150 Piensa en Java ) public String toStringO { return id : * * description + *\ price: $** + price;

) public void priceChange(double change) { price change,-

} public static Generator<Product> generator new GeneratorProduct0 j private Random rand - new Random(47); public Product next I) { return new Product(rand.nextlnt(1000), "Test", Math.round(rand.nextDouble0 * 1000.0) 0.99);

public class BankTeller (

1151 Piensa en Java b

) 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 = 0; i < nShelves; if*) add(new Shelf(nProducts));

} public class BankTeller (

1152 Piensa en Java ) class CheckoutStand (} class Office () cublie class Score extends ArrayList<Aisle> ( private ArrayList<CheckoutStand: checkouts = new ArrayList<CheckoutStan< () . private Office office = new Office''; public Store(int nAiales, int nShelvea, int. nProtiucts) { for lint i * Qj i * nAislesr add(new Aisle(nShelves, nProducts) i ; \ public String toStringO ( Strir.gBuilder result new StringBuilder () ; for(Aisle a : this) for(Shelf s : a) for{ Prod uct p : s) { resu lt.a ppen d(p) ; resu lt.a ooen d("\ n") ;

) return result.toString();

public class BankTeller (

1153 Piensa en Java } public static void main(String[3 args) { System.out.println(new Store(14, 5, 10));

*///:Como puede ver en Store.foString(). el resultado son muchos niveles de contenedores que. a pesar de la complejidad, resultan manejables y son seguros en lo que al tratamiento de tipos se refiere. Lo ms impresionante es que no resulta demasiado complejo, desde el punto de vista intelectual, construir dicho tipo de modelos

Ejercicio 19: (2) Siguiendo la forma de Siore.java. construya un modelo de un buque de carga donde se utilicen conte

nedores metlicos para las mercancas. public class BankTeller (

1154 Piensa en Java El misterio del borrado

A medida que nos sumergimos ms profundamente en los genricos, aparecen una serie de aspectos que parecen no tener sentido a primera vista. Por ejemplo, aunque podemos escribir ArravList.class, no podemos escribir ArrayList<integer>.class. Considere tambin lo siguiente: //: generics/ErasedTypeEquivalence.ja va import java.util.*; public class ErasedTypeEquivalence { public static void main(String[] args) ( Class el = new ArrayList<String>().getClass(); Class C2 = new ArrayList<Inteqer>().getClassO; System.out.println(el == c2);

) } /* Output: true V//:*

ArrayUst<String> y ArrayList<lnteger> son claramente de tipos disiinlos Los diferentes tipos se componan de forma distinta, y si tratamos de almacenar un objeto Integer en un public class BankTeller (

1155 Piensa en Java contenedor ArrayLlst<Strng>, obtendremos un comportamiento diferente (la operacin falla) que si tratamos de almacenar ese objeto Integer en un contenedor ArrayLi*t<lnteger> (la operacin si est permitida). Sin embargo, el programa anterior sugiere que ambos tipos son iguales

He aqui otro ejemplo que aumenta todava ms la confusin: //: ger.er i es / Lo st Information, java import java.utiL.*; class Frob {} class Fnorkle (} class Quark<Q> (} class Particle<POSITIQN.MOMENTUM> {) public class Lostlnformation ( public static void main(StringU args) ( List<Frob> list = new ArrayList<Frob>()/ Map<Frob,Fnorkle> map new HashMap<Frob,Fnorkie>(); Quark<Fnorkle> quark = new Quark<Fnorkle> O Partid e<Long,Double> p new Partide<Long, Double> () ,* System. out. println Arrays. tcStnng t list.getClassO.getTypeParameters()}); System.out.println (Arrays.toStringi map.gec.daBS O .gecTypeParamecers t)) ) ; System, out .println (Arrays. toString ( quark.getClass{).getTypeParameters')); System.out.println(Arraya.toString( o.getClass().aetTypeParameters()) J; public class BankTeller (

1156 Piensa en Java ) } /* Outputi EJ IK, V] IQ] [POSITION, MOMENTUM]

*///:-

De acuerdo con la documentacin del JDK, Ctoss.getTypeParanieters( ) devuelve una matriz de objetos rvpcVariable que representan las variables de tipo definidas en la declaracin genrica ..Esto parece sugerir que podriamos ser capaces de averiguar cules son los tipos de parmetro. Sin embargo, como podemos ver analizando la saJida, lo nico que podemos avenguar son los contenedores utilizados como variables para los parmetros, lo cual no constituye una informacin muy interesante.

l-a cruda realidad es que. No hay informacin disponible acara de los tipos de parmetros genricos dentro del cdigo genrico

public class BankTeller (

1157 Piensa en Java Por tanto, podemos llegar a detemnnar cosas como el identificador del parmetro de tipo y los limites del tipo genrico, pero no podemos llegar a determinar los parmetros de tipo reales utilizados para crear una instancia concreta. Este hecho, que resulta especialmente frustrante para los que tienen experiencia previa con C-H-, constituye el problema fundamental al que hay que enfrentarse cuando se trabaja con genricos de Java.

Los genricos de Java se implementan utilizando el mecanismo de borrado. Esto significa que toda la informacin especifica de tipos se borra cuando se utiliza un genrico. Dentro de un genrico, la nica cosa que sabemos es que estamos usando un objeto. Por tanto. List<String> y List<lnteger> son. de hecho, el mismo tipo en tiempo de ejecucin. Ambas Ib no as se borran** sustituyndolas por su tipo de origen List. Entender este mecanismo de borrado y cmo hay que tratar con el constituye uno de los principales problemas a la hora de aprender el concepto de genricos en Java, ste es precisamente el problema que analizaremos a lo largo de esta seccin La tcnica usada en C++

He aqu un ejemplo C++ que utiliza plantillis. Observar que la sintaxis para los tipos parametnzados es hastantc similar, va que Java se lia inspirado precisamente en C++: ,*/: generics/Templates.cpp include <iostream> using namespace std; remplate<ciass 7> ciass Manipulator { T obj; public: ManipulatoriT x) { obj = x; ) void maniplateI) j obj.f); )

public class BankTeller (

1158 Piensa en Java ) class HasF ( public: void f(> { cout << MHasF:fO" << endl; )

}: Inc. main ( ) { HasF hf; Manipulator<HasF> manipulacorhf); manipulator.maniplate(); } / * Output: HasF::f 0

///:-

La clase Manipulator almacena un objeto de tipo T Lo interesante es el mtodo manipulate( ) que invoca un metodo f( ) sobre obj Cmo puede saber que el mtodo f( ) existe para el parmetro de tipo T? El compilador C++ efecta la comprobacin cuando mstanciamos la plantilla, por lo que el punto de instantacion de Manipulator<HasF> comprueba que HasF tiene un mtodo f( ) Si no fuera asi. se obtendra un error en tiempo de compilacin, preservndose por tanto la segundad referente a los tipos.

public class BankTeller (

1159 Piensa en Java Escribir este tipo de cdigo en C-H- resulta sencillo, porque cuando se instancia una plantilla, el cdigo de la plantilla conoce el tipo de sus parmetros de plantilla, l.os genricos de Java son distintos. Me aqu la traduccin de HasF: //: generics/HasF.java public class HasF { public void { ) ( System.out.println("HasF.f()">j }

| ///:-

Si tomamos el resto del ejemplo y lo traducimos a Java no se podr compilar //: genencs/Manipulation.java // {CompileTimeError} Won't compile) class Manipulator<T> { private T obj; public Manipulator(T x) { obj = xf- ) // Error: no se puede encontrar el smbolo: mtodo (): oublic void maniplatel) { obj.f): )

] public class Manipulation { public static void main(String[] args) { HasF hf = new HasFO; public class BankTeller (

1160 Piensa en Java Manipulator<Ha sF> manipulator = new Manipulator<Ha sF>hf}; manipulator.maniplate ( ) , J l nh.~

Debido al mecanismo de borrado, el compilador de Java no puede relacionar el requisito de que maniplate* ) debe ser Capaz de invocar f( ) sobre obj con el hecho de que HasF tiene un mtodo f( ) Para poder invocar f( ). debemos ayudar a lu clase genrica, proporcionndola un limite que indique al compilador que slo debe aceptar los tipos que se conformen con dicho lmite. Para esto se utiliza la palabra clave extends. Una vez que se incluye el limite, si que se puede realizar la compilacin. : generies/Manipulator2.java class Manipulator2<T extends HacF> ( private T obj; public Manipulator2 (T x) ( ob} = x; ) D'ublic void maniplate () ( obj.fO; }

) /// i

public class BankTeller (

1161 Piensa en Java El limite <T extends HasF> dice que I debe ser de tipo HasF o algo derivado de HasF. Si es asi. entonces resulta seguro invocar f( ) sobre obj

Decimos a este respecto, que el parmetro de tipo generico v<* borra de acnenio con su primer limite (es posible tener mltiples limites, como veremos posteriormente) Tambin hablamos en relacin con esto, del borrajo dei parmetro Je tipo. El compilador, en la prctica, sustituye al parmetro de tipo por lo que el limite indique, de modo que en el caso anteriorT se borra y se sustituye por HasF. lo cual es lo mismo que sustituir T por HasF en el cuerpo de la clase.

El lector podra pensar, correctamente, que en Manipuiation2.java, los genricos no proporcionan ninguna ventaja Podramos perfectamente realizar el borrado de tipos nosotros y crear una clase sin genricos. //: aenerice/Kanipulator3.java class Manipulator3 | private HasF obj; public Manipulator3(HasF x) { ob} x; ] public void manipularei [ obj.flj; } l (/ / t -

Esto nos plantea una cuestin Importante: los genricos slo son tiles cuando deseamos public class BankTeller (

1162 Piensa en Java utilizar parmetros de tipo que sean ms genricos que un tipo especfico (y todos sus subtipos); en otras palabras, cuando queramos escribir cdigo que funcione con mltiples clases. Corno resultado, los parmetros de tipo y su aplicacin dentro de un fragmento til de cdigo genrico sern normalmente ms complejos que una simple sustitucin de clases. Sin embargo, no debemos concluir poi ello que cualquier cosa de la forma <T extends HasF> no tiene ningn sentido Por ejemplo, si una clase nene un metodo que devuelve T. entonces los genricos son tiles, porque permitirn devolver el tipo exacto //: generics/RetumGenerlcType. java class ReturnGensricTypevT extends HasP> { private T obj ; public RetumGenericType (T x> { obj = x; } public T get U { return obj t- )

) ///:-

Es preciso examinar todo el cdigo y determinar si es lo suficientemente complejo como para merecer el uso de genericos Examinaremos el tema de los lmites con ms detalle ms adelante en el capitulo.

public class BankTeller (

1163 Piensa en Java Ejercicio 20: (I) Cree una interfaz con dos mtodos y una clase que implementc dicha interfaz y aada un tercer mto

do. En otra clase, cree un mtodo generico 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( ). pase una instancia de la clase implementadora al mtodo genrico. Compatibilidad de la migracin

Para eliminar cualquier potencial contusin acerca del mecanismo de horrado de tipos, es necesario entender claramente que no se nata de una caracterstica del lenguaje. Se traa de un compromiso en la implementacin de los genricos de Java, compromiso que es necesario porque los genricos no han formado pane del lenguaje desde el principio Este Compromiso puede creamos algunos quebraderos de cabeza, por lo que e* necesario acostumbrarse a l lo antes posible y entender a qu se debe

Si los genricos hubieran formado pane de Java 1.0, esta funcionalidad no se habra implementado utilizando el mecanismo de borrado de tipos, sino que se habra empleado el mecanismo de la re (fieacin para retener los parmetros de ;po como entidades de primera clase, de modo que seriamos capaces de realizar, con los parmetros de tipo, operaciones de reflexin y operaciones del lenguaje basadas en tipos Vcremo> posteriormente en el capitulo que el mecanismo de bonado reduce el aspecto 'genrico" de los genricos. Los genricos siguen siendo tiles 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 bonado de tipos. public class BankTeller (

1164 Piensa en Java En una implementacin basada en dicho mecanismo, los tipos genericos se tratan como tipos de segunda clase que no pueden utilizarse en algunos contextos importantes. Los tipos genricos slo estn presentes durante la comprobacin esttica de tipos, despus de lo cual todo tipo genrico del programa se borra, sustituyndolo por un tipo lmite no genrico. Por ejemplo, las anotaciones de tipos como I.ist<T> se borran sustituyndolas por L.ist. y las variables de tipos nonnales se bonan sustituyndolas por Object a menos que se especifique un limite.

La principal motivacin para el mecanismo de bonado 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, existira un punto de partida en el que todo hubiera sido hecho genrico a la vez. En la realidad, incluso aunque los programadores 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 lleguen nunca a tener ningn motivo para hacer su cdigo ms genrico, o puede simplemente 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 existentes siguen siendo legales y continan significando lo que antes significaban) sino que tambin tienen que soportar la com- paubilidad 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 fonna genrica, no haga que dejen de funcionar el cdigo y las aplicaciones que dependen de ella. Despues de decidir que el objeto era ste, los diseadores de Java y diversos grupos que estaban trabajando en el problema, decidieron que el bonado de tipos era la nica solucin factible. El mecanismo de borrado de tipos permite esta migracin hacia el cdigo genrico. al conseguir que el cdigo no genrico pueda coexistir con el que si lo es.

public class BankTeller (

1165 Piensa en Java Por ejemplo, suponga que una aplicacin utiliza dos bibliotecas, X e V. y que \ utiliza la biblioteca Z Con la apancion de Java SE5. los creadores de esta aplicacin y de estas bibliotecas probablemente terminen por efectuar una migracin hacia cdigo gennco. Sin embargo, cada uno de esos diseadores tendr diferentes motivaciones y diferentes restricciones en lo que respecta a dicha migracin. Para conseguir la compatibilidad de migracin, cada hiblioteca y aplicacin tiene que ser independiente de todas las demas en lo que respecta a la utilizacin de genencos Por tanto, no deben ser capaces de detectar si las otras bibliotecas estn utilizando genricos o no. En consecuencia, la evidencia de que una biblioteca concreta est usando genricos debe ser bonada".

Sin algn tipo de nita de migracin, todas las bibliotecas que hubieran sido diseadas a lo largo del tiempo coneran el riesgo de no poder ser utilizadas por los desanolladores que decidieran comenzar a utilizar los genricos de Java Pero como la.% bibliotecas son la pane 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 bonado de tipos es el proceso de transicin de cdigo no gennco a cdigo gennco, y la incorporacin de genricos dentro del lenguaje sin hacer que dejen de funcionar las bibliotecas existentes. El bonado de tipos permite que el cdigo de cliente existente, no genrico, contine pudiendo ser usado sin modificacin, hasta que los clientes esten listos para reescribir el cdigo de cara a utilizar genericos. Se trata de una motivacin muy noble, porque no hace que de repente deje de funcionar todo el cdigo existente.

public class BankTeller (

1166 Piensa en Java El coste del horrado de tipos es significativo. Los tipos genricos no pueden utilizarse 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 inxtanceof y las expresiones new. Como toda la informacin de tipos acerca de los parmetros se pierde, cada vez que escribamos cdigo genrico debemos estar perpetuamente acordndonos de que la idea de que disponemos de informacin 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 cdigo de la clase Foo debera saber que ahora est trabajando con un objeto Cat. La sintaxis sugiere de manera directa que el tipo T est siendo sustituido a lo largo de toda la clase Pero en realidad no es asi y debemos siempre tener presente, cuando estemos escribiendo el cdigo para la clase, que se trata simplemente de un objeto de tipo Object.

public class BankTeller (

1167 Piensa en Java Adems, el borrado de tipos y la compatibilidad de migracin significan que el uso de genricos no se impone en aquellas ocasiones en que seria bueno que se impusiera: //: generics/ErasureAndlnhertanee.java class Genenc Base<T > { prv ate T elemen t; public void aet(T arg) { arg * element; ) public T gett) ( return element; )

} class Denvedl<T> extends GenericBase<T> (} class Derived2 extends GenericBase {} // Ninguna

advertencia f ! class Derived3 extends GenericBase<?> {) I I Extrao error: // unexpected type found : ? // required: class or interface without bounds public class ErasureAndXnheritance ( SuppressWamings \ unchecked" ) public static void main (String [] args) { Derived2 d2 = new Derived2(); public class BankTeller (

1168 Piensa en Java Object ob3 * d2.get(); d2.setiobj); // [Advertencia aqu!

) ///*-

Dcrived2 hereda de GenerlcBase sin ningn parmetro genrico y el compilador no genera ninguna advertencia. La advertencia no se genera hasta que se invoca set( )

Para que no aparezca la advertencia. Java proporciona una anotacin, que es la que podemos ver en el listado (esta anotacin no estaba soportada en las versiones anteriores a Java SE5): ^SuppressWarnings("unchecked") public class BankTeller (

1169 Piensa en Java Observe que esta anotacin se coloca en el mtodo que genera la advertencia, en lugar de en la clase completa. Es mejor enfocar" lo mximo posible a la hora de desactivar una advertencia, para no ocultar accidentalmente un problema real al desactivar 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 limites cuando queramos tratar el parmetro de tipo como algo mas que simplemente un objeto de tipo Object y la conclusin de todo ello es que hace falta un esfuerzo mucho mayor con unas ventajas mucho menores que cuando se utilizan tipos parametrizados en lenguajes tales como C+ +. Ada o EiTel. 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 limites

Debido al mecanismo de borrado de tipos, el aspecto ms confnso de los genricos es el hecho de que podemos representar cosas que no tienen ningn significado. Por ejemplo: //: generics/A rrayMaker. java public class BankTeller (

1170 Piensa en Java import java.lang. reflect.*; import java.til. ; public class ArrayMaker <T> { prvate Class<7> kind; public ArrayMaker(Class<T> kind { this.kind = kind; } SuppressWaraings("unchecked"l Til crate(int size) ( return (T[J)Array.newlnstance(kind, size>;

) public static void main(String(] args) { ArrayMaker<String> stringMaker new ArrayMaker<String>(Strmg.class) ; String [] strmgArray * stringMaker.create(9); System.out.println(Arrays.toString(stringArray));

) } /* Output: public class BankTeller (

1171 Piensa en Java [nul'l, nuil, nuil, nuil, nuil, nuil, nuil, nuil, nuil]

///:-

Aunque kind se almacena como Class<T>. el mecanismo de borrado de tipos significa que en realidad se est almacenando simplemente como un objeto Class sin ningn parmetro. Por tanto, cuando hacemos algo con ese objeto, como por ejemplo crear una matriz. Array.newlnstance( ) no dispone en la prctica de la informacin de tipos que est implcita en kind; como consecuencia, no puede producir el resultado especfico, lo que obliga a realizar una proyeccin de tipo que genera una advertencia que no se puede corregir.

Observe que la utilizacin de Array.newlnstance() es la tcnica recomendada para la creacin de matrices dentro de genricos.

Si creamos un contenedor en lugar de una matriz, las cosas son distintas: //; public class BankTeller (

1172 Piensa en Java generics/L istMaker.j ava import j ava.ut i1.; public class ListMaker<T> { Lisc<T> create0 | return new ArrayLiet<T>(); } public static void main(StringI] args) ( ListMaker<String> stringMaker^ new ListMaker<String>{) ,* List<Scring> stringLisc * sermgMaker.createO;

) ///s-

El compilador no genera ninguna advertencia, an cuando sabemos perfectamente (debido al mecanismo de borrado de tipos) que la <T> en new ArrayList<T>() dentro de create() se elimina en tiempo de ejecucin no hay ninguna <T> dentro de la clase, por lo que parece que no tiene ningn significado. Pero si hacemos caso de esta idea y cambiamos la expresin a new ArrayLfsf( ). el compilador generar una advertencia. public class BankTeller (

1173 Piensa en Java Carece realmente de significado en este caso? Que pasara si pusiramos algunos objetos en la lista antes de devolverla, como en el siguiente ejemplo?: //: ger.erics/FilledList Maker.java import j ava.ut 1.; public class FilleaListMaker<T> ( List<T> createIT t, int n) ( List<T> result * new ArrayList<T>() ; fortint i = 0; i < n; ) result.add(t) ; retum result;

) public static void main(String [] args) [ FilledListMaker<String> stringMaker *= new FilledListMaker<String>O? List<String> list stringMaker.createt"Helio", 4); ) System.out.println(list) ;

} / Output: [Helio, Helio, Helio, Hellol *///:-

Aunque el compilador es incapaz de tener ninguna informacin acerca de T en create(), sigue pudiendo garantizar (en tiempo de compilacin) que lo que pongamos dentro de public class BankTeller (

1174 Piensa en Java result es de tipo T. de modo que eoncuerde con ArrayU*t<T>. Por tanto, aun 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 garantizar la coherencia interna en lo que respecta a la forma en que se utiliza el tipo dentro del mtodo o de la clase.

Puesto que el mecanismo de horcado de tipos elimina la informacin de tipos en el cuerpo de un mtodo, lo que importa en tiempo de ejecucin son los mires: los puntos en los que los objetos entran y salen de un mtodo. Estos son los puntos en los que el compilador realiza las comprobaciones de tipos en tiempo de compilacin e inserta cdigo de proyeccin de tipos. Considere el siguiente ejemplo no genrico: //: generics/SimpleHolder.java public class SimpleHolder { prvate Object obj; public void set(Object obj) ( this.obj = obj; ) public Object get() { retum obj; } public static void main(String[J args) ( SimpleHolder holder <* new SimpleHolder (); holder.set{"Item"); String s (String)holder.getO;

public class BankTeller (

1175 Piensa en Java ) ///:-

Si descompilamos el resultado con javap -c SimpleHolder. obtenemos (despus de editar la salida): public void set(java.lang.Object); 0: 1: 2: 5: 0: 1: 4: 0: 3: 4: 7: 8: 9: aload_0 aload_l putfield#2;//Campo obj:Object; retum aload_0 getfield#2///Campo obj:Object; areturn new #3; //Clase SimpleHolder dup invokespecial astore_l aload_l ldc #5; //StringItem 6; *7; // Mtodo set:Ofcgect;) V // Mtodo get: ()Object; #4j //Mtodo M<init>":()V

public java.lang.Object get();

public static void main(java.lang.String[])/

11: nvokevirtual 14: aload_l 15: nvokevirtual 18: checkcast #8; 21: astore_2 22: return

//class java/lang/String

public class BankTeller (

1176 Piensa en Java Los mtodos set( ) y gct( ) simplemente almacenan y producen el valor, y la precisin de tipos se comprueba en el lugar donde se produce la llamada a get( ).

Ahora vamos a incorporar genricos al cdigo anterior: / /: ger.encs/GenericHolder.java public class GenericHolder<T> ( prvate T obj ; public void setlT obj) ( this.ob;) = obj; } public T get( ) { return obj; } public static void main(String[] args) ( GenericHolder< String> holder = new GenericHolder< String>(); holder.set("It em"); Strina s = holder.aetO;

public class BankTeller (

1177 Piensa en Java \ ///:-

1.a 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 void sec3ava.lang.Object); 0: 1: 2: 5: 0: 1: 4: 0: 3: 4: 7: 8: 9: aload_0 aload_l putfield #2;//Campoob;j: Object; return aload_0 getfield 4*2; areturn new #3; //Clase GenericHolder dup invokespecial astorel aload__l ldc 5; //String Item #4; //Mtodo H<init>";()V //Campo obj:Object;

public java. lang.Ob-ject get();

public sLatic void main(java.lang.String[1);

11: nvokevirtual *6; // Mtodo set:(Object;)V 14: aload_l 15: nvokevirtual 18: checkcast #8; 21: astore_2 22: return public class BankTeller ( #7; // Mtodo get:OObject; //class java/lang/String

1178 Piensa en Java i.l cdigo resultante es idntico. Hl trabajo adicional de comprobar el tipoentrante en set( )es nulo,yaque es el compilador quien se encarga de realizarlo. Y la proyeccin de tipos para el valor saliente de get( ) sigue estando ahora no

tenemos que hacerlo 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".

public class BankTeller (

1179 Piensa en Java

Puesto que get() y sct() generan el mismo cdigo intermedio, toda la accin referida a los genricos tiene lugar en los limites: en concreto, se trata de la comprobacin adicional en tiempo de compilacin para los valores entrantes y de la proyeccin de tipos que se inserta para los valores salientes. Para contrarrestar la confusin en lo que respecta al tema del mecanismo de borrado de tipos, recuerde siempre que los limites 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 realizar ciertas operaciones en el cdigo genrico. En concreto, no funcionar ninguna cosa que requiera conocer el tipo exacto en tiempo de ejecucin: //: generics/Erased.java // {CompileTimeError) (no se compilar} public clas3 Erased<T> ( // Error I I Error // Error // Advertencia de no comprobacin prvate final int S1ZE = 100,* public static void f(Object arg) ( iflarg instanceof T) (} T var new T(); T C1 array = new TfSIZE] / T[] array * (T)new Object [SI2E] ;

public class BankTeller (

1180 Piensa en Java }

1 ///:-

Ocasionalmente, podemos solventar mediante programa estos problemas, pero en ocasiones nos vemos forzados a compensar el mecanismo de borrado de tipos introduciendo lo que se denomina un marcador de tipos. Esto quiere decir que pasamos explcitamente el objeto C lass correspondiente 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 utilizar en su lugar un mtodo islnstance() dinmico: //: generics/ClassTypeCapture.java class fluilding (} class House extends Building {} public class ClassTypeCapture<T> ( Class<T> kind; public classrypecapture<class<T> kind) | this.kind kind; i public boolean public class BankTeller (

1181 Piensa en Java t {Object arg) ( retum kmd. islnstance (arg) ;

public static void main (String L] args} ( ClassTypeCapture<Building> cttl = new ClassTypeCapture<Building>(Building.classl; System.out.println(cttl.f(new Building()I); System.out.printin{cttl.f(new House())}; ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.classi; System.out.printin(ctt2.f(new Building())); System.out .prmtln(ctt2.f (new House O ) );

) } / Output: true true false true

*///:public class BankTeller (

1182 Piensa en Java

El compilador garantiza que el marcador de tipos se corresponda con el argumento genrico. Ejercicio 21: (4)Modifique C'lassTypeCapture.java Map<String,Class<?. un mtodo aadiendo kind) un y contenedor un mtodo

adcffype(Strng typcname, C'lass<?> createNew(Strtag typcname). createNew( )

generar una nueva instancia de la clase asociada con la cadena de caracteres que se le proporcione como argumento, o producir un mensaje de error. Creacin de instancias de tipos

El intento de crear un objeto con new T( ) en Erased.java no funciona, en parte debido al mecanismo de horrado de tipos v en parte porque el compilador no puede verificar que T tenga un constructor predeterminado (sin argumentos). Pero en esta operacin es natural, sencilla y segura (se comprueba en tiempo de compilacin): //j generics/InstantiateGener icType.cpp // iO+, no Java! public class BankTeller (

1183 Piensa en Java template<class T> class Foo { T x; // Crear un campo de tipo T T* y; // Puntero a T public: // Inicializar el puntero: Poo{ ) ( y = new T{); }

) class Bar (); Int main O { Foc<Bar> fb; Foc<int> ti; // ... y funciona con primitivas 1 ///=-

La solucin en Java consiste en pasar un objeto factora y utilizarlo para crear la nueva instancia. Un objeto factora muy adecuado es el propio objet Class, por lo que si utilizamos un marcador de tipos, podemos emplear newlnstance( ) para crear un nuevo objeto de dicho tipo: 11 i generics/InstantiateGener icType.java Import static net.mindview.til.Print. ; class ClnssAsFactory<T. { T X; public ClassABFactory (Class;T> kind) ( try { public class BankTeller (

1184 Piensa en Java x = kind.newlnstance(); } catch(Exception e/ ( throw new RuntimeException(el; l

) class Empioyee {) public class InstantiateGenericType ( public static void main(String11 args) { ClassAsFactory<Employee> fe * new ClassAsFactory<Employee>( Empioyee.class); printI"ClassA3Factory<Emp loyee> succeeded" ) try ( ClassAsFactory<Integer> fi new ClassAsFactory<Integer><Inteaer.class) ) cacchExcepcin e) ( print(MCassAsFactory<Inteaer> failed*') ;

public class BankTeller (

1185 Piensa en Java )

) ) / * OUtpUt: ClassAsFactory<EmpIoyee> succeeded ClassAsFactory<Integer failed *///:-

Este ejemplo se puede compilar, pero falla si utilizamos ClassAsFactory<lnteger> porque Integer no dispone de ningn constructor predeterminado. 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 explcita y que se restrinja el tipo de modo que slo admita una clase que implemente dicha factora: //: generics/PactoryConstraint.java nterface FactoryI<T> ( T create{);

) class Foo2<7> ( prvate T x; public <F extends public class BankTeller (

1186 Piensa en Java FactoryI<T Foo2(F factory) ( x = factory.create();

i n ...

) class IntegerFactory mplements FactoryI<Integer ( public Integer createO ( retura new IntegerI0)j

) clase Widget ( public static class Factory implements FactoryI<widget> ( public Widget create{) ( retum new Widget ();

public class BankTeller (

1187 Piensa en Java i )

} public class FactoryConstraint ( public static void main(String[3 args) ( new Foo2<Integer>(new IntegerFactoryO); new Foo2<Widget>(new Widget.Factory (J);

) ///=-

public class BankTeller (

1188 Piensa en Java Observe que esto no es ms que una variacin del hecho de pasar Class<T>. Ambas tcnicas pasan objetos factora; pero Class<T> resulta ser el objeto factora predefinido, mientras que en el ejemplo anterior creamos un objeto factora explcito. 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 plantilla y ereate) se define en la subclase para generar un objeto de dicho tipo: //: generics/CreatorGeneric.java abstract class Ger.ericWithCreate<T> { final T element; GenericWithCreate() { element = create0 } abstraen T create ();

) class X () class Creator extends GenericWithCreate<X> ( X createO ( return new XIJ; } void f 0 | System.out.println eiement.getClass().getSimpleName0 )

public class BankTeller (

1189 Piensa en Java } i public class CreatorGeneric ( public static void main(String] args) { Creator c * new Creator O; c. f t);

) ) / Outputt X

///:-

Ejercicio 22: (6) Utilice un marcador de lipos junto con el mecanismo de reflexin para crear un mtodo que emplee la

public class BankTeller (

1190 Piensa en Java versin con argumentos de ne\>lnstunce( ) con el fm de crear un objeto de una clase con un constructor que tenga argumentos. Ejercicio 23: (I) Modifique Pac t ory Const rain t.java para que creato( ) admita un argumento.

Ejercicio 24: (3) Modifique el Ejercicio 21 para que los objetos factora se almacenen en el mapa en lugar de en

ClasrO. Matrices de genricos

Como hemos visto en Erascd.java. no se pueden crear matrices de genricos. La solucin consiste en utilizar un contenedor ArrayLst en todos aquellos lugares donde necesitemos crear una matriz de genricos: //: genertcs/ListO fGenerics.java import 3 ava.uti1.; public class ListOfGenercs<T> ( prvate List<T> array * public class BankTeller (

1191 Piensa en Java new ArrayLst<T>O; public void addlT Item) ( array.add(itera); J public T get(int index) { return array.get(ndex); ) I ///:-

Aqu obtenemos el comportamiento de una matriz, sin renunciar por ello a las comprobaciones de tipos en tiempo de compilacin que los genricos permiten.

En ocasiones, seguiremos necesitando crear una matriz de tipos genricos (ArrayUit, por ejemplo, utiliza matrices internamente) Resultu interesante saber que podemos definir una referencia de una forma tal que haga que el compilador no se

queje. Por ejemplo / f: qenerics/ArrayQfGene ncReference .java public class BankTeller (

1192 Piensa en Java class Generic<7> () public class ArrayOfGenericRefe rence { static Generic<Integer>[] gia;

} ///:-

El compilador acepta esta sintaxis sin generar ninguna advertencia. 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 la misma estructura (tamao de cada posicin de la matriz y disposicin de la matriz) independientemente del tipo que almacene. 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 ClassCastExccplfon //: generics/ArrayOfGeneric.java public class ArrayOfGener ic { static final ine S I Z E = 100; static Generic<Inte ger>U gia; SuperessWarnings("unchecked") public static veid mam iString [] args) { // Se compila; genera ClasaCastException: //i gia = (Generic<Integer>[])new Object(SIZEJ; public class BankTeller (

1193 Piensa en Java // El tipa en tiempo de ejecucin es el raw (despus del borrado): gia = (Generic<integer>[])new Generic [SIZE]; System.out.println(gi.getClass(J .getSimpleName()); gia [O] = new Generic<Integer>(); //i giatil = new ObjectO/ // Error de tiempo de compilacin // Descubre la discordancia de tipos en tiempo de compilacin: / / ! gia [2] = new Generic<Double>0 ;

) ) / Output: Generic ti ///:-

El problema es que las matrices controlan su tipo real y esc tipo se establece en el momento de creacin de la matriz. Por tanto, aunque gia haya sido proyectado sobre Generic<lnteger>||, dicha informacin slo existe en tiempo de compilacin (y sin la anotacin flSuppressVYarnings. obtendremos una advertencia debido a dicha proyeccin). En tiempo de ejecucin, sigue siendo una matriz de tipo Object. y eso hace que se produzcan problemas. La nica forma de crear adecuadamente una matriz de un tipo genrico es crear una nueva matriz del tipo resultante del borrado de tipos > efectuar una proyeccin de tipos con dicha matriz.

public class BankTeller (

1194 Piensa en Java Veamos un ejemplo algo ms sofisticado. Considere un envoltorio genrico simple para una matriz: //: generica/GenericArray.java public class GenericArray<T> { private Tfl array; =&SuppressWamings I * unchecked" ) public GenericArray(int szJ { array - (T[J)new Object[sz] t i public void put(int index, T item) ( array[index] * item; i public T getint index) { return arraylindex] : ) // Mtodo que expone la representacin subyacente: public T[] rep) { return array; ) public static void main(String[J args) { GenericArray<Integer> gal * new GenericArray<Integer>(10); // Esto provoca una excepcin ClassCastExcepton: / / l Integer ti ia = gai.repO; // Esto es correcto: Object [J oa gai.repO;

i i ///>-

public class BankTeller (

1195 Piensa en Java Como ames, no podemos decir T|| array = new T|sz|, por lo que creamos una matriz de objetos y la proyectamos.

El mtodo rep( ) devuelve una matriz T[|, que en main( ) debe ser una matriz lnteger[| para gal, pero si llamamos a ese mtodo y tratamos de capturar el resultado como una referencia a Integer||. obtenemos una excepcin Cl*ssCa$tException, de nuevo debido a que el tipo real en tiempo de ejecucin es Object||.

Si compilamos GcnericArray.java despus de desactivar mediante comentarios la anotacin a SuppreisWarnings. el compilador genera una advertencia: Note: GenericArray. java uses unchecked or unsafe operan loas. Note: Recompile with -XIint:unchecked for detaiis.

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 -Xlintrunchecked: GenericArray.java:7: waming: cast found : java.lang. ObjectU required: T[] public class BankTeller ( [uncheckedj unchecked

1196 Piensa en Java array * (TU) A new Object [szj ; warmng

Ciertamente, el compilador se est quejando sobre la proyeccin de tipos. Puesto que las advertencias introducen ruido dentro del proceso de diseo, lo mejor que podemos hacer, una vez que verifiquemos que se produce una advertencia, es desactivarla utilizando (a SuppressWarnlngs. 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 Object|). Si lo proyectamos inmediatamente sobre T||, entonces el tipo real de la matriz se perder en tiempo de compilacin y el compilador podra no aplicar algunas comprobaciones de potenciales errores. Debido a esto, es mejor utilizar una matriz Object|| dentro de la coleccin y aadir una proyeccin a T cuando la usemos como elemento de la matriz. Veamos cmo se aplicara esta solucin con el ejemplo GcnericArray.java: //: generics/GenericArray2.java public class GenericArray2 <T> { private Object|] array; public GenericArray2 {int sz) ( array = new Object [szj;

public class BankTeller (

1197 Piensa en Java i public void putUnt index, ? irem) ( array[ndex] = tem; i 'iSuppressWamings ("unchecked") public T get(int ndex) ( return (T)arraylndex]; ) aSuppressWamings l" unchecked" ) public Tf 1 repl) { l return (T(l)srray; // Advertencia: proyeccin no comprobada

public static void main(Stringf] args) ( GenericArray2<Integer> gai * new GenericArray2<Integer>(10); for(int i = 0; i < 10; i +) gai.put(i, i); for (int i * 0; i < 10; i +t) System, out-print (gai. get (i) System.out.printlnO; try f Integer[] ia ^ gai.rep(); ) catch(Exception e) ( System.out.println(e)j } " ");

) ) / Output:(Sample) 0 1 2 3 4 5 6 7 8 9 ^ava.lang.ClassCastException: [Ljava.lang.Object? cannot be cast to ILjava.lang.Integer;

public class BankTeller (

1198 Piensa en Java Inicialmentc. 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 (S SuppressVVarnings. seguimos obteniendo advertencias que nos dicen que faltan comprobaciones. Sin embargo, la representacin interna es ahora Object|| en lugar de T||. 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, s invocamos repf ). el mtodo vuelve a intentar proyectar Object|| sobre T||. lo que sigue siendo incorrecto y genera una advertencia en tiempo de compilacin y una excepcin en tiempo de ejecucin. Por tanto, no hay ninguna manera de cambiar el tipo de la matnz subyacente, que slo puede ser ObjectU La ventaja de tratar array internamente como Objcctfl en lugar de como T|| es que resulta menos probable que nos olvidemos del tipo de la matn/ en tiempo de ejecucin e introduzcamos accidentalmente un error (aunque la mayora de esos errores, y quiz todos, se detectaran rpidamente en tiempo de ejecucin).

Par3 el nuevo cdigo que desarrollemos, lo que debemos hacer es pasar un testigo de tipos. En dicho caso. GenericArray tendra el aspecto siguiente: //: aenerics/GenericArrayWithTypeToken.java mport java.lang.reflect.30? public class GenericArrayWithTypeToken<T> ( privare T[J arrayj SuppressWarnings i "unchecked" > public GenericArrayWithTypeToken(Class<T> type, mt S2) { array = (T[])Array.newlnstance(tyoe, sz) ; i public void put(int ndex, T item) ( array(Index] = Item;

} public T get(int index) ( return arraytindex]; ) httptf/lcr.hfog.rpi.om 2004/00/pu^ting ibrcnigh trasurc unsuvrhtml public class BankTeller (
30

1199 Piensa en Java // Exponer la representacin subyacente: public T[] repO { return array; } public static void main (StringU args) { GenericArrayWthTypeT okencInteger* gai = new GenericArrayWithTypeT oken<Inteaer> Integer.class, 10)? // Esto ahora funciona: Integerf] ia = gai.repO?

} ///:*

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 matnz que necesitemos, aunque el mensaje de advertencia referido a la proyeccin de tipos deber ser suprimido mediante SuppressVVarnings Una vez que obtengamos el tipo real, podemos devolverlo y obtener los resultados deseados como podemos ver en main( ). El tipo de la matriz en tiempo de ejecucin es el tipo exacto T||.

Lamentablemente, si examinamos el cdigo fuente en las bibliotecas estndar de Java SF.5, podremos ver que existen por todas partes proyecciones de matrices de tipo Object public class BankTeller (

1200 Piensa en Java a tipos parametrizados. Por ejemplo, he aqu el constructor Array Lis! que utiliza como argumento un contenedor Collectinn. despus de simplificar y limpiar el cdigo un poco: public ArrayList(Collection c) { size * c.size{); elementData = (En Jnew Object [size] ; c.toArrav(elementData) ;

Si examina el cdigo de Array List.ja va. podr encontrar multitud de estas proyecciones Y qu es lo que sucede cuando compilamos este cdigo? Note: ArrayList. java uses unchecked or ur.safe operations. Note: Recompil with -Xlint:unchecked for details.

Como puede ver. las bibliotecas estndar generan una multitud de advertencias. Si el lector ha trabajado antes con C. y especialmente con el C 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 compilador no genere ningn tipo de mensaje, a menos que el programador deba hacer algo con ese mensaje.

public class BankTeller (

1201 Piensa en Java F.n su bitcora web. Neal Ciafter (uno de los principales desarrolladores de Java SE5) 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, aunque en los archivos de cdigo fuente de la biblioteca Java aparezcan ciertas tcnicas, eso no quiere decir que esa sea la forma correcta de hacer las cosas. Cuando examine el cdigo de biblioteca no d por sentado que se trate de un ejemplo que haya de seguir en su propio codigo. Lmites

Ya hemos presentado brevemente los limites anteriormente en el capitulo. Los limites uos permiten imponer restricciones a los tipos de parmetros que pueden utilizarse con los genricos. Aunque esto nos permite 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 informacin de tipos, los nicos mtodos que podemos invocar para un parmetro genrico al que no se le hayan impuesto limites son aquellos disponibles para Object. Sin embargo, si somos capaces de restringir el parmetro para que se corresponda con un subconjuuto de tipos, entonces podemos invocar todos los mtodos de dicho subconjunto. Para implementar esta restriccin, el mecanismo de genricos en Java utiliza la palabra clave extends. Es importante comprender que extends tiene un significado bastante distinto al normal dentro del contexto de los limites de genricos. El siguiente ejemplo ilustra los fundamentos bsicos de los mecanismos de limites: //: generics/fiascBounds.java interface HasColor ( java.awt.Color qetColort); ) class Colored<7 extends HasColor> ( T tem; public class BankTeller (

1202 Piensa en Java ColoredlT tem) ) this.item = tem; ) T getltemO { return tem; } // El lmite nos permite invocar un mtodo: java.awt.Color color) { return item.aetColor(); }

) class Dimensin ( public int x, y, z; l // Esto no funcionar -- primero hay que definir la clase // y luego lae interfaces: // class ColoredDimensioncT Ditaension> ( // Mltiples lmites: class ColoredDimension<T extends Dimensin & HasColor> ( T item; ColoredDimension(T item) { this.item = item; } T getItem() ( return tem; } java.awt.Color colorU ( return item.getColor O } extends HasColor &

public class BankTeller (

interface Weight { int weight(J j ) 1203 Piensa en Java )// Al igual que con la herencia, slo se puede tener una // clase concreta, pero puede haber mltiples interfaces: class Solid<T extends Dimension & HasColor & Weight> { T item; Solid(T Item) ( this.item = item; ) T getItem() ( return item; ) java.awt.Color color() f return item.getColor(); } int weight() { return item, weight () ; }

) class Bounded extends Dimension implements HasColor, Weight ( public java.awt.Color getColorO ( return null; ) public int weight() { return 0; } i public class BasicBounds ( public static void main(String[] args) ( Solid<Bounded> solid = new Sol id Bounded (new BoundedO); sol id.color(); solid.qetYO ; solid.weight();

interface Weight { int weight(J j ) 1204 Piensa en Java ) ///:-

Habr observado que BasicBotinds.juva parece contener redundancias que podran eliminarse recurriendo al mecanismo de herencia, lin el siguiente ejemplo, podemos ver cmo cada nivel de herencia aade tambin restricciones de lmite: //: generics/InheritBounds.java class HoldItem<T> ( T iten; Holdltem(T item) ( this.item item; } T getltemO { return item; J 1 class Colored2<T extends HasColor extends HoldltemcT ( Colored2(T item) ( super(item); ) java.awt.Color color<) ( return item.getColort>; )

} class ColoredDimension2<T extends Dimension & HasColor extends Colored2<T> {

interface Weight { int weight(J j ) 1205 Piensa en Java ) class Solid2<T extends Dimension & HasColor & Weight extends ColoredDlmension2<T> ( Solid2(T item) { super{item); ) int weight() { return item.weight 0 ; ) public class LnheritBounds ( public static void mam (String [] args) ( Solid2Bounded* solid2 = new Sold2<Bcunded'n ew Bounded!'>; solid2.color 0; solid2.getY(); sol id2.weight 0 r

) ///:-

interface Weight { int weight(J j ) 1206 Piensa en Java iloldltem simplemente almacena un objeto, por lo que este comportamiento es heredado dentro de Colorcd2, que requiere que tambin su parmetro se corresponda con HasColor ColorcdDimenson2 y Solid2 extienden todava mas la jerarqua y aaden limites en cada nivel. Ahora, los mtodos son heredados y no tienen por qu repetirse en cada clase.

I le

aqu un ejemplo con ms niveles: //: generics/EpicBattle. ;java // Ejemplo de lmites para genricos de Java, import ja va.util.*; interface Superpower { mterface XRayVison extends Superpower { void seeThroughWallat) ; i interface SuperHearing extends Superpower { void hearSubtleNoises();

) interface SuperSmell extends Superpower ( void trackBySmell [) ,*

interface Weight { int weight(J j ) 1207 Piensa en Java } class SuperHeroPOWER extends Superpower { POWER power; SuperHero(POWER power) { this.power power; ) POWER getPower() { return power; ) I class SuperSleuthcPOWER extends XRayVison extends superHero<PUWEK { SuperSleuth(POWER power) { super(power); ) void see() ( power. seeThroughWalIs (} ,* )

) class CanineHero<rPOWER extends SuperHearing & SuperSmell> extends SuperHerc*POWER { CanineHero(POWER power) { super(power); ) void hear() { powerhearSubtleNoises(); } void smellf) ( power.trackBySmell 0; ) i class SuperHearSmell implements SuperHearing, SuperSmell { public void hearSubtleNoises() (} public void trackBySmell() (}

interface Weight { int weight(J j ) 1208 Piensa en Java ) class DogBoy extends CanineHerocSuperHearSmell ( DogBoyO { super (new SuperHearSmell ()) ; }

1209 Piensa en Java }// Limites en mtodos genricos: static <POWER extends SuperHearng> void useSuperHearing(SuperHero<?CWER> hero) { hero.aetPower(i.hearSubtleNoises(1 ;

) static <POWER extends SuperHearmg & SuperSmell void superFind(SuperHero<POWER> hero) ( hero.getPower().hearSubtleNoises(J; hero.getPower(> ,trackBvSmeli );

) public static void mam (String [] argsi | DogBoy dogooy new DogBoy(); useSuperHearing',dogBoy); superFindidogBoy) // Podemos hacer esto: List<? extends SuperHearmg> audioBoys; // Pero no podemos hacer esto: // List<? extends SuperHearing & SuperSmell> dogBoys;

} J ///:)

1210 Piensa en Java Observe que los comodines (de los que hablaremos a continuacin) estn limitados a un nico limite.

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 limitado por la segunda interfaz. Cree una instancia de la clase que implcmcnta ambas interfaces y demuestre que se puede utilizar con ambos mtodos genricos. Comodines

Ya hemos visto algunos usos simples de los comodines (smbolos de interrogacin dentro de las expresiones de argumentos genricos) en el Capitulo 11, Almacenamiento de objetos y en el Capitulo 14. Informacin de tipos. En esta seccin vamos a explorar esta cuestin con ms detalle.

Empezaremos con un ejemplo que demuestra un comportamiento concreto de las matrices. Podemos asignar una matriz de un tipo derivado a una referencia de matriz del tipo base: )

1211 Piensa en Java II: generics/CovariancArrays.j ava class Fruit {) class Apple extends Fruit (} class Joathan extends Apple (} class Oranqe extendB Fruit (} public class CovariantArrays ( public scatic void main(String(1 args) ( Fruit[) fruit = new Apple[10]; fruit{0} * new Apple(); // OK fruit [ll = new JonathanO; // OK I I El tipo en tiempo de ejecucin es Apple[ ) , no Fruit[] ni OrangeU: try ( // El compilador permite aadir Fruit: fruit[0] = new Fruit); // ArrayStoreException ) catchtException e) ( System.out.println(e) ; ) try ( // El compilador permite aadir Oranges: fruit 101 new OrangeO; l l ArrayStoreException J catchtException e) { System.out.println(e); ) } /* Oucput: java.lang.ArrayStoreExcepti on: Fruit j ava.lang.ArrayStoreExcept ion: Orange ///:-

La primera linea de main< ) crea una matriz de objetos Apple y la asigna una referencia a una matriz de objetos Fruit. fisto tiene bastante sentido, ya que Apple es un upo de Fruit. por lo que una matnz de Apple tiene que ser una matriz de Fruit.

1212 Piensa en Java Sin embargo, si el tipo real de la matriz es Apple||, slo deberamos poder insertar en la matnz un objeto Apple o un subtipo de Apple, lo que de hecho funciona tanto en tiempo de compilacin como en tiempo de ejecucin. Pero observe que el compilador nos permite insertar un objeto Fruit dentro de la mam/ Esto tiene sentido para el compilador, porque dispone de una referencia a Fruit||; como dispone de esa referencia, por qu no debera permitir colocar en la matriz un objeto Fruit. o cualquier cosa que descienda de Fruit. como por ejemplo Orange? Por tonto, la operacin se permite en tiempo de compilacin. Sin embargo, el mecanismo de tiempo de ejecucin pani las matrices sabe que est tratando con una matriz Apple|| y genera una excepcin cuando se inserta un tipo incorrecto de la matriz.

Rl termino "generalizacin* resulta confuso dentro de este contexto. Lo que estamos realmente haciendo es asignar una matriz a otra. El componamiento de las matrices es tal que permite almacenar otros objetos, pero como podemos realizar una generalizacin, resulta claro que los objetos matnz pueden preservar las reglas acerca del tipo de objetos que contienen Es como si las matrices fueran conscientes de qu es lo que estn almacenando, por lo que entre las comprobaciones realizadas en tiempo de compilacin y las realizadas en tiempo de ejecucin, no podemos tratar de abusar del mecanismo de matnces.

Esta forma de comportarse de las matrices no resulta tan temble. porque al final si que detectamos en tiempo de ejecucin que hemos insertado un tipo incorrecto Pero uno de los objetivos 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? //: generics/NonCovariantGenencs. ]ava // (CompileTimeError) (Wont compile) import j ava.til. ; public class NonCovariantGenerics ( // Error d e compilacin: tipos incompatibles: liist<Fruit> flist - new ArrayLiat<Apple>();

1213 Piensa en Java ) ///:-

Aunque pudiramos sentirnos 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 genenco rea- d o n a d o con objetos Fruit Si el compilador, como sucede en el caso de las matrices, supiera lo suficiente acerca del cdigo como para determinar que hay contenedores implicados, quiz podra ser algo ms permisivo. Pero el compilador no sabe que es asi. por lo que rehusar permitir la "generalizacin" De todos modos, tampoco se trata de una generalizacin real; una lista de objetos Apple no es una lista de objetos Fruit. Una lisui de objetos Apple permitir almacenar objetos Apple y subtipos de Apple, mientras que una lista de objetos Fruit permitir almacenar cualquier clase de objetos Fruit Es cierto que esto incluye a los objetos Apple, pero eso no la hace una lista de objetos Apple; seguir siendo una lista de objetos Fruit Una lista de objetos Apple no es equivalente en lo que respecta a tipos a una lista de objetos Fruit. an cuando un ob|eto 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 compilacin como en tiempo de ejecucin: sin embargo, con los genricos, el compilador y el sistema de ejecucin no pueden saber lo que queremos hacer con los tipos y cules son las reglas que deberan aplicarse.

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 permiten. //: generics/GenericsAndCovarianc )

1214 Piensa en Java e.java import java.til.*/ public class GenericsAndCovarlance { public static void main(String[] args) { // Los comodines permiten la covarianza: List<? extends Fruit> flist = new ArrayList<Apple>(); // Error de compilacin: no se puede aadir cualquier tipo de objeto: t flist.addInew Apple!)); f f flist .add (new Fruit O); // flist.add(new Object!)); flist.add(nuil); // Legal pero poco interesante // Sabemos que devuelve al menos Fruit; Fruit f = flist.aet10J;

} ///:-

El tipo de flist es ahora List<? extends Fruit>, lo cual puede leerse como una lisia de cualquier tipo que herede de Fruit'V Sin embargo, esto no significa que la lisia pueda almacenar cualquier tipo de objeto Fruit El comodn hace referencia a un tipo concreto, por lo que significa algn tipo especfico que la referencia flist no especifique. Por tanto, la lista que se asigne tiene que estar almacenando algn tipo especificado como Fruit o Apple, aunque para poder realizar la generalizacin a flist. ese tipo se especifica como no importa cul sea". )

1215 Piensa en Java Si la nica restriccin es que la lista almacene un tipo o subtipo de F ruit especfico, pero no nos importa en realidad cul sea ste, entonces qu es lo que podemos hacer con dicha lista? Si no sabemos el tipo de objeto que la lista est almacenando, cmo podemos uadir con seguridad un objeto? Al igual que sucede con la generalizacin de la matriz Co\ ariant Arravs.java. no podemos aadir cualquier objeto que queramos, lo nico que sucede es que, en este caso, el compilador prohbe las operaciones no permitidas antes, en lugar de que sea el sistema de ejecucin el que se encargue de prohibirlas. En otras palabras, esto nos permite descubrir el problema bastante antes.

Podramos pensar que las cosas se han ido un poco de las manos, porque ahora ni siquiera podemos aadir un objeto Apple a una lista que habamos dicho que si poda almacenar objetos Apple. En efecto, asi es. pero es que el compilador no tiene ningn conocimiento de eso. Un contenedor Llst<? extends Fruit> puede apuntar a una lista List<Orange> Una vez que hacemos este tipo de generalizacin, perdemos la posibilidad de pasar ningn objeto, m siquiera de tipo Object.

Por otro lado, si invocamos un mtodo que devuelva Fruit, esa operacin s es segura porque sabemos que cualquier cosa que haya en la lista deber ser al menos de tipo Fruit, por lo que el compilador permitir la operacin.

Ejercicio 26: (2) Ilustre la covarianza de matrices utilizando objetos de tipo Number e Integer

1216 Piensa en Java Ejercicio 27: (2) Demuestre que la covarianza no funciona con las listas utilizando objetos de tipo Number e Integer

y luego introduzca comodines. Hasta qu punto es inteligente el compilador?

Ahora, podramos pensar que no podemos invocar mngUn mtodo que admita argumemos. pero vamos a analizar este ejemplo: //: generics/Compilerlntelligence.ja va import java.til.;0; public class CompilerIntel1igence ( public static void main (String [] args) ( List<? extends Fruit> flist = Arrays.asList(new AppleO); Apple a (Apple)flist.get(0) ; // Ninguna advertencia flist.contains(new Apple()); t El argumento es 'Object' flist. indexQf < new AppleO); // El argumento es 'Object'

} ) n/-.~ )

1217 Piensa en Java Podemos ver llamadas a contuins( ) e inde\Of() que toman objetos Apple como argumentos, y esas llamadas son perfectamente vlidas. Significa esto que el compilador si que examina el cdigo para ver si un mtodo concreto modifica su objeto?

Examinando Id documentacin de ArrayLst. podemos comprobar que el compilador no es tan inteligente. Mientras que add( ) toma un argumento del tipo de parmetro genrico, conlains() e indexOf() toman argumentos de tipo Object. Por tanto, cuando especificamos un contenedor ArrayList<? extends Frut>. el argumento de add() se conviene en *? extends Fruit*. A partir de dicha descripcin, el compilador no puede saber qu tipo especfico 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 invocar un mtodo (como add( )) si hay un comodn en la lista de argumentos.

Con contans( ) e ndexOf( ). los argumentos son de tipo Object. por lo que no hay ningn comodn implicado y el compilador si que permite la llamada. Esto significa que es responsabilidad del diseador de clases genricas definir qu clases s o n seguras" y utilizar el tipo Object para sus argumentos. Para prohibir una llamada cuando el tipo se utilice con comodines. utilice el parmetro de tipo dentro de la lista de argumentos.

Podemos ilustrar esto con tina clase Holdcr muy simple: / / : generics/Holder.java public class Holder<T> ( private T valu; public HolderO (} public Holder{T val) { valu = val; } )

1218 Piensa en Java public void set (T val) { valu * val; } public T get O { returo valu; ) public boolean equals(Object obj) { retum valu.equalp(obj); i public static void mainStringH args) { Holder<Apple> Apple = new Holder<Apple>(new AppleO); Apple d = Apple.getO; Apple.set(d); //Holder<Fruit> Holder<? extends Fruit p = fruit.getO; d = (Apple)fruit.get(); // Devuelve Object* try { Orange c * (Orange)fruit.get0; // Ninguna advertencia | catch(Excepton e) { System.out .prmtln(e) ; ) //fruit .set (new 0 //fruit .set (new 0 AppleO); // No se puede invocar invocar set set Fruit = Apple; // No se puede generalizar Fruit> fruit = Apple; // OK

Fruit 0 ) ; // No se puede

System.out.orintln(fruit.eouals(d)); // OK

) ) / Output: (Sample) java.lang.ClassCastException: Apple cannot be cast to Orange true ///:-

1219 Piensa en Java floldcr tiene un mtodo set() que toma un argumento T. un mtodo get() que devuelve T. y un mtodo cquah( ) que toma un argumento de tipo Object. Como ya hemos visto, si creamos un contenedor Holder<Apple>. no podemos generalizar

lo.i Holder<Fruit>. pero si que podemos generalizarlo a Holder<? extends Frut>. Si invocamos get(), slo devuelve un objeto Fruit. que es lo nico que el compilador sabe, teniendo en cuenta que el limite que hemos hecho es cualquier cosa que extienda Fruit. Si el programador tiene mas informacin acerca de los objetos implicados, puede efectuar una prediccin sobre un tipo especfico 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 compilador no puede verificar que cualquier cosa" sea segura en lo que a tipos respecta.

Sin embargo, el mtodo equa!s( ) funciona correctamente, porque toma un objeto Object en lugar de T como argumento. 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 compilador no analiza el cdigo para ver si efectuamos ninguna escritura o lectura real. Contravarianza

Tambin es posible proceder en sentido inverso y utilizar comodines de supertipo. Con esto, lo que expresamos es que e] comodn est limitado por cualquier clase base de una clase concreta, especificando <? super MvCIass> o incluso utilizando un parmetro de tipo: <? super T> (aunque no se puede dar un limite de supertipo a un parmetro genrico, es decir, no podemos escribir <T super V! yClass>). Esto nos permite pasar con segundad un objeto de un cierto a un tipo genrico. De este modo, con los comodnes de supertipo piulemos escribir dentro de un contenedor de tipo Collection )

1220 Piensa en Java //: generics/SuperTypeWildcards.java import java.til.*; public class SuperTypeWildcards ( static void writeTolList<? super Apple apples) { apples.add(new Apple(}); apples.add(new JonathanO); apples.add(new Fruit01; // Error

) } ///-

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 lista, porque eso permitira que la lista se abriera a la adicin de tipos distintos de Apple, lo que violara la segundad esttica de tipos.

Por tanto, podemos pensar en los lmites de subtipo y de supertipo en trminos de cmo se puede escribir* (pasar a un mtodo) en un tipo genrico y cmo se puede leer (devolver desde un mtodo) de un tipo genrico.

1221 Piensa en Java Los lmites de supertipo relajan las restricciones de lo que podemos pasar a un mtodo: //t generics/GenericWriting.java import java.til.*? public class Genericwrting ( static <T> void wrteExact(Lst<T> list, 7 item) ( list.add(tem);

) static List<Apple apples = new ArrayListcAppleO; static List<Pruit fruit * new ArrayList<FruitI)/ static void flO { writeExact(apples, new Apple OI; // wrteExact (fruit, new AppleO ) / / Error: i // 7ipos incompatibles: se encontr Fruit, se requiere Apple

static <T voi writeWithWildcard{Listc7 super T> list, T tem) { list.addiitem);

) atatic void 2 0 { writeWithWildcardapples. new AppleO); writeWithWldcard(fruit, new AppleO);

1222 Piensa en Java ) public static void main{String[] args) ( f1 O; f2 O; ) 1 ///:-

El mtodo writeExact( ) utiliza un tipo de parmetro exacto (comodines). En fl( ) podemos ver que esto funciona correctamente. siempre y cuando nos limitemos a insertar un objeto Apple en un contenedor I.st<Applc>. Sin embargo, wrltcExacK ) no permite insertar un objeto Apple dentro de un contenedor List<Fruif>. an cuando nosotros sepamos que eso debera ser posible.

En wriieWiih\Vldcard( ). el argumento es ahora List<? super T>. por lo que la lisia almacena un upo especfico derivado de T; por tanto, resulta seguro pasar T o cualquier cosa que se derive T como argumento a los mtodos de la lista.

podemos ver esto en f2( ). donde sigue siendo posible insertar un objeto Apple en una lista List<Apple>, como antes, pero ahora resulta posible insertar un objeto Apple en una lista Llst<Fruit>, tal como cabria esperar. Podramos realizar este mismo tipo de anlisis como revisin del tema de la covarianza y de los comodines. //: generics/GenencReading.ja va import java.til.; public class GenericReading ( scatic <T> T readExact(List<T> list) ( retum list .get (OJ ; )

1223 Piensa en Java > static List<Apple> apples * Arrays.asList'new Apple(|); static List<Fruit> fruit = Arrays.asList(new FruitM 1 ; // n mtodo esttico se adapta a cada llammada: static void fll) ( Apple a = readExact(appies); i Fruit f = readExact (fruit.) ; f * readExact(apples) j

// Sin embargo, s tenemos una clase, su tipo se // establece en el momento de instanciarla; static class Reader<7> ( T readExact 'List<T> list) ( retum list.qet(0)j )

l static void 2 1 ) ( F.eader<Fruit> fruitReader = new Reader<Fruit> ; Fruit f = fruitReader.readExact(fruit); // Fruit a * fruitReader.readExact(apples)j // Error: i // readExactListFruit>) no puede // aplicarse a (List<Appie>).

static class CovariantReaaer<T> ( T readCovariant(List<? extends list) { retum list.get(O); ii static void f3() ( CovariantReader<Fruit> fruitReader = new CovarantReader<Fruit>() ; Fruit f * fruitReader.readCovariant(fruit); Fruit a = fruitReader .readCovariant (apples) ; )

1224 Piensa en Java ) public static void main(StringU args) ( fll); f 2 ( ) ; f 3 () ;

) ///:-

Como antes, el primer mtodo readE.\act( ) utiliza 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 estanco readFxact( ) se adapta efectivamente a cada llamada a mtodo y devuelve un objeto Apple de una lista List<Apple> n un objeto Fruit de una lista List<Fruit>. como puede verse en fl< ). Por tanto, si podemos resolver el problema con un mtodo genrico esttico, no necesitamos recurrir a la covarianza si nicamente nos estamos limitando a leer.

Sin embargo, si tenemos una clase genrica, el parmetro se establece para la clase en el momento de crear una instancia de dicha clase. Como podemos ver en f2( ), la instancia fruitReader puede leer un objeto Fruit de una lista List<Fruit>, puesto que ese es su tipo concreto. Pero una lista List<Apple> tambin debera producir objetos Fruil y el objeto fruitReader no permite esto. )

1225 Piensa en Java Para corregir el problema, el mtodo CovariantRcader.readCovariant( ) toma una lista List<? extends T>, de modo que resulta seguro leer un objeto T de dicha lista (sabemos que todo lo que hay en esa lista es cuando menos de tipo T.

y posiblemente algo derivado de T). Hn 3( ) 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 Jipo T. Cree

una segunda clase genrica Generic2<T> con un nico mtodo que devuelva un argumento de tipo 1 Escriba un mtodo genrico con un argumento contrav ariante de la primera clase genrica que invoque al mtodo de dicha clase. Escriba un segundo mtodo con un argumento covariante de la segunda clase genrica 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 )

1226 Piensa en Java comodn no limitado parece equivalente a emplear un tipo normal. Ciertamente, el compilador parece aceptar, a primera vista, esta interpretacin: //: ger.erics/UnboundedWildcard sl.java iinport j ava . ut il. *; public class UnboundedWildcardsl { static List listl static List<?> list2; static List<? extends Object> list3; static void assignUList list) { listl = list; list2 = list; // list3 * list; // Advertencia: conversin no comprobada // Found: List, Required: List<? extends Object>

} static void assgn2(List<?> list) ( listl = List; list2 * list; list3 = list; static void assign3{List<? extendB Object> list) ( listl = list; list2 c list; Il8t3 = list; t public tLdtic void mdiu <SLi.iny ] auub) { assignl(new ArrayList()); assign2 {new ArrayList 0V; // assign3(new ArrayList()); // Adevertencia: // conversin no comporbada. No encontrado: ArrayList // Requerido: List<? extends Ob}ect> assignl(new ArrayList<String>(J); assign2{new ArrayList<String><)) ; assign3 new ArrayList<String>()); // Ambas formas son aceptables como List<?>: )

1227 Piensa en Java List<?> wildLst new ArrayList(); wildList = new ArrayList<String>O; assignl(wildLst) ; assign2(wildList); assign3(wildList);

) ///:-

Hay muchos casos como los de este ejemplo en que al compilador no le importa si utilizamos un tipo normal o <?>. En dichos casos. <?> parece completamente superfluo. 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 utilizando aqu no es un tipo normal, aunque en este caso el parmetro genrico puede referirse a cualquier tipo.

L'n segundo ejemplo muestra un uso importante de los comodines no limitados. Cuando estemos tratando con mltiples parmetros genricos, a menudo es importante permitir que un parmetro sea de cualquier tipo al mismo tiempo que se asigna un tipo concreto al otro parmetro: } f \ generics/UnboundedWiIdears2. ;java import. java.til.*; public class tTnboundedWildcards2 ( static Map mapl; static Map<?,?> map2; static Map<Strmg, ?> map3 ; static void assignl(Map map) ( mapl = map; ) static void assign2(Map<?,?> map) ( map2 = map; ) static void assign3(Map<3tring,?> map) { map3 = map; } public static void main(String[] args) ( assignl(new HashMap0); assign2(new HashMapO)/ )

1228 Piensa en Java // assign3 (new HashMapO); I I Advertencia: I I Conversin no comprobada. Encontrado: HashMap I I Requerido: Map<String,?> assignl(new HashMap<String.Integer>0); assign2Inew HashMap<String,lnteger>0); a3sign3(new HashMap<String,Integer>{));

l ///'*

Pero de nuevo, cuando lo que tenemos son todos comodines no limitados, como por ejemplo en Map<?,?>. el compilador parece no efectuar distinciones con respecto a un mapa normal y corriente. Adems. I nboundedWildcarcIsl.java muestra que el compilador trata List<?> y LUt<? exlcnds Object> de manera diferente.

Lo que resulta ms confuso es que el compilador no siempre diferencia entre, por ejemplo. List y Lisf<?>, 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 )

1229 Piensa en Java equivalente a Lst<Object>, y List es de hecho equivalente a Llst<Object> Sin embargo, estas afirmaciones no son completamente ciertas. List quiere decir en la prctica una lista simple que almacena cualquier tipo de objeto Objecf. mientras que List<?> significa una lista no simple de algn tipo especifico, aunque no sabemos qu tipo es ese".

En qu casos se preocupa el compilador de las diferencias entre los tipos normales y los tipos que incluyen comodines no limitados? El .siguiente ejemplo utiliza la ela.se IIoldcr<T> que hemos definido anteriormente. Contiene mtodos que toman Holder como argumentos, pero de distintas maneras: como tipo normal, con un parmetro de un tipo especifico y con un parmetro con un comodn no limitado: //i generics/Wildcardo.java // Exploracin del significado de los comodines. public class Wildcards ( // Argumento normal: static void rawArgs(Holder holder, Object ara) { // holder.set(arg); // Advertencia: // Llamada no comprobada a (TI como miembro // del tipo normal Holder I I holder.set(new WildcardsO); // Misma advertencia
II

No se puede hacer esto; no se dispone de ninguna 'T':

// T t * hclder.getO; // 0Kr pero la informacin de tipos se ha perdido: Object obj = holder.getO;

) t t Similar a rawArgsO. pero con errores en lugar de advertencias:static void unbaundedrg(Holder<?> holder, Object arg] ( // holder.set(arg); // Error: )

1230 Piensa en Java //set(capture of ?J enHolder<capture //no puede aplicarse a(Object) // holder.set(new Wildcards!)); // Mismo error // No se puede hacer esto; no se dispone de ninguna 'T': // Tt * holder.get(); // QK, pero la informacin de tipo3 se ha perdido: Object obj holder.get(); of ?>

) static <7> T exactl(Holder<T> holder) ( T t = holder.get(); return t;

) static <T> T exact2(Hoider<T> holder, T arg) { holder.setlarg); T t = holder.get(); return t;

) static <T> T wildSubtype(Holder? extends T> holder, T arg) ( )

1231 Piensa en Java // holder.set(arg); // Error: //set(capture of ? extends T) en //Holder<capture of ? extends T> // no se puede aplicar a (T) T t = holder.get0; return t;

) static <T> void wildSupertype{Holder<? super T> holder. T arg) { holder.set(arg) ; // T t = holder.get () ,* // Error: // Tipos incompatibles: encontrado Object, requerido T // OK. pero la informacin de tipos se ha perdido: Object obj holder.get() ; } public static void main(String[] args) { Holder raw - new Holder<Long>(); // 0 bien: raw = new Holder I) j Holder<Long> qualified = new HoldercLong>(); Holder<?> unbounded * new Holder<Long>t); Holder? extends Long> bounded = new Holder<Long>(); Long ing = 1L; rawArgs < raw. ing); rawArgs(qualified, Ing); rawArgs(unbounded, Ing); rawArgs(bounded, Ing); unboundedAra' raw, ing); unboundedArg(qualified, Ing); unboundedArg(unbounded, Ing); unboundedArg(bounded. Ing); // Object rl exactl(raw); // Advertencias )

1232 Piensa en Java :// Conversin no comprobada de Kolder a HcIder<T> //Invocacin de mtodo no comprobado: exactl(Holder<7>) // aplicadaa (Holder} Long r2 = exactl(qualified); Object r3 = exactlIunbounded:; // Debe devolver Object Long r4 = exactl(bounded) ; // Long r5 * exact2(raw, lng); // Advertencias: //Conversin nocomprobada de Holder a Holder<Long> ( f Invocacinde mtodo no comprobado: exact2(Holder<T>,T) // aplicadaa (Kolder,Long) Long r6 * exact2Iqualified, lng) ; // Long r7 = exact2(unbounded, lng); // Error: t t exact2(Holder<T>,T) no puede aplicarse a // (Holder<capture of ?>,Long) // Long r8 = exact 2 (bounded, lng) // Error: // exact2(Holder<7>,T) no puede aplicarse a / / to (Holder-ccapture of ? extends Long>,Long) // Long r9 = wildSubtype(raw, lng); // Advertencias: // Conversin no comprobada de Holder // a Holder<? extends Long> // Invocacin de mtodo no comprobado: // wildSubtype{Holder<? extends T>,T) // aplicada a (Holder,Long) Long rlO wildSubtype(qualified, lng); // OK. pero slo puede devolver Object: Object rll = wildSubtype(unbounded, lng); Long rl2 wildSubtype (bounded, ing); //wildSupertype(raw, lngl; // a Holder<? super Long> ) // Advertencias: // Conversin no comprobada de Holder

1233 Piensa en Java // Invocacin de mtodo no comprobado: // wildSupertype(Holder<? super T>,T // aplicada a (HolderfLong) wildSupertype(qualified, ing); //wildSupertype(unbounded, lng); //wildSupertype(Holdcr<? superT>fT) no // Error: puede

// aplicarse a (Holder<capture of ?>,Long) // wildSupertype(bounded, lng); // Error: //wildSupertypeHolder<? superT>.T) no puede // aplicarse a (Holder<capture of ? extends Long>,Long)

) ///:-

En rawArgs(). el compilador sabe que Holder es un tipo genrico, por lo que an cuando se lo exprese aqui como un lipo normal, el compilador sabe que pasar un objeto de upo Object a set( ) no resulta seguro. Puesto que se trata de un tiponormal. podemos pasar un objeto de cualquier tipo a set() y dicho objeto se generaliza a Object Por tanto, siempre que ten )

1234 Piensa en Java gamos un tipo normal, estaremos sacrificando posibilidades de comprobacin en tiempo de compilacin. La llamada a get() 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() demuestra que son distintos: este mtodo hace aflorar el mismo tipo de problemas, pero informa de ellos como de errores en lugar de como advertencias, porque el tipo Holder permitir almacenar una combinacin de objetos de cualquier tipo, mientras que Holdcr<?> almacena una coleccin homognea de algn tipo especifico, y no podemos limitamos a pasar un objeto de tipo Object.

En exactl() y exact2( ). podemos ver los parmetros genricos exactos utilizados sin comodines. Como vemos. exact2( ) tiene limitaciones distintas que exactl(). debido al argumento adicional.

En wildSubtype( ), las restricciones en el tipo de Holder se relajan para incluir un contenedor Holdcr de cualquier cosa que extienda T. De nuevo, esto significa que T podra ser Fruit. mientras que holdcr podra ser perfectamente el contenedor Holder<Apple>. Para impedir que se inserte un objeto range en un contenedor Holder<Apple>. la llamada a set( ) (o cualquier mtodo que tome un argumento referido al parmetro de tipo) no est permitida. Sin embargo, seguimos sabiendo que cualquier cosa que extraigamos de un contenedor Holder<? extends Fruit> ser al menos de tipo Fruit. por lo que s esta permitido invocar jet( ) (o cualquier mtodo que genere un valor de retomo que se corresponda con el parmetro de tipo).

1235 Piensa en Java Los comodines de supertipo se muestran en \MldSupcrt>pe( ). que tiene el comportamiento opuesto a \vildSubtypc( ) liol- 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 funcione con un tipo base tambin funcionar, polimrficamente, con un tipo derivado (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 Objeet.

Este ejemplo tambin muestra las limitaciones relativas a lo que se puede V no se puede hacer respecto a un parametro no limitado. El mtodo que ilustra esta situacin es unhouiided( ) no se puede invocar gel( ) o set( ) con T porque no disponemos de ningn T.

En main( ). podemos ver cules de estos mtodos pueden aceptar cada uno de estos mtodos sin errores ni advertencias. Para garantizar la compatibilidad de migracin. ra\\Args( ) admite todas las diferentes variaciones de Holdcr sin generar advertencias. F.I mtodo unboundedArj*( ) 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 upo genrico exacto' (comodines) obtendremos una advertencia, porque el argumento exacto est esperando informacin que no existe en el tipo normal. Y si pasamos una referencia no limitada a cxactl(). no existe la suficiente informacin de tipos como para establecer el tipo de retome

1236 Piensa en Java Podemos ver que exact2( ) tiene el mayor nmero de restricciones, ya que desea disponer exactamente de un Holder<T> y de un argumento de tipo T. y debido a ello genera errores o advertencias a menos que le entreguemos los argumentos exactos. 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 retomo de un tipo determinado a partir de nuestro argumento genrico (como puede verse en wldSubtype()) o de si queremos pasar argumentos con un tipo determinado a nuestro argumento genrico (como puede verse en \vldSuperfype()).

Por tanto, la ventaja de utilizar tipos exactos en lugar de tipos con comodn es que podemos hacer ms cosas con los para- metros genricos. Sin embargo, utilizar comodines nos permite aceptar como argumentos un rango ms amplio de tipos parainetrizados. El programador deber decidir cul es el compromiso 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 utilice el tipo exacto. El siguiente ejemplo muestra esta tcnica que se denomina conversin de captura porque el tipo no especificado con comodn se captura y se eon\ ierte en un tipo exacto. Aqu, los comentarios acerca de las advertencias slo se aplican si se elimina la anotacin ( SuppressWarnlngs: //: generica/CaptureConversion.java public class CaptureConversion { static <T> void fl(Holder<T> holderJ ( T t = holder. get O System.out.prntln(t.getClassO.aetSimpieName()); l static void f2{Holder<?> holder) { fl{holder); // Llamada con tipo capturado i )

1237 Piensa en Java j'SuppressWarnings ("unchecked") public etatic void main(String{J args) ( Holder raw = r.ew Kolder<Integer>(11; // fl(raw); // Genera advertencias f2(raw); // Sin advertencias Holder rawSasic * new Holder O; rawBasic. set inew ObjectO); // Advertencia f2 irawBasic); // Sin advertencias // Generalizacin a Holder<?>. sigue sabiendo cmo manejarla: Hoider<?> wildcarded * new Holder<Double>(1.0); 2 wildcarded); l } I Output: Integer Object Double

///:-

Los parmetros de tipo fl( ) son todos exactos, sin comodines ni limites. 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 f2( ). se invoca fl( ) y fl( ) requiere un parmetro conocido. Lo que est sucediendo es que el tipo de parmetro se captura en el proceso de invocacin de !2( ). de modo que puede utilizarse en la llamada a fl( ).

1238 Piensa en Java El lector podra preguntarse si esta tcnica podra utilizarse para escribir, pero eso requerira que pasramos un tipo especifico junto con HoIder<?>. La conversin de captura slo funciona en aquellas situaciones donde necesitamos, dentro del mtodo, trabajar con el tipo exacto. Observe que no se puede devolver T desde f2( ). porque T es desconocido para f2( ). La conversin de captura resulta interesante, pero bastante limitada.

Ejercicio 29: (5) Cree un mtodo genrico que tome como argumento un contenedor Holder<List<? Determine qu

mtodos puede o no invocar para el contenedor Holder y para la lista List. Repita el ejercicio para un argumento de tipo List<Holder<? Problemas

Esta seccin analiza diversos tipos de problemas que surgen cuando se intentan utilizar los genricos de Java. No pueden usarse primitivas como parmetros de tipo

1239 Piensa en Java Como se ha mencionado anteriormente en este capitulo, una de las limitaciones existentes en los genricos de Java es que no pueden utilizarse primitivas como parmetros de tipo. No podemos, por ejemplo, crear un contenedor ArrsiyList<nt>

La solucin consiste en utilizar las clases envoltorio de las primitivas en conjuncin con el mecanismo de conversin automtica de Java SE5. Si creamos un contenedor ArrayList<lnteger> y utilizamos enteros primitivos con este contenedor, descubriremos que el mecanismo de conversin automtica entra en accin y se encarga de convertir entre enteros y objetos Integer automticamente, por tanto, es como si dispusiramos de un contenedor ArrayList<int>: //: generics/ListOfInt.java // El mecanismo de conversin automtica compensa la incapacidad //de utilizar primitivas en los genricos, import java.til.*; public ciass ListOflnt { public static void main(String[] args) ( List<Integer> li = new ArrayList<Integer> O ; forint i * 0; i < 5; i*-) li.add(i); forint i : 11) System.out .prmt {i - " ");

) ) / Output:
0

12 3 4

1240 Piensa en Java *///:-

Observe que el mecanismo de conversin automtica admite incluso la utilizacin de la sintaxis foreach para generar valores Int

En general, esta solucin funciona adecuadamente, ya que podemos almacenar y extraer apropiadamente valores primitivos enteros. Se producen ciertas conversiones, pero stas se llevan a cabo de forma transparente. Sin embargo, si las cuestiones de rendimiento son un problema, podemos utilizar una versin especializada de los contenedores adaptada para tipos primitivos: un ejemplo de versin de cdigo fuente abierto para este lipo de contenedores especializados es org.apache. commons.coilections.prmitives

He aqu otro enfoque, que crea un conjunto de bytes: //: generics/ByteSet.java import java.ueil.*; public class ByteSet { Byte M possibles ( 1,2,3.4.5.6.7.8.9 ) Set<Byte> mySet new HashSet<Byte>{Arraye.asList(possibles)); // Pero no se puede hacer esto: // Set<Byte> mySet2 = new HashSet<Byte>( // Arrays.<Byte>asList(1,2,3,4,5,6,7,8,9)); )

1241 Piensa en Java } ///:-

Observe que el mecanismo de conversin automtica resuelve algunos problemas, pero no todos ellos. El siguiente ejemplo muestra una interfaz Generator gencrica que especifica un mtodo next) que devuelve un objeto con el tipo especificado del parmetro. La clase FArray contiene un mtodo genrico que utiliza un generador para rellenar una matriz con objetos (hacer la clase genrica no funcionara en este caso porque el mtodo es esttico). Lasimplementaciones de Generator

provienen del C apitulo 16. Matrices, y en main( ) podemos ver cmo se utilizaFArray.filK para rellenar matrices

objetos: //: generics/PrimitiveGenericTest.java import net.mindview.til // Rellenar una matriz utilizando un generador: class FArray ( public static <T> Ti] fill(T[] a, Generator<T> gen) { for{int i = 0; i < a.length; i++) a [i] * gen.next O ; retum a;

1242 Piensa en Java ) i public class Primit-iveGenericTest { public static void main(String[j aras) ( StringLl strings = FArray.filK new Stringl7j, new RandomGenerator.String<10)I; for(String s : strings) System.out.println(a); IntegerU integers FArray.filK new Integer[7]( new RandomGenerator.IntegerO); for{int i: integers) System.out.println( i); // El mecanismo de conversin automtica no funciona en este caso. // Lo siguiente no podr compilarse: // int [] b = // PArray.fi11 {new intf7], new RandlntGenerator());

) ) /* Output: VNzbmyGcF OWZnTcQrG

15 Genricos 1243 seGZMmJMRoE guEctJOneOE dLsmwHLGEa hKcxrEqUCB bklnaMesbt 7052 6665 2654 3909 5202 2209 5458

///:-

Tuesto que RandomGenerator.Integer implementa Generator<lnteger>. cabria esperar que el mecanismo de conversin automtica se encargara de convertir el valor de next( ) de Integer a int Sin embargo, el mecanismo de conversin automtica no se aplica a las matrices, asi que esta solucin no funciona.

15 Genricos 1244 Ejercicio 30: (2) Cree un contenedor Holder para cada uno de los tipos envoltorio de las primitivas y demuestre que el

mecanismo de conversin automtica Funciona para los mtodos set( ) y get( ) de cada instancia. Implementacin de interfaces parametrizadas

Una clase no puede implementar dos variantes de la misma interfaz genrica. Debido al mecanismo de borrado de tipos, ambas seran la misma interfaz. He aqu una situacin donde se produce este tipo de colisin: //: generics/MultiplelnterfaceVaria nts.java // (CompileTimeError) INo ae compilar) interface Payable<7> {) class Employee implementa Payable<Employee> {) class Hourly extends Employee mplements Payable<Hourly> {) ///:-

Hourly no se compilar debido a que el mecanismo de borrado de tipos reduce Payable<Employee> y Payable<Hourly> a la misma clase. Payable, y el cdigo anterior significara que estaramos tratando de implementar la misma interfaz dos veces. Lo que resulta interesante es que, si eliminamos

15 Genricos 1245 los parmetros genricos en ambos usos de Payable (como hace el compilador durante el borrado de tipos), el cdigo s que puede compilarse.

Esta cuestin 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: (I)Eliminetodoslos ejemplo para que el

genricos de MuliiplelnterfaceVariants.java y modifique el

cdigo pueda compilarse. Proyecciones de tipos y advertencias

Utilizar una proyeccin de tipos o instaneeof con un parmetro de tipo genrico no tiene ningn efecto. El siguiente contenedor almacena los valores internamente como objetos de tipo Object y los proyecta de nuevo sobre T en el momento de extraerlos: //: generics/GenericCast.java

15 Genricos 1246 class FixedSizeStack<T> { private int index 0; private Object [] storaae; public FixedSizeStack(int size) { storage new Object[sizej;

} public void pushtT item) { storaae[ndex**] = Item; ) SSuppressWamings ("unchecked") public T pop) ( tura (T) acorage [--ndex] ; }

t public class GenericCast ( public static final int SIZE = 10; public static void main (String [] args) { FixedSizeStack<String> strings * new FixedSizeStack<Scring>(SIZE); for (String s: " A B C D E F G H I J".split(" ")) strings.push(s); for(int i * 0; i < SIZE; i++) { String s = strings.pop{); System.out.print(s " ")j

15 Genricos 1247 } | / * Output: J I H G F E D C B A

///:-

Sin la anotacin (a SupprcssWarnlngi, 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 limite que es Object de manera predeterminada. por lo que pop( ) est, de hecho, proyectando un objeto de tipo Object sobre Object

May veces en que los genricos no eliminan la necesidad de efectuar la proyeccin, y esto genera una advertencia por parte del compilador, que es inapropiada. Por ejemplo: //: generics/NeedCasting.java import j ava.io.*; import java.til.*; public class NeedCasting { &SuppressWarnings("unchecked") public void f(String(J args) throws Exception { ObjectInputStream in = new ObjectlnputStream( new FilelnputStreamlargs[0])); List<Widget> shap-es * (List<Widget>) ir. readObject () ;

15 Genricos 1248 )

} ///*-

Como veremos en el siguiente captulo, readObject! ) no puede saber lo que est leyendo, por lo que devuelve un objeto que habr que proyectar. Pero cuando desactivamos mediante comentarios la anotacin (a SuppressWarnings y compilamos el programa, se obtiene una advertencia: Note: NeedCasting.java uses unchecked or unsafe operations. Note: Recompile with -XIint: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.til.List<Widget> List<Shape> shapes (List<Widget>)in.readObject() ;

Estamos obligados a realizar la proyeccin y a pesar de ello se nos dice que no podemos hacerlo. Para resolverel

15 Genricos 1249 problema. debemos utilizar una nueva forma de proyeccin de tipos introducida en Java SE5. que es la proyeccin de tipos a tra

vs de una clase genrica: //: generics/ClassCasting.java import java.io.*; import j ava.til.*;oublic class ClassCasting ( SuppressWamings ('unchecked*} public void ftStringl] aras) throws Exceplion { ObjectInputStream in = new ObjectInputStreami new FilelnputStreamlargsl0] ) ) i // No se compilar: // // List<Widgst> iwl = List<Widget>.class.cast(in.readQbject() ) ; List<Widaet> lw2 = List.class.cast(in.readObject{));

) ///?-

15 Genricos 1250 Sin embargo, no podemos efectuar una proyeccin sobre el tipo real (List<\Vidget>). En otras palabras, no podemos escribir:

List<Wldget>.class.cast(in.readOb ject( ) ) e incluso si aadimos otra proyeccin como la siguiente: iLi9t<Widget>)List.class.cast {in.readObject()) seguiremos obteniendo una advertencia.

Ejercicio 32: (I) Verifique que FixedSizeStack en (enericCast.java genera excepciones si intentamos salimos de los

limites fijados. Quiere esto decir que no se necesita el cdigo de comprobacin de limites?

15 Genricos 1251 Ejercicio 33: (3)CorrijaGenericCast.ja\autilizandouncontenedor Sobrecarga ArrayList

El siguiente ejemplo no se podr compilar, aunque parece que se trata de algo bastante razonable: //: generics/UseList.java // (CompileTimeError) {no se compilar) rcport java.util.*; public class UseList<W,T> { void f(List<T> v) {) void flList<w> v) {} i ///=A! sobrecargar el mtodo se produce, debido al mecanismo de borrado de tipos un conflicto debido a la existencia de signaturas 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 listas de argumentos diferenciadas: //: generics/UseList2. java import java.util.*; public class UseList2<W, T> ( void f1{List<T> v) {) void f2(List<W> v) {)

15 Genricos 1252 ) ///:-

Afortunadamente, el compilador 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: //: generics/ComparablePet.java ComparablePet: ( implements Comparable*

public int compareTo(ComparablePet arg) ( return 0; )

) ///:-

Resulta razonable tratar de enfocar mejor el tipo con el que pueda compararse una subclase de ComparablePeL Por ejemplo. un objeto Cal slo debera ser Comparable con otros objetos Cal //: genencs/Hi jackedlnterface.java // (CcmplleTimeError) no se compilarJ

15 Genricos 1253 class Cat extends ComparablePet implements Comparable<Cat>( // Error: Comparable no puede heredarse con // diferentes argumentos: <Cat> y <Pet> public int compareTo(Cat arg) { return 0; ) 1 ///:-

Lamentablemente, esta solucin no funciona. Una vez que se establece el argumento ComparablePet para Comparable, no puede ya compararse ninguna otra clase implementadora con ninguna cosa, salvo con ComparablePet: //: generics/Rest rictedComparablePets.java class Hmster extends ComparablePet implements Comparable<ComparablePet> ( public int compareTo(ComparablePet arg) ( return 0; }

) // O simplemente: class Gecko extends ComparablePet ) public int comoareTo(ComparablePet arg) ( return 0; }

} ///:-

15 Genricos 1254 Hmster demuestra que es posible reimplementar la misma interfaz que podemos encontrar en ComparablePet, siempre y cuando sea exactamente la misma, incluyendo los tipos de parmetro. Sin embargo, esto es lo mismo que limitarse a sustituir los mtodos en la clase base, como puede verse en Gecko. Tipos autolimitados

Existe una sintaxis que provoca bastante confusin y que aparece con bastante asiduidad en el caso de los genricos de Java. He aqu el aspecto: class SelfBoundedcT extends SelfBounded<T ( // ...

Esto es algo comparable a tener dos espejos enfrentados, lo que genera una especia infinita. clase

de reflexin

Seldiounded toma un argumento genrico T. por su parte. T est restringido por un limite, y dicho limite es SelfBoundcd. con T como argumento.

15 Genricos 1255 Este tipo de sintaxis resulta difcil de entender cuando se ve por primera vez, y nos permite enfatizar el hecho de que la palabra clave extends, cuando se utiliza con los lmites de los genricos, es completamente distinta 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 sintaxis, sin el auto- limite.

No podemos heredar directamente de un parmetro genrico. Sin embargo, si que podemos heredar de una que utili

ce dicho parmetro genrico en su propia definicin. En otras palabras, podemos escribir: //: generics/CuriouslyRecurringGeneric.java class GenericType<T> (

15 Genncos 1256 )public class CuriouslyRecurringGeneric extenda GenericTypecCuriousiyRecurringGenerio {) ///:~

Este tipo de estructura podra denominarse genrico curiosamente recurrente siguiendo el titulo del articulo Curiously Recurring Templte Paftern 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 lo 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, asi 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 Objcct. He aqui una clase genrica que nos permite expresar esto: //: generics/BasicHolder.java public class Ba3icHolder<T> ( T element; void set(T arg) ( element = arg; ) T get() { retum element; } void f O l System.out .printlnlelement .getClassd .getSimpleName () ) ;

class Other {)

15 Genncos 1257 )

} ///;-

Se trata de un tipo genrico 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 nicamente realiza operaciones de tipo Objcct con ese campo).

Podemos utilizar Basicllolder cu un genrico curiosamente recurrente: 7: generlcs/CRGWitnBasicHolder.java class Subtype extends BasicHolder<Subtype> (} pubiic class CRGWithBasicHolder ( public static void main(String[] args) ( Subtype stl = new Subtype0 st2 * new Subtype 0 ; stl.set(st2) } Subtype st3 * stl.getd; aLl.fO ;

class Other {)

15 Genncos 1258 ) | / Output: Subtype

///:-

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 BasicHoldcr. sta es la esencia de los genricos curiosamente recurrentes, la clase derivada sustituye o la clase hase en sus parmetros Esto significa que la clase base genrica se convierte en una cierta clase de plantilla para describir la funcionalidad comn de todas sus clases derivadas, pero esta funcionalidad utilizar 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 sct() como el tipo de retomo de geM ) son exactamente Subtype. Autolimitacin

El contenedor Basicllolder puede utilizar cualquier tipo como su parmetro genrico, como puede verse aqu: generics/Unconstrained.java claas BasicOther extends BasicHoIder<Other> {) public class Unconstrained { public static void main(String[] args) { class Other {)

15 Genncos 1259 BasicOther b * new BasicOther0, b2 new BasicOther(}; b.set (new Other())fOther other b.get(); b.f (J; i ) ! * Output: Other

*///:-

La aurolimitacin rcali/a el paso adicional de obligar a que el genrico se utilice como su propio argumento lmite.

Examinemos cmo puede utilizarse y cmo no puede utilizarse la clase resultante: //: generics/SelfBounding.java class SelfBounded<T extends SelfBounded<T>* { T element; 5elBounded<T> set(T arg) ( element = arg; return this;

) T get(J ( return element; ) class Other {)

15 Genncos 1260 }

C setAndGetiC

arg) { set(arg);

return

get(); )

) class D {} // No se puede hacer esto: // class E extends SelfBounded<D> {} // Error de compilacin: el parmetro de tipo D no est dentro de su lmite // Sin embargo, podemos hacer esto, asi que no se puede forzar la sintaxis: class F extends SelfBounded {) public class SelfBounding ( public static void mam (String 11 args) l A a new A () ; set(new A()); a s a.set(new Ai)).get(); a = a.get() j Ce new C(); c = c.setAndGet(new C())/
a.

) ) fhr class Other {)

15 Genncos 1261 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 tuerza a pasar la clase que estemos definiendo como parmetro a la clase base.

Cul es el valor aadido que obtenemos al auiolimitar el parmetro? El parmetro de tipo debe ser el mismo que la clase

que se est definiendo. Como podemos ver en la definicin de la clase B. tambin podemos heredar de una clase

SelfBounded que utilice un parmetro SelfBoundcd. aunque el uso predominante parece ser el que podemos ver en la clase El intento de definir E demuestra que no se puede utilizar un parmetro de tipo que no sea SelfBounded

class Other {)

15 Genncos 1262 Lamentablemente, F se compila sin ninguna advertencia, por lo que la sintaxis de autolimitacin no se puede imponer. Si fuera realmente imprtame, se necesitara una herramienta externa para garantizar que los tipos normales no se utilizan en lugar de los tipos paramctrfzados.

Observe que se puede eliminar la restriccin y todas las clases se seguirn compilando, pero entonces F tambin se compilara: : generics/NotSelfBounded.java public class NotSelfBounded<T> ( T element; NotSelBounded<T> set(7 arg) | element = arg; return this; i I T get) ( return element; )

elas8 A2 extends Not5elfBunded<A2> {) class B2 extends NotSelfBounded<A2> () ciass C2 extends NotSelfBounded<C2> ( C2 setAndGet(C2 arg) | set<arg); return get(); )

l class D2 | \ // Ahora esto es OK: class E2 extends NotSeifBounded<D2> () ///

class Other {)

15 Genncos 1263 Asi que. obviamente, la restriccin de autol imitacin slo sirve para imponer la relacin de herencia. Si se utiliza la autol i- 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 formato.

Tambin resulta posible utilizar la autol imitacin en los mtodos genricos: //: generics/SelfBoundingMethods.java public class SelfHoundmgMethods ( static <T extends SelfBounded<T>* T f(T arg] ( return arg.set<arq).get();

) public static void main(String[] args) { A a - f (new A<) I * i

) ///!-

class Other {)

15 Genncos 1264 Esto evita que el mtodo se aplique a ningn tipo, excepto un argumento autolimitado de la Corma mostrada. Covarianza de argumentos

El valor ile los tipos autolimitados es que producen tipos Je argumentos covariantes, 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 SE5 ya introduce la funcionalidad de tipos Je retorno covariantes: // : generics/CovariantReturr.Types. java ciass Derived extends Base {) nterface OrdinaryGetter ( Base get( )

> interface DerivedGetter extends OrdinaryGe11er { // El tipo de retomo del mtodo sustituido puede variar: Derived get(); i public class Covariant-RetumTypes { void test(DerivedGetter d) ( class Other {)

15 Genncos 1265 Derived d2 * d.getO;

) ) r//i~

El mtodo gel( ) en DerivedGetter sustituye a get( ) en Ordinar> Getter v devuelve un lipo que se deriva del tipo devuelto por OrdinaryGcttcr.gctf ) Aunque se trata de algo perfectamente lgico (un mtodo de un tipo derivado debera poder devolver el tipo ms especifico que el mtodo del tipo base que est sustituyendo), no podia hacerse en las versiones ante* riores de Java.

Un genrico autolimitado produce, de hecho, el tipo exacto derivado como valor de retomo, como podemos ver aqu con el mtodo get( ) / /: generics/GenericsAndRetumTypes java interface GenencGetter<T extends GenericGetter<Tp> { T get(); 1 interface Getter extends GenericGetter<Getter> () public claas GenericsAndRetumTypes { void test(Getter g) ( Getter result = g.getl); GenericGetter gg - g.getO // Tambin el tipo base

) )///t-

class Other {)

15 Genncos 1266 Observe que este cdigo no podra haberse compilado de no haberse incluido los tipos de retorno covariantes en Java SE5. Sin embargo, en el cdigo no genrico, los tipos de argumento no pueden variar con los subtipos: //: generics/OrdinaryArguments.java class OrdinarySetter ( void set(Base base) ( System, out .print ln (OrdinarySetter. set (Base i ") ;

} class DerivedSetter extends OrdinarySetter ( void set(Derived derived) { i System.out.println"DerivedSetter.set(Derived)M);

} public class OrdinaryArguments { public static void main(Stnng [] arg9) ( Base base = new BaseU;

class Other {)

i 15 Genricos 1267 Derived derived = new DerivedO;DerlvedSetter ds = new DerlvedSetter |) ; ds. set (deri ved) ; ds. set (base) // Se compila: sobrecargado, no sustituido | / Output: DenvedSetter.set Derived) OrdinarySetter.set(Basel ///:-

Tanto set(derived) como set (base) son legales, por lo que DerivedSetter.sef() no est sustitu>endo OrdmarySetter.setc). sino que est sobtvcargiuu/o dicho mtodo. Analizando la salida, puede ver que hay dos mtodos en DcrivedScttcr, por lo que la versin de la clase base sigue estando disponible, lo que confirma 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 toma el tipo derivado como argumento, no el tipo base: //: generics/SelfBoundingAndCovariantArguments.java interface SelfBoundSettercT extends SelBoundSetter<T>> ( void set (7 arg) ;

i 15 Genricos 1268 ) xnterface Setter extends SelfBoundSetter<Setter> {) public class SelfBoundingAndCovariantArguments { void testA<Setter si, Setter s2, SelfBoundSetter sbs) ( si.set(s2) // si.setsbs); // Error: // setiSetter? en SelfBoundSetter<Setter> f l n o Duede 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 sido, en la prctica, sustituido.

i 15 Genricos 1269 Sin el mecanismo de aulolimitacin. entra en accin el mecanismo normal de herencia y lo que obtenemos es una sobrecarga. al igual que sucede en el caso no genrico: //: generics/PlainGenericInheritance.iava class GenericSetterT> { 1 / Sin autolimitacin void set IT arg) ( System.out.println("GenericSetter.set{Base)");

) class DerivedGS extends GenencSetter<Base> { void setDerived derived)) System.out.println("DerivedGS.set(Derived)");

i 15 Genricos 1270 ) public class PlainGenencInheritance { public static void main(String(] args) ( Base base = new Base O; Derived derived = new DerivedO; DerivedGS dgs = new DerivedGSI) ; dgs.set(derived); i dgs.set'base); // Se compila: sobrecargado, no sustituido

) / Output: DerivedGS.set {Derived GenericSetter.set(Bas e)

///:-

liste cdigo se asemeja a OrdinaryArguments.java. en dicho ejemplo, DerlvedSetter hereda de OrdinarySeltcr que contiene un conjunto set(Base). Aqu. DerlvedGS hereda de CenericSetter<fiase> que tambin contiene un conjunto sci(Base), creado por el gencrico. Y al igual que en OrdinaryArgumcnts.java. podemos ver analizando la salida que DerivedGS contiene dos versiones sobrecargadas de scf(). Sin el mecanismo de la autolimitacin. lo que se hoce es sobre- cargar los tipos de argumentos. Si se utiliza la autolimitacin, se termina disponiendo de una nica versin de un mtodo, que admite el tipo exacto de argumento.

i 15 Genricos 1271 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 generico. En un mtodo 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 SE5. sigue existiendo la posibilidad de que cdigo escrito con el estilo antiguo corrompa nuestros contenedores. Java SE5 dispone de un conjunto de utilidades en java.utiLCollections para resolver el problema de comprobacin de tipos en esta situacin los mtodos estticos dicckedC'ollectionf ). checkedlJst( ). checked\lap( ). checkedSet( ). chcckedSortedMap( ) y checkcdSortedSeK ) 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 objeto inapropiado, a diferencia de los contenedores anteriores a la introduccin del mecanismo de genricos (contenedores normales y corrientes), que lo que haran seria informamos de que hay un problema en el momento de tratar de extraer el objeto. En csic ultimo caso, sabremos que existe un

i 15 Genricos 1272 problema, pero no podremos determinar quin es el culpable: por el contrario, con los contenedores comprobados, si que podemos averiguar quin es el que ha tratado de insertar el objeto errneo.

Examinemos este problema de "insercin de un gato en una lista de perros" utilizando un contenedor comprobado. Aqu. oldStylc\lethod( ) representa un cierto cdigo heredado, porque admite un obejto List normal, siendo necesaria la anotacin fcrSuppressWarnings(unchccked") para suprimir la advertencia resultante //: genenes/CheckedList.j ava // Using Collection.checkedLis tl). import LypsinCu.peta. * ; import j ava. til. publlc class CheckedList { eSuppressWammg3 ("unchecked") static void oldStyleMethod(List prcbablyDogs) { orobablyDogs.add(new Cat());

} public static void main(StringM args) ( List<Dog> dogsl * new ArrayList<Dog> ( ) ; oldStyleMethod(dogsl); // Acepta sin rechistar un objeto Car List<Dog> dogs2 * Collections.checkedList{ new

i 15 Genricos 1273 ArrayList<Dog>(), Dog.class); try ( oldStyleMethod(dogs2)/ / / Genera una excepcin ) catchlException e) ( System.out.orintln e);

>
II

Los tipos derivados funcionan correctamente:

List<Pet> pets = Collections.checkedList( new ArrayList<Pet>(). Pet.class); pets. add (new DogO); pecs.add(new Cat{)J r

) ) /* Output: ;ava.lar.g.ClassCastException: Attempt to insert class typeinfo.pets.Cat element into collection with element type class typeinfo.pets.Dog

///=-

i 15 Genricos 1274 Cuando ejecutamos el programa, vemos que la insercin de un objeto Cat ni provoca ninguna queja por parte dogs I, pero dogs2 genera inmediatamente una excepcin al tratar de insertar un tipo incorrecto. Tambin podemos ver que resulta perfectamente posible introducir objetos de un tipo derivado dentro de un contenedor comprobado donde se est haciendo la comprobacin segn el tipo base.

Ejercicio 35: (I) Modifique Checked List.jav a para que utilice las clases Coffee definidas en este captulo. Excepciones

Debido al mecanismo de borrado de tipos, la utilizacin 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 excepcin 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 si que pueden usarse en la clusula throws de la declaracin de un mtodo Esto nos permite escribir cdigo genrico que vare segn el tipo de una excepcin comprobada: : f : generics/ThrowGenericException.java import java.til.*;

i 15 Genricos 1275 interface Processor<T,E extends Exception | void process(List<T> resultCollector) throws E;

) class ProcessRunner<T,E extends Exception extends ArrayList<Prcessor<T, E { List<T> processAll() throws E ( List<T> resultCollector = new ArrayLiet<T>{); for(Processor<T,E> processor : this) processor.process(result Collector); return resultCollector;

} class Failurel extends Exception {) class Processorl implements Processor<String,Failurel> { static int count =* 2; public void

i 15 Genricos 1276 process(List<String* resultCollector^ throws Failurel I if(count-- > 1) resultColle ctor.add("Hep 1"); else resultColle ctor.add("Ho! " ) j if (count < 0) throw new Failurel();

1 class Failure^ extends Exception {) class Processor2 implements Processor<Integer,Faiiure2> ( static int count *= 2; public void process{Listdnteger* resultCollector) throws Failure2 ( f(count * 0) resultCollector.add1 47) el se { resultCollector.add{11)?

i 15 Genricos 1277 ) if(count < 0) throw new Failure2{);

) public ciass ThrowGenericException ( public static void main{StringN args' ( ProcessRunner<String,Failurel runner = new ProcessRunner<Stnng, Failurel () ; for (int 1 * 0; i < 3; it--*-) runner.addInew ProcessorlO ) ? try { System, out. println i runner .processAll () ) ; ) catchFailurel e) { i System.out.println(e);

ProcessP.unner<Integer. Failure2> runner2 = new ProcessRunner<Inteaer, Failure2 () ; for (int i * 0; i < 3; i-f-0 runner2.add(new ? rocessor2()); try {

i 15 Genricos 1278 System, out .println (runner2 .processAll ()); ) catch{Failure2 e) { System.out.println(e);

} ///=-

Un objeto Pracessor ejecuta un mtodo process( ) y puede generar una excepcin de tipo E. F.l resultado de process( ) se almacena en el contenedor Lst<T> resultCollector (esto se denomina parmetro de recoleccin). Un objeto ProcessRunner liene un mtodo processAll( ) que ejecuta todo objeto Process que almacene y devuelve el objeto resultCollector.

i 15 Genricos 1279 Si no pudiramos parametrizar las excepciones generadas, no podramos escribir este cdigo en forma genrica, debido a la existencia de las excepciones comprobadas.

Ejercicio 36: (2)Aadaunasegundaexcepcinparametrizada demuestre que lasexcecpiones

la claseProcessory

pueden variar independientemente.

Mixins

El trmino mixin parece haber adquirido distintos significados a lo largo del tiempo, pero el concepto fundamental se refiere 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.

i 15 Genricos 1280 Una de las ventajas de los mixins es que permiten aplicar coherentemente una serie de caractersticas y comportamientos a mltiples clase. Como ventaja aadida, si queremos cambiar algo en una clase mixin. dichos cambios se aplicaran 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 programacin orientada a aspectos (AOP. aspect-orientedpmgramming). y diversos autores sugieren precisamente que se utilice el concepto de aspectos para resolver el problema de los mixins. Mixins en C++

Uno de los argumentos ms slidos en favor de la utilizacin de los mecanismos de herencia mltiples en C++ es. precisamente. el poder utilizar mixins. Sin embargo, un enfoque ms interesante y ms elegante a la hora de tratar los mixins es el que se basa en la utilizacin de tipos parametrizados: segn este enfoque, un mixto es una clase que hereda de su parmetro de tipo. En C+^\ podemos crear mixins fcilmente debido a que C-*-r recuerda el tipo de sus parmetros de plantilla.

He aqu un ejemplo de C++ con dos tipos de mixin: uno que permite aadir la propiedad de disponer de una marca temporal y otro que aade un nmero de sene para cada instancia de objeto: / / : generi es/Mixins. cpp #include <string> include <ctime> #include <iostream> usina namespace std; templateeclass 7> claas TimeStamped : pubiio 7 { long timeStamp; publlC: TimeStampedl) ( timeStamp * timetO),* ) long getStamp( {

i 15 Genricos 1281 return timeStamp; )

); template<class T> class SerialNumbered : public T ( long serialNumber; static long counter; public: SerialNumbered0 ( serialNumber - counter^; } long getSerialNumber() { return serialNumber; } ) // Definir e inicializar el almacenamiento esttico: template<class T> long SerialNumbered<T>::counter = 1; class Basic { string value; public: void set(sti'ing val) ( valu = vai; } string get() ( return value; )

); int main ( ) ( TimeStamped<SerialNumbered<Ba sic> > mixinl, mixin2,* mixinl.set<"test string 1"),* mixin2.set("test string 2")? cout mixinl.get() " " mixinl .getStampl)

i 15 Genricos 1282 * " mixinl.getSerialNumber(> . endl; cout mixir.2 .get ( ) << M mixin2. getStamp 0 << " mixln2.getSerialNumberU <e endl; ) / Output: (Sample) test string 1 1129840250 1 test string 2 112S840250 2 *///:-

En main( ), el tipo resultante de mixinl y mi\in2 tiene todos los mtodos de los tipos que se han usado en la mezcla. Podemos considerar un mixto como una especie de funcin que establece una correspondencia entre clases existentes y una serie de nuevas subclases. Observe lo fcil que resulta crear un irrisn utilizando esta tcnica; bsicamente, nos limitamos a decir lo que queremos y el compilador se encarga del resto: TimeStamped<SenalNumbered<Basic> > raixinl, mixin2;

Lamentablemente, los genricos de Java no permiten esto Ll mecanismo de borrado de tipos haee que se pierda la informacin 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 forma siguiente:

i 15 Genricos 1283 //i generics/Mixins.java import java.ut il.*; interface TimeStamped ( long getStamp O j } class TimeStampedlmp implements TimeStamped { private final long timeStamp; public TimeStampedlmpl) { timestamp = new Date(}.getTime();

} public lona aetStampO ( return timeStamp; }

) interface SerialNumbered ( long getSerialNumber () ; ) class SeriaINumberedImp implements SerialNumbered { prvate static long counter * 1; private final long serialNumber = counter-^*-; public long getSerialNumber() { return serialNumber; )

i 15 Genricos 1284 interface Basic ( public void set{String val); public String get!);

) class Basiclmp implements Basic { private String value; public void set(String val) ( value = val; ) public String get() ( return value; }

} class Mixin extends Basiclmp implements TimeStamped, SerialNumbered ( private TimeStamped timestamp = new TimeStampedlmp(); private SerialNumbered serialNumber = new SeriaINumberedImpO; public long getStampO { return timestamp.getStamp(); | public long getSerialNumber() { return serialNumber.getSerialNumber();

i 15 Genricos 1285 } J public class Mixins { public static void main(String[] args) { Mixin mixinl = new MixinO, mixin2 = new Mixin0; mixinl.set("test string 1); mixin2.set("test string 2M); System.out.println(mixinl.get I) M " + mixinl.getStamp{) + M * mixinl.getSerialNumber()); System.out.println(mixin2.get() + " " -+ mixin2.getStampO " ' + mixin2.getSerialNumber ()) ;

) } A* Output: (Sample) test se ring 1 113243715135 9 1 test string 2 113243715135 9 2 ///:-

La clase Mixin est utilizando, bsicamente, el mecanismo de delegacin, por lo que cada uno de los tipos mezclados requiere un campo en Mixln, y es necesario escribir todos los mtodos que se precisan en Mixln para redirigir las llamadas al objeto apropiado. Este ejemplo utiliza clases triviales, pero en un mixin ms complejo el cdigo

i 15 Genricos 1286 puede llegar a crecer de tamao de forma bastante rpida.31

Ejercicio 37: (2) Aada una nueva clase mixin Coloree! a Mixins.java. mzclela en Mixin 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? 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.

Observe que alguno* entornos de programacin, como Eclipse e lntell Idea, generan automticamente el cdigo de delegacin.
31

' Los patrones se tratan en Thinking ln Paiterm iwith Java), que puede encontrar en m'Wh1.Aflai/I lew. nei Consulte tambin Desigti Pattems. de Hnch Gamma tt al I Addison-Wesky, 1995).

i 15 Genricos 1287 El patrn Decorador utiliza objetos en distintos niveles para aadir responsabilidades, de forma dinmica y transparente, a distintos objetos individuales El Decorador especifica que todos los objetos que envuelven al objeto inicial de partida tienen la misma interfaz bsica. En cierto modo, lo que hacemos es definir que un cierto objeto es decorablc y luego agregarle 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, independientemente 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 implcmentan utilizando los mecanismos de composicin junto con estructuras formales (la jerarqua de objetos decorables decoradores), mientras que los mixins estn basados en el mecanismo de herencia. Por tanto, podramos decir que los mixins basados en tipos parametnzados son lina 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: (/: generics/decoracor /Decoratian.java package generics.decor3tor ; impon java.til.*; class Easic ( private String valu* public void set(String val ( valu = val* } public Strina get() { retum valu; )

i 15 Genricos 1288 i class Decorator extends Basic ( protected Basic basic; public Decorator(Basic basic) { this.basic = basic; ) public void set(String val) ( basic.set(val); ) Dublic String get() ( return basic.get); }

) class TimeStamped excends Decorator { private final long timeStamp; public TimeStamped(Basic basic) ( superIbasc)i timeStamp a new Date 11 . getTime O ; i 1 public long getStamp{)( return timeStamp/ )

class SerialNumbered extends Decorator { privare static lona counter = 1; privare final long serialNumber counter***? public SerialNumberedBasic basic) { super(basicl ) public ionq qetSeriaINuraberf) ( return serialNumber, J

i 15 Genricos 1289 ) public class Decoration ( public static void main (String f] argBJ | TimeStamped t * new TimeStampednew BasicM) TimeStamped t2 * new TimeStamped! new SerialNumberedlnew Basic())); //i t2 .getSerialNumber{> ,* // No disponible SerialNumbered s = new SerialNumbered<new Basic!))j SerialNumbered s2 = new SerialNumbered( new TimeStamped(new Basic0 )) ? //i s 2 . getStampO // No disponible

) ///:-

l.a 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 objeto haya sido

i 15 Genricos 1290 decorado. En otras palabras, aunque es posible aadir ms de un nivel, el tipo real ser el ltimo de esos niveles, de modo que solo sern visibles los mtodos de ese nivel final: por el contrano. el tipo de un mixin es todas 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 niveles de decoracin (el nivel final) mientras que la tcnica basada en mixin resulta bastante ms natural Por tanto, el patrn de diseo Decorador slo constituye una solucin limitada para el problema que los mixins abordan.

Ejercicio 38: (4) Cree un sistema Decorador simple comenzando con una clase que represente 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 posible utilizar uu pnj.xy dinmico para un mecanismo que permita modelar los mixins de forma ms precisa que lo que se puede conseguir utilizando el patrn de diseo Decorador (consulte el Capitulo 14. Informacin de tipos. para ver una explicacin acerca de cmo funcionan los proxies dinmicos en Java). Con un prvxy dinmico, el tipo dinmico de la clase resultante es igual a los tipos combinados que hayamos mezclado.

i 15 Genricos 1291 Debido a las restricciones de los p r o x i e s dinmicos, cada una de las clases que intervengan en la mezcla deber ser la imple- mentacin de una interfaz: //: generics/DynamicProxyMixin.java import ]ava.iang.reflect. * s import java.til.*; import net.mindview.til; import static net.mindview.util.Tupie.*; class MixinProxy implements InvocationHandler { Map<String,Object> delegatesByMethod; public MixinProxy (TwoTuple<Object, Clasa?. . . pairs) ( delegatesByMethod = new HashMap<String#Object>(); for(TwoTuple<Object,Class<?>> pair : pairs) { for(Method method : pair.second.getMethods()i { String methodName = method. getName ' I ; // 2-a primera interfaz del mapa // implemento el intodc. if 'delegatesRyMethod.cor.tainsK ey :methcdName delegaresByMethod.putimethodK ame. pair.first)

i 15 Genricos 1292 l )

public Object invoke{Object proxy, Method method, ObjectJ argsi throws Throwabie | String merhodName = method.getName(J; Object delegate delegatesByMethod .get (methodKameI , return method.invoke(delegate, args/;

} sSuppressWarmngs < "unchecked" public static Object newTnstance(TwoTuple... pairs) ( Class {J interfaces new Class I pairs. length] , for (int i * 0; 1 c paixs.length; i++) { interfacesli] = (Class)pairs[i].second; \ ClassLoader cl - pairs fO).first.getClass i: .getClassLoader i1; return Proxy.newProxyInstance{ I cl, interfaces, new MixinProxy(pairs));

i 15 Genricos 1293 I public class DynamicProxyMixin ( public static void main(String[j args) ( Object mixin MixinProxy.newln3tancet tuple (new BasicIrnpM, Basic.class) , tupis (new TimeStampedlmp (J , TimeStainped.class) , tuple mew SeriaINumberedImp().SerialNum bered.class)); Basic b = (Basic)mixin; TimeStamped t * (TimeStamped)mixin; 6erialNumberecl s - (SerialNumbered) mixin j
b.

set / "Hello*11) ;

System.out .println ib.get if System. out. print In (t. getStamp (Mr System.out.println(s.getSerialN umber()J;

) | / Output:(Sample) Hello 1132519137015 1

i 15 Genricos 1294 ///:-

Puesto que el nico que incluye iodos 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 apropiado antes de que podamos invocar ningn mtodo del mismo. Sin embargo, esta solucin est significativamente ms prxima que las anteriores a lo que es el concepto de un verdadero mixin.

Es mucho el trabajo que se ha realizado para poder soportar mixins en Java, incluyendo la creacin de una extensin del lenguaje (el lenguaje Jam) que se ha definido especficamente con el objeto de soportar los mixins.

Ejercicio 39: (I) Aada una nueva clase mixin Colored a DynamicPro\> Mixin.java. mzclela en mixin, y demuestre

que funciona.

i 15 Genricos 1295

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 formas 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 situaciones; en otras palabras, cdigo ms genrico.

Los genricos de Java parecen dar un paso adicional en esta direccin. Cuando escribimos o utilizamos genricos que simplemente se emplean para almacenar objetos, el cdigo funciona con cualquier tipo de datos (salvo con las primitivas, aunque 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 manipulaciones con los tipos genricos (distintas de la invocacin de mtodos de Object). porque el mecanismo de borrado de tipos requiere que especifiquemos los limites que pueden usarse para los tipos genricos, con el fin de poder invocar de manera

i 15 Genricos 1296 segura mtodos especficos para los objetos genricos dentro del codigo. Esta 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 terminemos generando en lugar de genricos una clase o interfaz normales, debido 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 Intentes o tipos estructurales. Un trmino ms coloquial es el de tipos pato {duck typrng): este trmino ha llegado a ser muy popular debido a que no acarrea el bagaje que los otros trminos si acarrean. El termino proviene de la frase **si camina como un pato y se comporta como un pato, podemos tratarlo como un pato.

El cdigo genrico, normalmente, slo invoca unos cuantos mtodos de un tipo genrico, y los lenguajes con tipos latentes relajan la restriccin (teniendo que producir codigo ms genrico) requiriendo que tan slo un subconjunto de los mtodos sea implementado. na una clase o interfaz concretas. Debido a esto, los tipos latentes permiten saltar las fronteras de las jerarquas de clase, invocando mtodos que no forman parte de una interfaz comn. En la practica, un fragmento de cdigo podra decir No me importa de qu tipo eres, siempre y cuando dispongas de los mtodos speak< ) y sit() Al no requerir un tipo especifico, el codigo 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 cdigo son las

i 15 Genricos 1297 herramientas fundamentales de la programacin informtica 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 permiten 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 Python (que puede descargarse gratuitamente de ww'w.Python.org) y C-H-.32 Python es un lenguaje con tipos dinmicos (prcticamente todas las comprobaciones de tipos se realizan en tiempo de ejecucin) y C-H- 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 class Dog;

Si tomamos la descripcin anterior y la expresamos en Python. tendra el aspecto siguiente:

# : generi.cs/DogsAndRob ot8. py

32

Lo* lenguajes Ruby y Smolllalk tumbicu -suportan li> tipos latcnies.

i 15 Genricos 1298 class Robot :

a = D o g ( ) b * = R o b o t ( ) p e r f o r m l a ) p e r f o r m (

def perfarm(anythi ng): anything.speak {) anything.sit O

i 15 Genricos 1299 b )

Python utiliza el sangrado para determinar el mbito (asi que no hacen falta smbolos de llaves) y un smbolo de dos puntos para dar comienzo a un nuevo mbito. Un smbolo *# indica un comentario que se extiende hasta el final de la linea, al igual que *// en Java. Los mtodos de una clase especifican explcitamente, como primer argumento, el equivalente de la referencia Ihis. 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 normales (es decir, funciones que no son miembro de una clase), como pone de manifiesto la funcin perform()

En perform(anything), observe que no se indica ningn tipo para for anything. y que anything es simplemente jn identificados Esa variable debe ser capaz de realizar las operaciones que perform( ) le pida, por lo que hay una interfaz implcita Pero nunca hace falta escribir explcitamente dicha interfaz, ya que est latente. pcrform() no se preocupa de cul sea el tipo de su argumento, asi que podemos pasarle a esa funcin cualquier objeto siempre y cuando ste soporte los mtodos spt*ak() y sit( ). Si pasamos a perform( ) un objeto que no soporte estas operaciones, obtendremos una excepcin en tiempo de ejecucin.

Podemos producir el mismo efecto en C-H-: //: generies/DogsAndRobots.cpp class Dog

i 15 Genricos 1300 { public: void speak ( ) {} void sit t) {) void repr oduc e() (]

)t

class Robot ( public: void spea k O {) void sit( ) {) void oilC hang eO {

}; templatecclass T> void perform(T anything) { anything.speak(); anything.sit(); i int mainl) { Dog d; Robo t r; perfor m(d);

i 15 Genricos 1301 perfor m(r); i ///=-

Tanto en Pylhurt como en C++. Dog y Robot no tienen nada en comn33 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 especifico de su argumento y el mecanismo de tipos latentes le permite aceptar ambos tipos de objeto.

C+-r verifica que pueda enviar dichos mensajes. El compilador proporcionar un mensaje de error si tratamos de pasar el tipo incorrecto (estos mensajes de error han sido, histricamente, bastante amenazantes y muy extensos, y son la razn principal de que las plantillas 0-+ tengan una reputacin tan mala). Aunque ambos lo hacen en instantes distintos tCt-t- en tiempo de compilacin y Python en tiempo de ejecucin), los dos lenguajes garantizan que no se puedan utilizar incorrectamente los tipos. > esa es la razn por la que decimos que los dos lenguajes son fuertemente Upados / Los tipos latentes no afectan al tipado fuerte.

Como el mecanismo de genricos se aadi a Java en una etapa tardia, no hubo la oportunidad de implementar un mecanismo de tipos latentes, asi que Java no tiene soporte para esta funcionalidad Como resultado, puede parecer al principio que el mecanismo de genricos de Java es menos genrico" que el de otros lenguajes que si La impletncntBCtn de los mecanismo de Java utilizando el borrado de tipos denomina, en ocasiones, mecanismo dr tipvi genricos Je tagarnia Jan
33

i 15 Genricos 1302 soporten los tipos latentes.* 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 limite: / / : generies/Perforas.3ava public interface Performs ( void speak f1; void sit(1; ) //ft//: generics/DogsAndRobots.java // No hay tipos latentes en Java import typeinfo.pets. ; import static net.mindview.util.Print. *; class PerforminaDog extends Dog implements Performs ( public void speak() ( print("Woof!"); | public void sit(I { print i"Sitting")j } public void reoroduce(I {}

) class Robot implements Performs ( public void speaki ) { print("Click I")j } public void sit() { print ("ClanJc! *}; } public void oilChanoell {)

i 15 Genricos 1303 } class Communicate { public static <T extends Performs> void perform(T performer) { performer.speak <); performer.alt 0;

} public class DogsAndRobots ( public static void main(String[] args) (

PerformingDog d = new PerformingDog {)

i 15 Genricos 1304 ;Robot r = new Robot <> Coramunicate.perform.di; Ccmmunicate.Derform(rl; /* OUtpUt: Woof t Sitting Click! Clank

*///:-

Sin embargo, observe que pcrform( ) no necesita utilizar genricos para poder funcionar Podemos simplemente especificar que acepte un objeto Performs: //: generics/SimpleDogsAndRobots.java // Eliminacin del genrico, el cdigo sigue funcionando. class CommunicateSimply ( static void performPerforms performer) { performer.speak(); performer .sit () ,* i

i public clasa SimpleDogsAndRobots (

i 15 Genricos 1305 public static void mam (String i] arqs) ( CommunicateSimply .perform (new Perf ormingDog ()) ; ) CommunicateSimply.perform(new Robot(Mr

) / * Output: Woof t Sittmg Click! Clank!

///:-

En este caso, los genricos eran simplemente innecesarios, ya que las clases estaban ya obligadas a mplementar la intcrfnz Performs. Compensacin de la carencia de tipos latentes

Aunque Java no soporta el mecanismo de tipos latentes, resulta que esto no significa que el codigo gcnenco con limites no pueda aplicarse a travs de diferentes jerarquas de tipos. Un otras palabras: sigue siendo posible crear cdigo verdaderamente genrico, aunque hace falta algo de esfuerzo adicional. Reflexin

i 15 Genricos 1306 Una de las tcnicas que podemos utilizar es el mecanismo de reflexin. He aqu el mtodo perform( ) que utiliza tipos latentes: / / : oenerics/LatentReflection.java // Utilizacin del mecanismo de reflexin para generar tipos latentes. import java.lang.reflect./ import static net .mmdview.util. Print / // No implementa Performs: class Mime ( public void walkAgamstTheWind () {) public void sit(J ( print (Pretending to sit"),* ] public void pushlnvisibleWalls0 {} public String toStringO ( return "Mime"; )

} // No implements Performs: class SmartDog ( public void speak(' { print("Woof!) ; ) public void sit() ( print("Sitting*) ; } public void reproduce O ()

} class CommunicateReflectively ( public static void perform(Object speaker) ( Class<?> spkr = speaker.getClass0 * try ( try ( Method speak -

i 15 Genricos 1307 spkr.getMethod("speak" ) speak.invoke(speaker); ) catch(NoSuchMethodException e) ( print(soeaker " cannot speak">;

) try ( Method sit * spkr.getMethod("sit"); s it.invoke(speaker); } catch(NoSuchMethodException e) { print (soeaker + " cannot sit"),*

) ) catch(Exception e) { throw new RuntimeException(speaker.toString0, e);

i 15 Genricos 1308 )

) public class LatentReflection { public static void main(String[J args) { CommunicateReflectively.perform(new SmartDog()); CommunicateReflectively.perform (new Robot( ) ) ; CommunicateReflectively.perform(new MimeO);

} ) /* Output: Woof I Sitt ing Clic k! Clank * Mime cannot speak Pretending to sit ///:-

Aqu, las clases son completamente disjuntas y no tenemos clases base (distintas de Object) ni interfaces en

i 15 Genricos 1309 comn. Gracias al mecanismo de rellexin. CommunicateRelectively.perormt ) puede establecer dinmicamente si los mtodos deseados estn disponibles e invocarlos. Incluso es capaz de gestionar el hecho de que Mime slo tiene uno de los mtodos necesarios, cumpliendo parcialmente con su objetivo. Aplicacin de un mtodo a una secuencia

F.l mecanismo de reflexin proporciona algunas posibilidades interesantes, pero relegan todas las comprobaciones de tipos a tiempo de ejecucin, por lo que resulta indeseable en muchas situaciones. Normalmente, siempre es preferible conseguir que las comprobaciones de tipos se realicen en tiempo de compilacin. Pero, es posible tener una comprobacin de tipos en tiempo de compilacin y tipos latentes?

Examinemos un ejemplo donde se analiza este problema. Suponga que desea crear un mtodo apply( ) que pueda aplicar cualquier mtodo a lodos los objetos de una secuencia. sta es una situacin en la que las interlaces parecen no encajar. Lo que queremos es aplicar cualquier mtodo a una coleccin de objetos, y las interfaces introducen demasiadas restricciones como para poder describir el concepto de cualquier mtodo. Cmo podemos hacer esto en Java?

Inieialmentc. podemos resolver el problema mediante el mecanismo de reflexin, que resulta ser bastante elegante gracias a los varargs de Java SE5: //: generics/Apply.java // {main: ApplyTest) import java.lang.reflect.*; import java.util.,* import static net.mindview.util.Print.; public class Apply {

i 15 Genricos 1310 public static <T. S extends Iterable<? extends ? void apply(S seq, Method f, Object... args) { try ( for(T t: seq) f . invoke (t, args > ; ) catch(Exception e) ( // Los fallos son errores del programador throw new RuntimeException(e) ;

) class Shape { public void rotated ( print {this" rotate"); } public void resize(int newSize) { print(this " resize newSize); +

i 15 Genricos 1311 ) class Square extends Shape () class FilledLiet<T> extends ArrayList<T> ( public FilledList(Class<? extends T> type, int size) ( try ( for{int i * 0; i < size; 1++) // Presupone un constructor predeterminado: add(type.newlnstance 01/ | catch(Exception e) { throw new RuntimeException(e);

) class ApplyTest ) public static void main(String[] args) throws Exception ( List<Shape> shapes = new ArrayList<Shape> () ,* for (int i 0; i < 10; i++) shapes.add Inew Shape )); Apply.apply (shapes, Shape.class.getMethoa<"rotaten 0I) .* Apply.apply(shapes, Shape.class.getMethod("resize*, int.class!, 5); List<Square> squares = new ArrayList<Square>(); for (int i - 0; i < 10/ +-0 squares.add (new SquareO): Apply.apply(squares, Shape.class.aetMethod(Mrotate"));

i 15 Genricos 1312 Apply.apply\Bquares, Shape. class. getMethod "re5iz',4 mt - class I, 5);

Apply.apply(new Fi lledList<Shape>(Shape.clas, 1C,

Shape.class.gecMetkod("rotateM > } ; Apply.apply1new FilledList<Shape>(Square.class, 101,

Shape.class.getMethodl"ratate'M > ; S iTnpleQueue< Shape > shapeQ = new SimpleQueue<Shape> i) ; for(int i 0; 1 < 5; i-t-4) | shapeQ.add(new Shape())j shapeQ. add (new Square (} ) /

) Applv.apply(shapeQ, Shape.class.getMethod("rotate \ J;

i 15 Genricos 1313 ) ) / * (Execute to see output) ///:-

En Applv. tenemos suerte, porque se da la circunstancia de que Java incorpora una interfaz Iterable que es utilizada por la biblioteca de contenedores de Java. Debido a esto, el mtodo apply() puede aceptar cualquier cosa que implemento la interfaz Iterable. lo que incluye todas las clases CoUection. como List. Pero tambin puede aceptar cualquier otra cosa, siempre y cuando hagamos esa cosa de tipo Iterable: por ejemplo, la clase SimpleQueiie definida a continuacin y que se utiliza en el ejemplo anterior en maln( ).

//: generics/SimpleQueue.java // Oh tipo diferente de contenedor que es Iterable import java.til.*; public class SimpleQueue<T> implements Iterable<T> ( prvate LinkedListcT storage - new LinkedLlstcT ( ) ; public void addT t) ( storage.offer(ti ; ) public T get) ( return storage .poli i ) public Iterator<7> iteratorU ( return storage. iterator () ;

) ) rm~

i 15 Genricos 1314 E.n Applv.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 limites y comodines para poder utilizar Apply y FilledList en todas las situaciones deseadas. Pruebe a experimentar quitando los limites comodines y descubrir que Appl) y FilledList no funcionan en algunas situaciones.

FilledList representa un cierto dilema. Para poder utilizar un tipo, ste debe disponer de un constructor predeterminado (sin argumentos). Java no tiene ninguna tonna de descubrir eso en tiempo de compilacin, asi que el problema se pasa a tiempo de ejecucin. Una sugerencia muy comn para poder garantizar la comprobacin de tipos en tiempo de ejecucin consiste en definir una interfaz factora que disponga de un mtodo que genere objetos, entonces FilledList aceptara dicha interfaz en lugar de la factora normal correspondiente al tipo especificado. El problema con esto es que todas las clases que se utilicen en FilledList deben entonces implementar 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. Posteriormente veremos una posible 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.

i 15 Genricos 1315 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 Pmgramming Langt/age, de Gilad Bracha9. En dicho articulo, el autor seala que:

15 Genricos 1316 v 1 case la cita al final de este capitulo."Se irata 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 Jav a resulta bastante elegante, debemos observar que el uso del mecanismo de reflexin (aunque se ha mejorado significativamente en las ltimas versiones de Juva puede hacer que el programa sea ms lento que la implementaciones no basadas en la reflexin, ya que hay demasiadas tareas que llevar a cabo en tiempo de ejecucin. Esto no debera impedir que empleramos sta solucin, al menos como primera solucin al problema (salvo que queramos caer en el emir 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.petv Modifique Apply.java para invocar al

mtodo speakt ) para una coleccin heterognea de objetos Pet. Qu pasa cuando no disponemos de la interfaz correcta?

class TitleTransfer extends Contract {)

15 Genricos 1317 El e jemplo anterior aprovechaba el hecho de que la interfaz Iterable ya est disponible, siendo esa interfaz precisamente lo que necesitbamos. Pero que sucede en el caso general, cuando no existe todava una interfaz que se ajuste a nuestras necesidades?

Por ejemplo, vamos a generalizar la idea de FilIedList y a crear un mtodo fill( ) parametri/ado que admita una secuencia y la rellene utilizando un objeto Generafor. Al tratar de escribir esto en Java nos encontramos con un problema, porque no existe ninguna interfaz adecuada Addahlc (una interfaz que permita aadir objetos), mientras que en el caso anterior si que tenamos ma interfaz Iterable. Por tanto, en lugar de decir cualquier cosa para la cual podamos invocar el mtodo add( V. nos vemos forzados a decir un subtipo de Collection". El cdigo resultante no es particularmente genrico, ya que debe restringirse para funcionar con implementaciones de (ollectinn. Si tratamos de utilizar una clase que no implemento Collection. el cdigo genrico no funcionar Me aqu el aspecto que tendra este ejemplo: //t generics/Fill.java // Generalizacin de la idea de FilledList // {main: FillTest} import j a va.ufcil.; / No funciona con "cualquier cosa que tenga un mtodo addO". No hay // una interfaz "Addahle", por lo que nos vemos limitados a // utilizar un contenedor Collection. le podemos generalizar > J empleando genricos en este ca30. public class Fill ( public staCric <T> void fill<Collectlon<T> collection, Class<? extends T> classToken, int Bizei ( foriint i 0 i < eize; !) / / Presupone un constructor predeterminado: try { collection.3dd{classToken.newlnstanee OI; } catch(Exception e) { rhrow new RuntimeExceptionle)j class TitleTransfer extends Contract {)

15 Genricos 1318 ii }

class Contract { private static long counter * 0; prvate final long id = counter public String toStringO { Y retum getClass I J .getName IJ 4 -# id;

) class FillTest ( public static void main(String[] args) ( List<Contracta contracts * new ArrayList:<Contract> (> Fill.fill(contracts. Contraer, .class. 3) ; Fill .filKcontracts, TitleTransfer.class. 2 ) ; for(Contract c: contracts) System.out.println(c)? SimpleQueue<Contract> contractQueue = new SimpleQueue<Contract>(); // No funciona, fillO no es lo suficientemente genrico: // Fill.fill(contractQueue, Contract.class, 3);

class TitleTransfer extends Contract {)

15 Genricos 1319 ) ) / Output: Contract 0 Contract 1 Contract 2 TitleTransfer 3 Ti tleTransfer 4 *///:-

Es en estas situaciones donde resulla ventajoso disponer de un mecanismo parametmado con tipos latentes, porque de esa forma no estaremos a merced de las decisiones 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 (asi que el cdigo ser verdaderamente genrico"). En el caso anterior, 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 Collection. y SimpleQueue no funcionar, an cuando disponga de un mtodo add() Dado que ahora est restringido a trabajar con Collection. 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 escribir cdigo que pueda aplicarse traspasando las fronteras entre las clases (es decir, cdigo genrico). Hay alguna forma de salvar esta limitacin?

class TitleTransfer extends Contract {)

15 Genricos 1320 Qu es lo que no permitira hacer los tipos latentes? Los tipos latentes implicaran que podramos escribir codigo que dijera. No me importa qu tipo estoy usando, siempre y cuando ese tipo disponga de estos mtodos. En la prctica, los tipos latentes crean una interfaz implcita que contiene los mtodos deseados. Por tanto, si escribimos la interfaz necesaria a mano (ya que Java no lo hace por nosotros), eso deheria resolver el problema.

Escribir cdigo para obtener una interfaz que necesitamos a partir de otra interfaz de la que disponemos constituye un ejemplo del patrn de diseo Adaptador Podemos utilizar 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 Coffet anteriormente definida, ilustra las diferentes formas de escribir adaptadores: //: generics/Fill2.java // Utilizacin de adaptadores para simular tipos latentes. f (main: Fill2Test) import generics.coffee. * \ import j ava. Util. ,* import net.mindview.til.; import static net.mindview.til.Print. *; interface Addable<T> { void add(T t); } public class F112 ( // Versin con indicador de clase: public static <T> void fiil(Addable<T> addable.

Class<? extends T> classToken, int size) class TitleTransfer extends Contract {)

15 Genricos 1321 (for(inc i = 0; i < size; i++) try { addable.add(classToken.newInstance()); ) catch(Exception e) { throw new RuntimeException<e);

) // Versin con generador: public static <T> void fill(Addable<T> addable, Generator<T> generator, int size) ( for(int i * 0; i < size; i + + ) addable.add(generator.next()J; Para adaptar un tipo base, es necesario utilizar composicin. // Definir Addable como contenedor Collection usando composicin: class AddableCollectionAdapter<T> implements Addable<T> ( prvate Collection<T> c; public AddableCollectionAdapter(Collection<T> c) { this.c = c;

class TitleTransfer extends Contract {)

15 Genricos 1322 ) ) public void add(T item) ( c.add(item); }

f [Jn mtodo auxiliar para capturar el tipo automticamente: class Adapter { public static <T> Addable<T> coIlectionAdapter (Collection<T> c) ( retura new AddableCollectionAdapter<T>(c); i

) // Para adaptar un tipo especifico, podemos usar la herencia. // Hacer Addable un contenedor SimpleQueue utilizando la herencia: class AddableSimpleQueue<T> extends SimpleQueue<T> implements Addable<T> ( public void add(T itero} { super.add(item) ; }

) class Fill2Test ( public static void main(StringU args) ( // Adaptar una coleccin: List<Coffee> carrier = new ArrayList<Coffee>< : ; F112.fill ( new AddableCollectionAdapter<Coffee>(carrier), Coffee.class, 3); / / E l mtodo auxiliar captura el tipo: class TitleTransfer extends Contract {)

15 Genricos 1323 F12 . f ill (Adapter. coIlectionAdapter (carrier) , Latte.class, 2J; for(Coffee c: carrier' print(c); print ("-------------") ; // Utilizar una clase adaptada: AddableSimpleQueue<Coffee> coffeeQueue = new AddableSimpleQueue<Coffee>(); Fill2.fill(coffeeQueue, Mocha.class, 4);

Fill2. fill (coffeeQueue, Latte.class, 1) ,- for(Coffee c: coffeeQueue) print(c) ;i ) / O u t p u t : C o f f e e 0 C o f f e e class TitleTransfer extends Contract {)

15 Genricos 1324 1 C o f f e e 2 L a t t e 1 L a t t e 4 M o c h a 5 M o c h a 6 M o c h a 7 M o class TitleTransfer extends Contract {)

15 Genricos 1325 c h a 8 L a t t e 9 * / / / : -

FII2 no requiere un objeto Collection a diferencia de FD1. En lugar de ello, slo necesita algo que implemente Addahle. y Addahle ha sido escrita precisamente para FUI. este ejemplo es una manifestacin del tipo latente que queramos que el compilador 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 Generator no presenta problemas de seguridad de tipos en tiempo de compilacin: el compilador garantiza que lo que pasemos sea un objeto Generator valido, asi que no puede generarse ninguna excepcin.

class TitleTransfer extends Contract {)

15 Genricos 1326 El pnmer adaptador. AddahleCoIlectionAdapter, funciona con el tipo base Collcction, lo que significa que puede utilizarse cualquier implementacin de Collcction. F.sta versin simplemente almacena la referencia a Collection y la utiliza para implementar add( ).

Si disponemos de un tipo especifico 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 Fill2Tcst.man( ). podemos ver cmo funcionan los diversos tipos de adaptadores. En primer lugar, se adapta un tipo Collection con AddablcCollcctonAdaptcr. Una segunda versin de esto utiliza el mtodo auxiliar genrico, y podemos ver cmo el mtodo genrico captura el tipo, asi que no es necesario escribirlo explcitamente: se trata de un iruco bastante conveniente, que nos permite escribir cdigo ms elegante.

A continuacin, se utiliza la clase AddableSimpleQueue pre-adaptada Observe que en ambos casos los adaptadores permiten utilizar con Fill2.fill( ) las clases que previamente no implementaba Addahle.

class TitleTransfer extends Contract {)

15 Genricos 1327 La utilizacin de adaptadores de esta forma parece compensar la falta de un mecanismo de tipos latentes, por lo que podra pensarse que podemos escribir cdigo genuinameme genrico. Sin embargo, se trata de un paso de programacin adicional y es ncccsano 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 mecanismos de tipos latentes permiten, al eliminar este paso adicional, aplicar el cdigo genrico ms fcilmente, y ah radica precisamente su valor.

Ejercicio 41: (l) Modifique Fill2.ja\a para utilizar las clases typeinfo.pets en lugar de las clases Coffee. Utilizando los objetos de funcin como estrategias

Este ejemplo final nos permitir 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 cualquier tipo que pueda sumarse), pero termin evolucionando hacia la realizacin de operaciones generales, usando un estilo de pn)gramacion funcional.

Si nos fijamos exclusivamente en el proceso de sumar objetos, podemos ver que este es un caso en el que tenemos operaciones comunes entre clases, pero dichas operaciones no estn representadas en ninguna clase base que podamos especificar. en ocasiones se puede incluso utilizar un operador *+* y otras veces puede haber algn tipo de mtodo suma". sta es. generalmente la situacin con la que nos encontramos cuando tratamos class TitleTransfer extends Contract {)

15 Genricos 1328 de escribir cdigo genrico, porque lo que queremos es que el cdigo se pueda aplicar a mltiples clases: especialmente, como en este caso, mltiples clases que ya existan y que no tengamos posibilidad de corregir Incluso si restringiramos este ejemplo a las subclases de Number, dicha superclase no incluye nada acerca de la sumabilidad

class TitleTransfer extends Contract {)

15 Genricos 1329 .a solucin consiste en utilizar el pairon de diseo de Estrategia, que permite obtener cdigo ms elegante porque aisla completamente las cosas que cambian dentro de un objeto de funcin10. Un objeto de funcin es un objeto que se comporta. en cierta manera, como una funcin: normalmente, existe un mtodo de inters (en los lenguajes que soportan el mecanismo de sobrecarga de operadores, podernos hacer que la llamada a este mtodo parezco una llamada a mtodo normal) El valor de los objetos de funcin es que, a diferencia de un mtodo normal, los podemos pasar de un sitio a otro y tambin pueden tener un estado que persista entre sucesivas llamadas. Por supuesto, podemos conseguir algo como esto con cualquier mtodo de una clase, pero (al igual que con cualquier patrn de diseo) el objeto de funcin se distingue principalmente por su intencin original. Aqu la intencin es crear algo que se compone como un nico mtodo que podamos pasar de un sitio a otro; por tanto* est estrictamente acoplado (y en ocasiones es indistinguible de l), con el patrn de diseo de Estrategia.

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, y esos 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: //: generics/Functional.java import j a va . math. ; import J a va. til. concurrent. atonde. import java.til.*; import static net.mindview.til.Print.*; // Distintos tipos de objetos de funcin: interface Combiner<T> { T combine(T x, T y); J interface UnaryFunction<R,T> { R functionT x); } interface Collector<T> extends UnaryFunction<T,T> { T resulto? // Extraer resultado del parmetro de recopilacin J interface UnaryPredicate<T> { boolean test(T x); }

15 Genricos 1330 public class Functional ( // Invoca al objeto Combiner para cada elemento con el fin de / / combinarlo con un resultado dinmico, devolvindose // al final el resultante: public static <T> T reduceIterable<7> seq, Combiner<T> combiner) ( Iterator<T> it seq. lterator ti ; if <it .nasNext 0 ) { T result = it.nextO; whiielie.hasNext{)) result * combiner .combine (result. it.nextO}; return result;

] // Si seq es la lista vacia: return nuil; // O generar unaexcepcin

) 1 ) Tomar un objeto de funcin einvocarlo de // la lista, ignorando el valorde retomo.El funcin para cada objeto de

objeto

// puede actuar como un parmetro de recopilacin, as que // se lo devuelve al final, public static <T> Collector<T> forEach(Iterable<T> seq. Collector<T> func) { for(T t : seq) func.function(t); lu En ocasiones, podr ver que a estos objetos se los denomina fmetores, En este texto, utilizaremos el

15 Genricos 1331 termino objeto de funcin en lugar i/unctor. ya que el termino funetor' nene un significado diferente y muy especifico en matemticas. return func;

) // Crea una lista de resultados invocando un objeto // de funcin para cada objeto de la lista: public static cR,T> List<R> transform(Iterable<T> seq. UnaryFunction<R,T> func) ( List<R> result = new ArrayList<R>0; for(T t : seq) result.add(func.function(t)); return result;

} // 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> seq, naryPredicate<T> pred) ( List<T> result = new ArrayListcT>0 / for(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

15 Genricos 1332 concretas: static class IntegerAdder implements CombinerInteger: ( public Integer combine(Integer x. Integer y) ( return x + y ; ) ) static class IntegerSubtracter implements Combiner*Integer> { public Integer combine(Integer x. Integer y) ( return x - y;

} static class BigDecimalAdder implements Combiner<BigDecimal> ( public BigDecimal combine(BigDecimal x, BigDecimal y) { return x.add(y);

15 Genricos 1333 static class BialntegerAdder implements Combiner<BigInteger> { public QigInteger combine(Biglnteger x, Biglnteger y) { return x.add(y);

) static class AtomicLongAdder implements Combiner<AtomicLong> { public AtomicLong combinetAtomicLong x, AtomicLong y) { I f No est claro si esto es significativo: return new AtomicLong(x.addAndGet(y.get)));

) )
II

Podemos incluso hacer una funcin unaria con un ulp"

// (Units in the last place, las unidades en el ltimo lugar): static class BigDecimalUlp implements UnaryFunction<EigDecimal,BigDecmal> ( public BigDecimal function(BigDecimal x) ( return x.ulp()? ) } static class Greater7han<7 extends Comparable<T implements UnaryPredicate<T* { private T bound? public GreaterThanT bound) { this.bound = bound; } public boolean test(T x) { return x.compareTo(bound) > 0;

15 Genricos 1334 ) ) static class MultiplyinglntegerCollector implements Collector<lnteger> ( private Integer val ^ 1? public Integer function{Integer x) ( val *= x? return val; ) public Integer resultO ( return val; } i public static void main(String[J args) { // Genricos, varargs y conversin automtica funcionando conjuntamente: List<Integer> li Arrays-asList{1r 2, 3, 4, S, 6 , 7) Integer result = reduce(li, new IntegerAdder())? print(result)? result = reduce(li, new IntegerSubtracter())? print(result)? print(filter(li, new GreaterThan<Integer>(4)))? print(forEach(li, new MultiplyinglntegerCollectorO).resulto)? print(forEach(filter(li, new GreaterThan<Integer>(4) ) , new MultiplyinglntegerCollector()).result())? Mathcontext me = new Mathcontext(y); List<BigDecimal> Ibd => Arrays .asList ( new BigDecimal(1.1, me}, new BigDecimal12.2, me), new BigDecimal(3.3, me), new BigDecimal14.4, me))? BigDecimal rbd = reducedbd, new 8igDecimalAdder () ) ? print(rbd)? print(filter(lbd, new GreaterThan<BigDecimal>(new BigDecimal(3))1)? // Utilizar la funcionalidad de generacin de primos de Biglnteger: List<BigInteger> lbi = new ArrayLlst<BigInteqer>()? Biglnteger bi = Biglnteger.valueOf(11J? for(int i = 0? i < 11? i*+) ( lbi.add(bi); j bi = bi.nextProbablePrimeO?

print(lbi)? Biglnteger rbi = reduce(lbi, new BigIntegerAdder())? print(rbi);// La sum de esta lista de primos tambin es prima: print(rbi.isProbablePrtme(5)); List<AtomicLor.g> lal * Arrays.asListt new AtomicLong 111, new AtomicLong (47) > new AtomicLong (74) , new

15 Genricos 1335 AtomicLong (133 )) ; AtomicLong ral * reduce (1 al, new AtomicLcngAdder ( M ; print(ral) t print ftransformilbd.new BigDecimalUlp(>)); } } / * Outputi 26 -26 [5, 6, 7] 5040 210 1 1 .000000 [3.300000, 4.400000] tU, 13, 17, 19, 23, 29, 31, 37, 41. 43, 471 311 true 265 10.000001, 0.000001, 0.000001, 0.000001J / / / : -

El ejemplo comienza definiendo interfaces para distintos tipos de objetos de funcin. Estas interfaces se han creado a medida que eran necesarias mientras se desarrollaban los diferentes mtodos y se descubra la necesidad de cada una. La ciase Combiner 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. Como resultado, podemos ver que IntegerAdder y IntegerSubtracter pueden ser tipos de Combiner

Una funcin unario (LnaryFunction) toma un nico argumento y produce un resultado, el argumento y el resultado no tienen por qu ser del mismo tipo Se utiliza un elemento Collcctor como parmetro de recopilacin y podemos extraer el resultado una vez que hayamos acabado. Un predicado Unary Predcate produce un resultado de tipo boolean Hay otros tipos de objetos de funcin que pueden definirse, pero estos son suficientes para entender el concepto.

La clase Functional contiene una serie de mtodos genricos que aplican objetos de funcin a secuencia. El mtodo reducu( ) aplica la funcin contenida en un objeto Combiner a cada elemento de una secuencia con el lln de producir un nico resultado.

15 Genricos 1336 forKachf) toma un objeto Collector 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 funcionar pero puede, a pesar de todo, ser til), o bien el objeto Collector puede mantener el estado interno para actuar como un parmetro de recopilacin, como es el caso en este ejemplo.

transfornif ) genera una lista invocando una funcin llnaryFunetion sobre cada objeto de la secuencia y capturando el resultado.

Finalmente. filler( ) aplica un predicado l'naryPredieate a cada objeto de una secuencia y almacena los objetos que producen true en un contenedor List, que luego se devuelve.

Podemos definir funciones genericas adicionales. La biblioteca estndar de plantillas de C++, por ejemplo, dispone de multitud de ellas. El problema tambin se ha resuelto con unas bibliotecas de cdigo abierto, como por ejemplo JGA (Generic Algorfthms forJovci).

En C++, el mecanismo de tipos latentes se encarga de establecer la correspondencia entre las operaciones cuando se invocan 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 funcin. Observe, por ejemplo, que IntegerAdder V BiyDecimalAddt r resuelven el mismo problema, (sumar dos objetos).

invocando las operaciones apropiadas para su tipo concreto. Este es un ejemplo de combinacin de los patrones de diseo Adaptador y Estrategia.

Fn maini ). podemos ver que en cada llamada a mtodo se pasa una secuencia junto con el objeto de funcin apropiado. Asimismo, vemos que hay expresiones que pueden llegar a ser bastante complejas, como por ejemplo: forEach(tilter(1 i, new GreaterThan(4) . new MulciplyinalntegerCcllector t)).result O

E:sta expresin genera una lista seleccionando todos los elementos de li que sean mayores que 4. y luego aplica el mtodo MulliplyinglntegerCoUector( ) a la lista resultante y extrae el resultado con resulti ). No vamos a explicar los detalles del reste del cdigo, aunque el lector no debera tener problemas en

15 Genricos 1337 comprenderlo sin ms que analizarlo.

Ejercicio 42: (5) Cree dos clases separadas, que no tengan nada en comn Cada clase debe almacenar un valor v dis

poner al menos de mtodos que produzcan dicho valor y permitan realizar una modificacin del mismo. Modifique Functional.java para que realice operaciones funcionales sobre colecciones de dichas dos clases (estas operaciones no tienen que ser aritmticas como son las de Functional.java i Resumen: realmente es tan malo el mecanismo de proyeccin?

Habiendo estado trabajando en explicar las plantillas de C+4 desde que stas fueron concebidas, probablemente yo haya estado haciendo la pregunta que da titulo 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 forma siguiente. Uno de los lugares ms obvios para utilizar un mecanismo de tipos genricos es con clases contenedores tales como List, Set, Map, etc., es decir con las clases que ya hemos visto en el Capitulo 11. Almacenamiento de objetas, v de lo que hablaremos ms en detalle en el Capitulo 17. Anlisis detallado de los contenedores. Antes de Java SE5, cuando incluamos un objeto en un contenedor, ste se generalizaba a Object, perdindose as la informacin de tipos. Cuando se quera extraerlo de nuevo para hacer algo con l, era necesario volver a proyectarlo sobre el upo apropiado. Un ejemplo seria una lista List de objetos Cat (gatos). Sin la versin genrica de los contenedores introducida en Java SE5. lo que haramos seria introducir objetos de tipo Object v extraer objetos de tipoObject. 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 permitan era mal utilizar los objetos introducidos en un contenedor Si introducimos un objeto Dog en un contenedor donde estamos almacenando objetos C at y luego intentamos tratar todo lo que hay en el contenedor como si fuera un objeto Cat. se obtiene una excepcin RuntimcF\ccption al extraer la referencia Dug del contenedor Cat y Halar de proyectarla sobre Cat. Asi pues, el problema termina por descubrirse: la nica desventaja es que se descubra el problema en tiempo de ejecucin en lugar de en tiempo de compilacin.

15 Genricos 1338 En las ediciones anteriores del libro, yo deca que. Esto no es slo una molestia. En ocasiones, puede dar lugar a errores de programacin difciles de detectar Si una parte de un programa (o vanas partes de un programa) inserta objetos en un contenedor y lo nico que podemos descubrir en una parte separada del programa, polla generacin de una excepcin, es (pie se ha introducido un objeto incorrecto dentro del contenedor. entonces nos vemos obligados a averiguar en que 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 asi yt cuando he preguntado a la gente que asista a mis conferencias, tampoco he logrado encontrar a nadie que me dijera que a ellos 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 completamente natural aadir un objeto File (archivo) a files, por lo que habra sido mejor denominar al objeto fileNames (nombres de archivo). Independientemente de lo exhaustivos que sean los mecanismos de comprobacin de tipos de Java, sigue siendo posible escribir programas enrevesados, y el hecho de que un programa mal escrito se pueda compilar no quiere decir que deje de ser un programa mal escrito. Quiz la mayora de los programadores utilicen contenedores con nombres apro- piados como cats que proporcionan una advertencia visual al programador que est intentado aadir un objeto que no sea del tipo Cal. E incluso si llegara a darse el caso de que alguien introduzca el objeto incorrecto, durante cunto tiempo permanecera .culto ese problema antes de ser descubierto'1 Parece lgico pensar que. tan pronto como empezramos a ejecutar pruebas con datos reales, se generara rpidamente una excepcin.

Un determinado autor ha llegado a decir que dicho problema podra 'permanecer oculto durante aos, pero yo no he podido encontrar informes que hablen de personas que tengan grandes dificultades para encontrar errores del tipo perro en una lista de gatos ", ni tampoco he encontrado informes donde se diga que ese problema se produce muy a menudo Mientras que con las hebras de programacin, como veremos en el Captulo 21, Concurrencia^ resulta bastante sencillo y comn que haya errores que slo se manifiesten de forma muy infrecuente, y que slo nos proporcionan una vaga indicacin de qu es

lo que anda mal. en este otro ejemplo de la insercin de objetos erroneos. las cosas no son as. Por tanto, es el problema de la insercin de objetos errneos la razn de que todas estas funcionalidades tan significativas y complejas hayan sido aadidas a .lava?

En mi opinin, la intencin de esa funcionalidad del lenguaje de propsito general denominada 'genricos" (no necesariamente 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 disponer de contenedores que sean seguros respecto a los tipos de datos se obtiene como efecto colateral de la capacidad de crear cdigo que tenga un propsito ms general.

15 Genricos 1339 Por tanto, aunque el argumento de la insercin de tipos incorrectos en una lista se utiliza a menudo para justificar la existencia de los genricos, dicho argumento resulta cuestionable. Y, como decamos al principio del capitulo, no creo que el concepto de genricos tenga nada que ver con ese problema. Por el contrario, los genricos, como su propio nombre indica. constituyen una forma 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 codigo pueda aplicarse a una mayor cantidad de tipos de datos. Como hemos visto en este capitulo, resulta fcil escribir clases contenedoras verdaderamente genricas (es decir, lo que son los contenedores de Java), pero escribir cdigo que manipule sus tipos genricos requiere un esfuerzo adicional tanto por parte del creador de la clase, como por pane del consumidor de la misma, que debe comprender el concepto y la implementacin del patrn de diseo Adaptador. Dicho 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 tambin 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 contenedores no pueden ser tan robustos como deberan. Por ejemplo. ljese en Map. v en particular en los mtodos containsKey(Objeet kcy) y gct(Objcct key). Si estas clases hubieran sido diseadas con genricos previamente existentes, estos mtodos hubieran utilizado tipos parametnzados en lugar de Object. permitiendo asi las comprobaciones en tiempo de compilacin que se supone que los genricos deben proporcionar. En los mapas de C++ por ejemplo, el tipo de la clave se comprueba siempre en tiempo de compilacin.

Hay una cosa muy clara, introducir cualquier tipo de mecanismo genrico en una versin posterior del lenguaje despus de que ese lenguaje haya llegado a ser de uso general, conduce a situaciones extremadamente liosas, y es imposible cumplir el objetivo sin un esfuerzo enorme. En C++. las plantillas fueron introducidas en la versin ISO inicial del lenguaje (aunque incluso eso fue causa de cierta contusin, porque ya se estaba usando una versin anterior, sin plantillas, antes de que el prt- mer estndar de C-^4- apareciera), por lo que. en la prctica, las plantillas fueron siempre una pane del lenguaje. En Java, los genricos 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 lenido un impacto significativo en el diseo del propio mecanismo de genricos. El resultado es que nosotros, los programadores, tenemos que sufrir ahora las consecuencias derivadas de la falla de visin de los diseadores de Java que crearon la versin 1.0. Cuando Java se diseo originalmente, los diseadores tenan conocimiento, 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 diseo). Como resultado, tanto el lenguaje coino los programadores que lo emplean tienen que enfrentarse con una serie de problemas derivados 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 lenguajes, y en especial Xice (vase httjr mcc.soutxeJorge.net; este lenguaje genera cdigo intermedio Java y funciona con las bibliotecas Java existentes) y NextGen (vase http: jopan.es.rice.edu/nextgen) han incorporado otras soluciones ms limpias y menos problemticas para el lema 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 mismo enfoque exacto que C++- adopt con respecto C: usar aquello que estaba disponible y mejorarlo. Lecturas adicionales

15 Genricos 1340 El documento de carcter introductorio para los genricos es Generics in the Java Programming Language, dc Gilad Bracha, que puede encontrar en http://java.sun.eom/j2se/l.5/jHif/generics-tutorial.pdf. Java Generics FAQs de Angelika Langer es un recurso muy til. Puede encontrarlo en tnnv.langer.camclot.de/GenericsFAQ JavaGenericsFA Q. html.

Puede ver detalles acerca de los comodines en Adding Wildcards to the Java Programming Language, de Torgerson. Ernst. Hansen, von der Ahe, Bracha y Gafter. que podr encontrar en \v\v\v.jot.fm/issues/issue_2004_J2/articlc5

Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico The Thinking inJo\ a Annotated Solution Guide, disponible para tu venta en itwii XtnuHlew,net

.16 Matrices

Al final del Capitulo 5. Inicial racin i limpieza. vimos cmo definir e inicializar una matriz.

podramos frutar de describir de manera simple las matrices diciendo que Jo que hacemos es crearlas y rellenarlas, luego seleccionar elementos de las mismas utilizando ndices enteros y diciendo, ademas, que las matrices no cambian de tamao. La mayora de las veces, eso es todo lo que necesitamos saber pero en ocasiones hay que realizar operaciones ms sofisticadas con las matrices, y tambin puede que tengamos que comparar la utilizacin de una matriz con la de oros contenedores ms flexibles. En este capitulo veremos cmo analizar las matrices con un mayor detalle Por qu las matrices son especiales

Existen diferentes maneras de almacenar objetos, asi que qu es lo que hace que las matrices sean especiales?

Existen tres aspectos que distinguen a las matrices de otros 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 ellas. 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 esla velocidad es que el tamao de un objeto matriz es rijo y no puede cambiarse lo largo de la vida de la matriz. Podramos pensar, como alternativa, en utilizar un contenedor de tipo ArrayList (consulte el Capitulo 11, Almacenamiento Je objetos), que se encargara de asignar automticamente mas espacio, creando un nuevo contenedor y desplazando toda? 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 cierto coste asociado, de manera que un contenedor ArrayList o 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 lina matriz o un contenedor

obtendremos una excepcin Kuntiineltxception si nos pasamos de los lmites, un hecho que indica que se ha producido un error del programador.

Antes de lo adopcin de los genricos, las otras ciases 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 podemos crear una matriz para almacenar un tipo especifico. Esto significa que se dispone de una comprobacin de tipos en tiempo de compilacin, con el fin de impedir que insertemos un objeto de tipo incorrecto o que confundamos el upo 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 tns 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 adopcion de mecanismo de genricos 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 aqu un ejemplo donde se comparan las matrices con los contenedores genricos: //: arrays/ContainerComparison.jav a mport java.til. import static net.nundview.util.Print. *;

class BerylliumSphere ( privace static long counter; private final long id <= counter*^; public String toStrngO ( return "Sohere 11 -* id; } j public class CentainerComparison { public static void main(String[J args) { BeryllumSphere[] spheres = new BerylliumSphere[10]; for(int i 0; i < 5; i*+) spheres[i] = new BerylliumSphere(); print(Arrays.toString(spheres )); printisphere34] ); List<BeryliiumSphere> sphereLiat = new ArrayList<BerylliumSphere >l); for(int - 0; i < 5; i++) sphereList.add(new BerylliumSphere()); print(sphereList); print(sphereList.gec(4) ) int [] integers = { 0, 1, 2 , 3. 4. 5 ); print(Arrays.toString(integer s) ) ; print(integers[4]); List<Integer> intList = new ArrayList<Integer>l Arrays.asList(0. 1, 2 , 3. 4, 5)); intList.add(97); print (intList) ,* print l intList.get(4)); ) ) / Output: Sphere 0, Sphere 1, Sphere 2 , Sphere 3, Sphere 4, nuil, nuil, nuil, nuil, nuil] Sphere 4 [Sphere 5, Sphere 6, Sphere 7. Sphere 8. Sphere 9] Sphere 9 [0. 1, 2. 3, 4, 5] 4 (0, 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 | 1 para acceder a los elementos, mientras que un contenedor de tipo List utiliza mtodos como add( ) y let( ) La similitud entre las matrices y el contenedor ArrayList es intencionada, de manera que resulte conceptualmente fcil conmutar entre ambas soluciones. Pero, como vimos en el Captulo 11. Almacenamiento de los objetos, los contenedores tienen una funcionalidad mucho ms nca que las matrices.

Con la aparicin de los mecanismos de conversin automtica, los contenedores son casi tan fciles de utilizar con primitivas como las matrices. La nica ventaja que le queda, en consecuencia, a las matrices es la eficiencia. Sin embargo, cuando lo que estemos iratando 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 matriz con el que estemos trabajando, el identificador de la matriz es de hecho una referencia 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 pane de la sintaxis de ini- ciaiizacin de la matriz, cuanto explcitamente mediante una expresin new. Pane del objeto matriz (de hecho, el nico campo o mtodo al que podemos acceder) es el miembro lencth (longitud) de slo lectura que nos dice cuntos elementos pueden almacenarse en dicho objeto matriz. La sintaxis *| J* es la nica otra forma que tenemos de acceder al objeto matriz.

F.l siguiente ejemplo resume las diversas formas en que puede inicializarse una matriz, y las maneras en que las referencias de matriz pueden asignarse a diferentes objetos matriz. El ejemplo tambin muestra que las matrices de objetos y las matrices de primitivas son casi idnticas en lo que a su uso se refiere. La nica diferencia es que las matrices de objetos almacenan referencias, mientras que las matrices de primitivas almacenan directamente valores primitivos. //: arrays/ArrayCptions.java // Inicializacin y reasignacin de matrices, import java.Util.*; import static net .mindview.util. Print. ,* public class ArrayOptions { public static void main(String[J args) (

// Matrices de objetos: BeryiliumSphereII a; // Variable local no inicializada BeryiliumSphere[] b = new BeryiliumSphere[5]; // Las referencias dentro de la matriz se inicializan // automticamente con nuil: print("b: " + Arrays.toString(b)); BeryiliumSphereU c * new BeryiliumSphere[4]; for (int i = 0; i < c.length; !-*+) if c[i] == nuil) una // Se puede comprobar si referencia nula

c[i] = new BeryiliumSphere(); / / Inicializacin agregada: BeryiliumSphere[] d = ( new BeryiliumSphere(), new BeryiliumSphereO, new BeryiliumSphere() }; // Inicializacin agregada dinmica: a e new BeryiliumSphere[J{ new BeryiliumSphere(), new BeryiliumSphere(), ). // (La coma final es opcional en ambos casos) print ("a. length = " + a. length); print ("b.length * H + b.length) print("c.length = w f c.length); print("d.length M + d.length); a = d; print (Ma. length = ** + a.length); // Matrices de primitivas: int[] e; // Referencia nula int [] f =* new int [5] ; // Las primitivas contenidas en la matriz se // inicializan automticamente con cero: print ("f: " Arrays.toString(f));

int H 9 * new int (4] ; for (int i * 0; i < g.length; i++) 9til - i*i; inttJ h * ( 11, 47, 93 }; // Error de compilacin: variable e no nicilizada: f f Sprint ("e. length *' + e. length); print("f.length = +- f.length); print("g.length = " + g.length); print{"h.length * w h.length); e hj print("e.length " * e. length);

e * new int U{ 1. 2 }; print ("e. length = H + e. length);

Q] }
*

Output:

null, null, null]

La matriz a es una variable local no inieializada y el compilador nos impide que hagamos nada con esta referencia hasta que la hayamos inicializado adecuadamente. La matriz b se imcializa para que apunte a una matriz de referencias BerylliumSphere. pero en esa matriz nunca se llegan a almacenar objetos BerylliumSphcre directamente. De todos modos, podemos seguir preguntando cul es el tamao de la matriz, ya que b est apuntando a un objeto legitimo. Esto nos revela una cierta desventaja: no podemos averiguar cuntos elementos hay realmente almacenados en la matriz, 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 micializan automticamente con el valor nuil, por lo que podemos ver si una posicin concreta de una matriz tiene un objeto almacenado, comprobando si esa posicin tiene un valor nuil. De forma similar, las matrices de primitivas se inicializan automticamente con cero para los tipos numricos, con (chnr)O para char. y con false para boolean.

La matriz c permite ilustrar la creacin del objeto matriz seguida de la asignacin de objetos BerylliumSphcre a todas las posiciones de la matriz. La matriz d muestra la sintaxis de inicializacin agregada que hace que el objeto matriz se cree i implcitamente con new en el cmulo de memoria, al igual que la matriz c) e inicialicc con objetos BerylliumSphere, todo ello en una nica instruccin.

La siguiente inicializacin de matriz puede considerarse como una especie de inicializacin agregada dinmica". La inicializacin 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 argumento una matriz de objetos BerylliumSphere.

Podramos invocar ese mtodo escribiendo: hide(d);

pero tambin podemos crear dinmicamente la matriz que queramos pasar como argumento: hide(new BerylliumSphere[]( new BerylliumSphere(), new BerylliumSphere()});

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 oiro objeto matriz, al igual que podemos 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 objetos. salvo porque las matrices de primitivas almacenan directamente los valores primitivos.

Ejercicio 1: (2) Cree un mtodo que tome como argumento una matnz de objetos BerylliumSphere. 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.

Devolucin de una matriz Suponga que estamos escribiendo un mtodo v que no queremos devolver un umeo valor, sino un conjunto de ellos Los lenguajes como C v (>* hacen que esto sea difcil, porque no se puede devolver una matriz, sino slo un puntero a una matriz. Esto genera problemas, porque resulta complicado controlar el tiempo de vida de la matriz, lo que a su vez conduce a fugas de memoria. En Java, basta Con devolver directamente la matriz. Nunca tenemos por que preocupamos por esa matriz: la matriz pervivir mientras que sea necesaria y el depurador de memoria se encargar de borrarla una vez que hayamos terminado de utilizarla. Como ejemplo, vamos a ver cmo se devolvera una matriz de objetos String: //: arraya/Ic2 Cream.java // Devolucin de matrices desde mtodos. import java.util.*; public class IceCream ( private static Random rand = new Random(47); static final String [J FLAVORS - "Chocolate", "Strawberry", "Vanilla Fudge Swirl", "Mint Chip", "Mocha Almond Fudge". "Rum Raisin", "Praline Cream" , "Mud Pie" public static String[J flavorSet(int n) ( if (n > FLAVORS.length) throw new IIlegalArgumentException("Set too big"); String[] results = new String[n] ; boolean[J picked new boolean(FLAVORS.length]; for tint i * 0 ; i c n; i+*0 ( int t; do t rand.nextInt(FLAVORS.length) ; while(picked[t]); results[i] = FLAVORS[t] / picked[tI = true;

) return results; ) public static void main(String[] args)

{ for tint i = 0; i < 7; i+4-) System.out.printingArrays.toString(flavorSet(3)));

} ) /* Output: [Rum Raisin, Mint Chip, Mocha Almond Fudge] [Chocolate, Strawberry, Mocha Almond Fudge] [Strawberry, Mint Chip, Mocha Almond Fudge] [Rum Raisin, Vanilla Fudge Swirl. Mud Pie] [Vanilla Fudge Swirl. Chocolate. Mocha Almond Fudge] [Praline Cream, Strawberry, Mocha Almond Fudge] [Mocha Almond Fudge, Strawberry, Mint Chip] ///:-

El mtodo flavorSet ) crea una matriz de objetos Siring denominada results. El tamao de esta matriz es n. que est determinado por el argumento que le pasemos al mtodo. A continuacin, el mtodo selecciona una serie de valores aleatoriamente de entre la matriz FLAVORS y los coloca en results, devolviendo despus esta matriz. La devolucin de una matriz es exactamente igual que la devolucin de cualquier otro objeto: se trata simplemente de una referencia. No es importante que la matriz haya sido creada dentro de flavorSet( ), o en cualquier otro lugar. El depurador de memoria se encargar de borrar la matriz cuando hayamos terminado de usarla y esa matriz persistir durante todo el tiempo que la necesitemos.

Como nota adicional, observe que cuando lavorSeM ) selecciona valores aleatoriamente, se encarga de comprobar que un valor concreto no haya sida previamente seleccionado. Esto se hace en un bucle do que contina realizando selecciones aleatorias hasta que encuentre un valor que no este ya en la matriz picked (por supuesto, tambin podra haberse realizado una comparacin String para ver si el valor aleatorio est ya en la matriz results). Si tiene xito, aade la nueva entrada y localiza la siguiente (i se incrementa).

Puede ver analizando la salida que flavorSet( ) selecciona los valores en orden aleatorio cada una de las veces.

Ejercicio 2: (I) Escriba un mtodo que tome un argumento int y devuelva una matriz de dicho 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: //: arrays/MultidimensionalPrimitiveArr ay.java // Creacin de matrices multidimensionales. import java.util.*; public class MultidimensionalPrimitiveArray ( public static void main (String [] args) ( a ( ( 1, 2. 3. }, ( 4. 5, 6. ], )' ) System.out.println(Arrays.deepToString(a) }

) / Output: IU. 2, 31. {4, 5. 63 ] *///;-

Cada conjunto anidado de llaves nos desplaza al siguiente nivel de la matriz.

Este ejemplo utiliza el mtodo Arrays.deepToSiring() de Java SE5. que transforma matrices multidimensionales en objetos String. como podemos ver a la salida.

Tambin podemos asignar una matriz con new He aqui una matriz tridimensional asignada en una expresin new //: arrays/ThreeDWithNew.java import java.til.*; public class ThreeDWithNew { public static void main(Strlng[] args) (

// Matri2 3-D con longitud fija: int t) 11 ti a = new t t2] [2] (4l; System.out.println(Arrays.deeoToString(a) ) ; ) } / * Output: [[[O. 0, 0. 01. 10. 0. 0. 01J4 [CO. 0. Q. 01. ///:[0, 0, 0, 0] 1 ]

Podemos ver que los valores primitivos de la matriz se inicializan automticamente si no proporcionamos un valor de inicializacin explcito. Las matrices de objetos se inicializan con nuil.

Cada vector de las matrices que forman la matriz total puede tener cualquier longitud (esto se denomina matriz desigual): //: arrays/RaggedAr ray.j ava import java.utii.*; public class RaggedArray { public static void main(Stringl) args) ( Random rand = new Random(47); // Matriz 3-D con vectores de longitud varaible: inti] [] I] a = new mt [rand.nextlnt 17) ] [] [] ; for(int i = 0; i < a.length; i+*) { a [i] * new int[rand.nextlnt(5)][1; foriint j - 0; j < all].length; a[i][j] * new int [rand.nextlnt(5)];

) System.out.priatln(Arrays.deepToStrmg(a));

} ) /* Output: tu. no], to], io. o, o. on, in, to. oj, io. on, I to, 0 , 0 ], [0 ], [o, a, 0 , 01 ), [0 , o, o], to, o, o], roi, m. i to], i], [o]]] ///:-

La primera instruccin new cica 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 indeterminado 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: //: arrays/MultidimensionalObjectArrays.java import java.util.*; public class MultidmensionalObjectArrays { public static void main(String[J args) { BeryiliumSphere [] [] spheres >= { { new BeryiliumSphere(), new BeryiliumSphere!) ), { new BeryiliumSphereO, new BeryiliumSphere(), new BeryiliumSphereO . new BeryiliumSphere() { new BeryiliumSphere(), BeryiliumSphere(J, BeryiliumSphere(), BeryiliumSphere (), BeryiliumSphere() , BeryiliumSphere0 * BeryiliumSphere(), BeryiliumSphere() },

). new new new new new new new

b I

System.out .printlniArrays.deepToString (spheres) )

} / Output: I [Sphere 0, Sphere 1], [Sphere 2, Sphere 3, Sphere 4, Sphere 5J , [Sphere 6. Sphere 7, Sphere 8. Sphere 9, Sphere 10, Sphere 11, Sphere 12, Sphere 13]1 *///:-

Podemos ver que spheres es otra matriz desigual, siendo la longitud de cada lista de objetos diferente.

El mecanismo de conversin automatica tambin funciona con los imciallzadores de matrices: //: arrays/AutoboxingArrays.java import java.util.*; public class AutoboxmgArrays {

public static void main(String[] args) { Integer!][] a * { // Conversin automtica; { 1, 2, 3. 4,5, 6, 7, 8, 9, 10 }, } J 1 System.out.printIn(Arrays.deepToString(a);

( / Output: lit, 2, 3. 4, 5, b, 7, 6. 9, 10), [21, 22, 23, 24, 25, 26, 2 1 f 28. 29. 30). [51, 52. 53, 54, 55. 56, 57, 50, 59. 60). [71r 1 2 , 73, 74, 75. 7o, 77, 70, 79, 80)3 *///:He aqui cmo podramos construir por partes una matriz de objetos no primitivos : arrays/AssemblingMuitidimer.sionalArrays. java // Creacin de matrices multidimensionales. import java.util.*; public class AosemblingMultidimensionalArrays ( public static void main (String [] args) ( Integer I] [] a : a e new Integer (3) [] ; for tint 1 = 0; i < a. length; it-0 ( a[i] = new Integer[31; foriint j = 0; j < a[i].length; j} i i ai] fj) i j; // Conversin automtica

System.out.printIn(Arrays.deepToString(a));

) / Output: L10. 0, 03. L0, 1, 21, [0. 2, 4]) ///:La expresin l*j slo tiene por objeto asignar un valor interesante al objeto Integer. L1 mtodo Arrays.deepToStrin*( ) funciona tanto con matrices de primitivas como con matrices de objetos: / / : arrays/MutiDmWrapperArray.java // Matrices multidimensionales de objetos envoltorio". import java.util.*; public class MultiDimWrapperArray {

public static void main(String[) args) { Integer[1[] al * ( // Conversin automtica ( i. 2, 3, ), { 4, 5, 6. }, )* Double[J [] (J a2 = ( // Conversin automtica

( "Jumped", "Over" ), { "The", "Lazy", "Brown". "Dog", "and", "friend" }, h System.out.println("al: " - Arrays.deepToString(al)J; System.out.printin<"a2: " - Arrays.deepToString(a2))/ System.out.println("a3: " + Arrays.deepToString(a3)); } } /* Output: al: [ [ I , 2, 3), [4, 5, 6)3 [[5.5, a2:[[[1.1, 2.23,[3.3, 4.4]), 6.6], [7.7, 8.8)I, [9.9, 1.21, 12.3, 3.433) a3:[[The, Quick. Sly, Fox), [Jumped, Over], [The, Lazy, Brown, Dog, and. friend])De nuevo, en las matrices Integer y Dotible. el mecanismo de conversin automtica de Jasa SE5 se encarga de crear por

nosotros los objetos envoltorio

Ejercicio 3: (4} Escriba un mtodo que cree e inicialice una matriz bidimensional de valores douhle. El tamao de la

matriz estar determinado por los argumentos del mtodo y los valores de inicializacin sern un rango

determinado 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.

Ejercicio 4: (2) Repita el ejercicio anterior para una matriz tridimensional.

Ejercicio 5: (1) Demuestre que las matrices multidimensionales de tipos primitivos se imeilizan automticamente con nuil.

Ejercicio 6: (I) Escriha un mtodo que tome dos argumentos int, indicando las dos dimensiones de una matriz 2-D El

mtodo debe crear y rellenar una matnz 2-D de objetos BerylliumSphere de acuerdo con los argumentos de dimensin

Ejercicio 7: (1 j 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: Peel<Banana>[] peels = new Peel<Banana>[10]; // Ilegal

El mecanismo de borrado automtico de tipos elimina la informacin 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 si se puede es parametrizar el tipo de la propia matriz: //: arrays/ParameterizedArrayType.java class ClassParameter<T { public T[] f(T[l arg) ( return arg; ) i class MethodParameter ( Integer [] ints2 new ClassParameter<Integer>i). f(ints); DoubleH doubles2 = new CiassParameter<Double> 1.f(doubles); ints2 = MethodParameter.(ints); doubles2 = MethodParameter . f (doubles) f) ) ///r-

Observe la comodidad que se deriva de utilizar un mtodo parametnzado en lugar de una clase parametrizada: no hace falta instanciar una clase con un parmetro para cada tipo diferente al que necesitemos aplicar, y adems el mtodo se puede definir como esttico. Por supuesto, no siempre podemos utilizar un mtodo parametnzado en lugar de una clase parametriza- da, aunque si hay muchas ocasiones en las que puede ser preferible.

En realidad, no es del iodo correcto decir que no se pueden crear matrices de tipos genricos. El compilador no permite, en efecto, instanciar una matriz de un tipo genrico, sin embargo, lo que s permite es crear una referencia a dicha matriz. Por ejemplo: List<5tnng> 13 ls;

El compilador admite este tipo de sintaxis sin emitir niguna queja. Y. aunque no podemos crear un objeto matriz real que almacene gencricos. si que podemos crear una matriz del tipo no genrico y efectuar una proyeccin de tipos: //: arrays/ArrayOfGenerics.java // Ea posible crear matrices de genricos. import j ava.til.*; public ciass

ArrayOfGenerics { fcSuppressWamings ("unchecked") public static void main(String(] args) ( List<String>H Isj ListU la = new List [10]; ls = (List<String>[]lla; // Advertencia no comprobada ls 01 * new ArrayList<String>() ; // La comprobacin en tiempo de compilacin produce un error: //l lsUJ = new ArrayList<Integer>O ; // El problema: List<String> es un subtipo de Object Object [) objeets = ls; // Por loa eu la asignacin es correcta // Se compila y se ejecuta sin ningn problemas objectsU] = new ArrayList<Integer>(); // Sin embargo, si se // puede crearuna // advertencia no comprobada: List<BerylliumSphere>[1 spheres = (List<Beryllium5phere>[])ne w List(10]; forint i * 0; i < spheres.length; i*+) scheres(i] -new } ) ///;ArrayList<BerylliumSohere>(}; nuestras necesidades son simples

matriz de genricos, aunque con una

Una vez que disponemos de una referencia a List<String>||. podemos ver que se obtiene una cierta comprobacin en tiempo de compilacin. El problema es que las matrices son covariantes, por lo que una matriz Lisl<8trinj>|| es tambin una matriz Object||. y podemos utilizar esto para asignar un objeto ArrayLst<Integcr> a nuestra matriz, sin que se produzca ningn error ni en tiempo de compilacin ni en tiempo de ejecucin.

Sin embargo, si sabemos que no vamos a efectuar ninguna generalizacin y nuestras necesidades son relativamente simples, es posible crear una matriz de genricos, lo que nos proporciona una cierta comprobacin de tipos bsica en tiempo de compilacin. No obstante, casi siempre un contenedor genrico ser preferible a una matriz de genricos.

En general, nos encontraremos con que los genricos son efectivos en los imites de una clase o mtodo. En los interiores, el mecanismo de borrado de tipos suele hacer inutilizables los genricos. De este modo, no podemos, por ejemplo, crear una matriz de un tipo genrico: //: arrays/ArrayOfGenericType.java // Las matrices de tipos genricos no se pueden compilar. public class ArrayOfGenericType<T> { Til array; // OK i-SuppressWammgs { unchecked*) public ArrayOfGenericType(int Bize) ( / / [ array = new Tlsiel; // Ilegal array (T[])new Object[size] ; // Advertencia no comprobada } // Ilegal: //I public <U> U(] makeArrayO { return new llO] ; ) ) ///.-

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 pojemos crear una matnz de tipo Object, y proyectarla, pero si quitamos la anotacin i a SuppressWarnings obtendremos una advertencia no comprobada en tiempo de compilacin, porque la matriz no almacena realmente, ni comprueba de forma dinmica el tipo T. En otras palabras, si creamos una matriz String||. Java impondr tanto en tiempo de compilacin como en liempo de ejecucin que slo podemos colocar objetos String en dicha matriz. Sin embargo, si creamos una matnz Object|J. podemos almacenar en ella cualquier cosa menos tipos primitivos.

Ejercicio 8: (l) Demuestre las afirmaciones del prrafo anterior

Ejercicio 9: (3) Cree las clases necesarias para el ejemplo Peel<Banana> y demuestre que el compilador no lo acep

ta. C orrija el problema utilizando un contenedor ArrayList.

Ejercicio 10: (2)ModifiqueArrayOfGenerics.java para Demuestre que

emplear contenedores en lugar de matrices.

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 llenas de datos de prueba. Las herramientas de esta seccin permiten rellenar una matnz con valores u objetos. Arrays.fill()

La clase Arrays de la biblioteca estndar de Java tiene un mtodo fill() 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 aqui un ejemplo: //: arrays/FillingArrays.ja va // Utilizacin de Arrays.fill{) import java.util.*; import static net.mindview.util.Print. *; public class FillingArrays { public static void main(String[] args) ( int sire * 6/ booleanf] al = new boolean[size] ; byte[J a2 new byte[size] ; chartJ a3 = new char [size]; short [] a4 *- new shortLsizej; int[] a5 = new int[size] long[J a = new long[size] ; float [] a7 new float[size]; doublet] a9 = new double(size) ; String[J a9 * new String[sizej; Arrays.fill(al. true); printt"al = H + Arrays.toStringfal) 1; Arrays.fill (a2, (byte) 11) , print ("a2 = H + Arrays . toString Ia2)) ,* Arrays.fill(a3, 'x'); print I "a3 = " + Arrays. toString (a3)) ; Arrays.fillla4. (short)17);

print | "a4 = u Arrays. toString(a4)) ; Arrays.fill(a5, 19); printl"a5 = " Arrays.toString<a5)); Arrays.fill(a6. 23); print(Ma6 = n + Arrays.toString(a6) 1 ; Arrays.fill(a7, 29); print(Ma7 = H Arrays.toString<a7)}; Arrays.fill(aS. 47); print ("a8 = " Arrays. tc-String a0U ; Arrays, fill la9, "Helio")r prmt(',a& = " * Arrays . toStrmg la9M ; f f Manipulating ranges: Arrays. f 111 <a9, 3, 5, "Worl*); print C*a9 = Arrays . toString (a9) ) ;

) } /* Output: al [true, true,true, true,true,true] a2 = [11, 11, 11, 11, 11, 11] a9 = [Helio, Helio, Helio, Helio, Helio, Helio] a9 * [Helio. Helio, Helio, World, World, Helio] *///;-

Todcmos rellenar la matriz completa o. como muestran las dos ltimas instrucciones, rellenar tan solo un rango de elementos. Pero como slo se puede invocar Arrays.fill() con un nico valor de datos,los resultados no son especialmente tiles. Generadores de datos

Para crear matrices de datos ms interesantes, pero de una manera flexible utilizaremos el concepto de Generador introducido en el Capitulo 15. Genricos. Si una herramientas utiliza un objeto Generator.

podemos generar cualquier clase de datos eligiendo el objeto Generator adecuado (se trata de un ejemplo del patrn de diseo basado en estrategia), cada uno de los diferentes generadores representa una estrategia diferente.34

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 aqui 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 CountingGonerafor. de modo que pueden utilizar el mismo nombre que los tipos de objeto que estn generando: por ejemplo, un generador que cree objetos de tipo Integer podra generarse mediante la expresin ncw Countin>Generatnr.lnteger( ): //: net/mndview/util/CountingGenerat or.java // Implementaciones simples de generadores, package net.mindview.util; public class CountingGenerator ( pufclic static class Boolean irapleraents Generator<java.lang.Boolean> ( private boolean valu = false; public ja va.lang.Boolean nextO { valu = !valu; // slo salta hacia atrs y hacia adelante retum valu; i

) public static class Byte iraplements Generatorcjava.lang.Byte> { private byte valu * 0; public java.lang.Byte nextI) ( return value++; }

Si bien hay que recalcar que en este tema las trunicra** resulta un tanto borrosas Tambiu podramos argumentar que un objeto Generator represento el patrn de diteflo de Comando: sin embargo, en mi opinin, la tarea consiste en rellenar unti matriz y el objeto Generator lleva a cabo parle de diilm tarca, asi que se traa ms de una estrategia que de un comando
34

16 Matrices 1363 jstatic eharU chars = (abcdefghijk.lmnopqrstuvwxyz" ABCDFGHIJKLMNOPQRSTUVWXYZ"!.toCharArray( ) ; public static class Character implements Generator)ava. lar.g. Character ( int index =* -1; public j ava. lar.g. Character next O ( index => (index -4-1) % chars.length; return chars[index]; ) ) public static class String implements Generatorj ava.lang.String> j private mt length 7; Generator}ava.lang-Character eg = new Character!); public String) {} public String (int length) { this, length = length,* ) public j ava. lang .String nextO ( chart) buf * new char[length! ; for (int i s Oj i < length; i++) buf [ij = eg.next {); return new java.lang.String(buf); ] ) public static class Short implements Generatorjava.lang.Short ( private short value = 0; oublic java.lang.Short next() ( return value*+; ) ) public static class Integer implements Generatorjava.lanq.Integer { private int value = 0; public j ava. lang. Integer next () ( return value-*-*; ) ) public static class Long implements Generatorjava.lang.Long> { private long value = 0; oublic java.lana.Long next f) { return value**; f )

16 Matrices 1364 public static class Float implements Generatorjava.lang.Float> ( private float value = 0; public j ava. lang. Float nextO ( float result * value; value *= 1.0; return result;

} } public static class Double implements Generatorjava.lang.Double> { private double value 0.0; public j ava. lang. Double nextO ( double result value; value +* 1.0; return result; } ) 1 ///:-

Cada clase implemento su propio significado del trmino recuento Ln el caso de CountingGenerator.Charactcr. se trata simplemente de las letras maysculas y minsculas repetidas una y utra vez. La clase CountingC.enerator.Slring utiliza

CounfingGcnerator.Character para rellenar una matriz de caracteres, que luego se transforma en un objeto de tipo String. El tamao de la matriz est determinado por el argumento del constructor. Observe que CountingGenerator.Slring utiliza un objeto Generator<java.lang.Character> bsico en lugar de una referencia especfica a CountingGenerator. Character Posteriormente, este generador puede sustituirse para generar RandomGenerator.String en RandomGenerator.java.

He aqu una herramienta de prueba que utili/a el mecanismo de reflexin con la sintaxis de generadores anidados, de manera que pueda utilizarse para probar cualquier conjunto de generadores que se adapten a esta estructura: //: arrays/GeneratorsTest.java import net.mindview.util; public class GeneratorsTest { public static int size = 10; public static void test(Class<?> surroundingClass,' ( fortClass<?>

16 Matrices 1365 type : surroundingClass.getClasses()) ( System.out.print(type.getSimpl eName(J + try | Generator? g = (Generator? >)type.newlnstance(); for (int i = 0; i size; i+*0 System.out.printf(g.next() + " "); System.out.println(); } catch(Exception e) { throw new RuntimeException(e); ) )

> public static void main (String [] args) { test(CountingGenerator.class); ) } /* Output: Double: 0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 Float: 0.0 1.0 2.0 3.0 4.0 S.O 6.0 7.0 8.0 9.0 Lang: 0 1 2 3 4 5 6 7 8 9 Integer: 0 1 2 3 4 5 6 7 8 9 Short: 0 1 2 3 4 5 6 7 8 9 String: abedefg hijklmn opqrstu vwxyzAB CDEFGHI JKLMNOP QRSTUVW XYZabcd efghijk lmnopqr Character: a b e d e f g h i j Byte: 0 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 dispone de un constructor predeterminado (sin argumentos). El mtodo de reflexin getClasses( ) genera todas las clases anidadas Entonces, el mtodo test() crca una instancia de cada uno de estos generadores e imprime el resultado producido invocando next( ) diez veces.

He aqui un conjunto de objetos Generator que utilizan el generador de nmeros aleatorios. Puesto que el constructor Random se inicializa con un valor constante, la salida es repetible cada vez que

16 Matrices 1366 ejecutemos un programa empleando uno de estos generadores: //: net/mindview/util/RandomGenerator.java f Generadores que producen valores aleatorios, package net.mindview.util; import j ava.util. \ public class RandomGenerator { private static Random r = new Random(47); public static class Boolean implements Generatorjava.lang.Boolean (public java.lang.Boolean next() ( return r.nextBoolean( 1 ;

) ) public static class Byte implements Generatorejava.lang.Byte> { public java.lang.Byte next0 ( retum (byte) r .nextInt 0 ; i ) public static class Character implements Generatorejava.lang.Character> ( public java.lang.Character next() { retum Count ingGenerator. chars ( r.nextlntlCountinqGenerator.chars.length)}; )

) public static class String extends CountingGenerator.String { // Insertar el generador aleatorio de caracteres: { cg = new CharacterO; } // Inicializador de instancia public Stringi) {} public Stringint length) { super(length); ) } public static class Short implements Generatorejava.lang.Short> { public java.lang.Short next() ( return t short)r.nextInt(); ) )

16 Matrices 1367 public static class Integer implements Generatorejava.lang.Integer> { private int mod = 1Q000; public Integer() {) public Integer(int modulo) ( mod = modulo; } public java.lang.Integer next() ( return r.nextlnt(mod) ) ) public static class Long implements Generatorejava.lang.Long> ( prvate int mod = 10000? public Long() () public Long(int modulo) { mod modulo; } public java.lang.Long next0 { retum new java. lang. Long (r. next Int (mod) ) ; } ) public static class Float implements Generatorejava.lang.Float> ( public java.lang.Float next() ( // Eliminar todos los decimales salvo los dos primeros int trimmed = Math.round(r.nextFloat() * 100); retum l (float)trimmed) / 100;

} } public static class Doubl implements Generatorejava.lana.Double> { public java.lang.Doubl next() (long trimmed = Math. round Ir.nextDouble) 100)/ return ((doublitrimmedi / 100; > ) ) ///.-

Puede ver que RandomGenerator.String hereda de CountlngGenerator.String y simplemente inserta el nuevo generador de tipo Character

16 Matrices 1368 Para generar nmeros que no sean demasiada grandes. RandomGenerator.lnteger utiliza de forma predeterminada un mdulo igual a 10.000. pero el constructor sobrecargado nos permite elegir un valor ms pequeo. La misma tcnica se usa para RandomGenerator.Long. Para los generadores de tipo Float y Doubl, los valores situados detrs del punto decimal se recortan. Podemos reutilizar GeneratorsTest para probar RandomGenerator: / /: arrays/RandomGeneratorsTest.java import net.mindvxew.til.; public class RandomGeneratorsTest ( public static void main(String[J args) ( GeneratorsTest.test(RandomGenerator.class); i ) /* Output : Doubl! 0.73 0 - 53 0.16 0.19 0.52 0.27 0.26 0.05 0.B 0.76 Float: 0.53 Q.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: B303 3141 7138 6012 9966 8689 7185 6992 5746 3976 Short: 3358 20592 284 26791 12834 -8092 13656 29324 -1423 5327 String: bklnaMe sbtWHkj UrUk2Pg 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 matriz, necesitamos dos herramientas de conversin. La primera utiliza cualquier generador para producir una matriz de subtipos Object. Para resolver el problema de las primitivas, la segunda herramienta toma cualquier matriz de tipos envoltorio de primitivas y produce la matriz de primitivas asociada.

La primera herramienta nene dos opciones, representadas por un mtodo esttico sobrecargado que se

16 Matrices 1369 denomina array( ) La primera versin del mtodo toma una matriz existente y la rellena utilizando un generador, mientras que la segunda versin toma un objeto Class, un generador v el nmero deseado de elementos y crea una nueva matriz, de nuevo rellenndola mediante el generador elegido Observe que esta herramienta slo produce matrices de subtipos Object y no permite crear matrices de primitivas: //: net/mindview/util/Generated.java package net. mmdview.util; import java.util.*; public class Generated { // Rellenar una matriz existente public static <T> TU arrayT(] a, Generator<T> gen) { return new CollectionData<T>(gen, a.length).toArray(a); ) // Crear una nueva matriz: iSuppressWarnings ( "unchecked'

16 Matrices 1370 )public static <T> T[] array(Class<T> type, Generator<T gen. int sise { Til a * T[]} java.lang.reflect .Array .newlnstance (type. size) ; return new CollectionData<7>Iaen, size*.toArrayfa) ;

) ) ///;-

La clase Collect ionDuta se definir en el Capitulo 17, Anlisis detallado tic los contenedores lista clase crea un objeto Collection relleno con elementos producidos por el generador gen. El nmero de elementos est determinado por el segundo argumento del constructor. Todos los subtipos de Collection tienen un mtodo toArrayt ) que rellena la matriz argumento con los elementos del objeto Collection.

El segundo mtodo emplea el mecanismo de reflexin para crear dinmicamente una matriz del tipo y tamao apropiados. Entonces esta matnz se rellena empleando la misma tcnica que con el primer mtodo.

Podemos probar Generated utilizando una de las clases CountingGenerator definidas en la seccin anterior //: arrays/TestGenerated.java import j ava.ut il.*; import net .mindview.util . ,* public class TestGenerated { public static void main(String[] args) { Integer[] a = ( 9, 9, 7, 6 ); System, out .printIn (Arrays. toString (a)) ; a = Generated.array(a.new CountingGenerator.Integer()); System.out.printIn(Arrays.toString(a)); Integer[] b = Generated.array(Integer.class, new CountingGenerator.Integer( ) , 15); System.out.println(Arrays.toString(b)); j ) / Output:

16 Matrices 1371 [9. 8. 7, 6] [0. 1, 2. 31 tO. 1, 2, 3.4, 5,6,7, 8. 9, 10,11, ///:12,U. 14]

An cuando la matriz a est inicializada. dichos valores se sobreesenben al pasarla a travs de Generated.arrav( ). que sustituye 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 primitivas. Para resolver este problema, creamos un convertidor que toma cualquier matriz de objetos envoltorio y la convierte en una matnz de los tipos primitivos asociados Sin esta herramienta, tendramos que crear generadores especiales para todas las primitivas. //: net/mindvlew/util/ConvertT o.java package net.mindv i ew.ut i1; public class ConvertTo j public static boolean I] primitive(Boolean I] in) { boolean [] result =new boolean (in. length] ; for (int i =0; i< in.length; i-*t-> result[i] in[i]; // Conversin automtica return result;

) public static chart] primitive(Character[] in) ( chart] result new char tin.length) ; for(int i - 0; i < in.length; 1+-*-) resultti] = in til; return result; ) public static bytef] primitive (Byte [ ] in) ( byteU result = new byte[in.length]; for (int i * 0; i < in. length; 1++) result[i] * in[i]f return result; }

16 Matrices 1372 public static short[] primitive(Short[] in) ( short[] result = new short[in.length]; for(inti ^ 0;i < in.length; !) result[i] = inti]/ return result; } public static int[j primitive! Integer[] in) { int[] result = new int[in.length] ; for (inti * 0; i < in. length; i-M*) result[i] a in[i]; return result; } public static long[] primitive(LongU in) { iongU result = new long[in.length]; for (int i = 0 ; i < in.length; i*+) result [i] = inti]; return result; ) public static float[] primitive(Float[] in) ( float[] result * new float[in.length] for (int i = 0; i < in. length; i++) resultti] * in(i]; return result; } public static doublet] primitive(Doublet] in) ( double [] result new double[in.length]; for (int i * 0; i < in. length; i*-*) result[i] = in[i]; return result; ) ) ///:-

Cada versin de primitive! ) crea una matriz de primitivas apropiada con la longitud correcta, y luego copia los elementos desde la matriz in de tipos envoltorio Observe que el mecanismo de conversin automtica entra en accin en la expresin. result[i] = in ti];

16 Matrices 1373 He aqui un ejemplo que muestra cmo puede utilizarse ConvertTo eon ambas versiones de Generated.array( ): //: arrays/PrimitiveConversionDemonstration.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.Integer 0, 15); int[] b = ConvertTo.primitive(a)j System.out.printIn(Arrays.toStrmg (b)); boolean!] c * ConvertTo.primitive( Generated.arrayIBoolean.class. new CountingGenerator .Boolean () , 7) ) ,* System.out.printIn(Arrays.toString(c)); } /+ Output: [0, 1, 2, 3, 4, 5. 6 , 7, 8, 9, 10, 11, 12, 13, 14] [true, false, true, false, true, false, true] *///:-

Finalmente, he aqui un programa que prueba las herramientas de generacin de matrices utilizando clases

RandomGencrator: //: arrays/TestArrayGeneration.java // Comprobar las herramientas que utilizan generadores // para rellenar matrices. import java, util.*'? import net .mindview.util import static net .mmdview. util. Print .*; public class TestArrayGeneration { public static void mainlString[] args) ( int si2 e * 6; boolean!] al = ConvertTo.primitive (Generated.array (

16 Matrices 1374 Boolean.class, new RandomGenerator.Boolean(), size)); print(al " Arrays.toString(al)); byte[] a2 = ConvertTo.primitive(Generated.array( Byte.class, new RandomGenerator.Byte 0, size)); print(hMa2 = M Arrays.toString(a2)); char I a3 = ConvertTo.primitive(Generated.array( Character.class, new RandomGenerator .Character () , size)),- print ("a3 = M Arrays.toString(a3)); short[] a4 * ConvertTo.primitive(Generated.array( Short.class, new RandomGenerator.Short(), s i ze)); print(Ba4 * " + Arrays . toString (a4)) ; int[] a5 ConvertTo.primitive(Generated.array( Integer.class, new RandomGenerator.Integer0 , size)); print (Ha5 H + Arrays.toStnng{a5)) ; longtl a 6 * ConvertTo.primitive(Generated.array Long.class, new RandomGenerator.Long(), size)); print(Ha6 = " * Arrays. toString (a)) j floattl a7 = ConvertTo.primitive(Generated.arrayt Float.class, new RandomGenerator.Float() , size)), print("a7 " * Arrays.toString(a7)); doubleU a8 = ConvertTo.primitive(Generated.array( Double.class, new RandomGenerator.Double I), size)); print("a8 = " + Arrays .toString (a8)) ; ) ) / * Output al = [true, false, true, false, false, true] a2 = [104, -79, -76. a3 = [Z, n, T, c, Q , a5 -[7704,7383, a6 -[7674,8804, a7 =[0.01,0.2, aB x[0.16,0.87, 126, 33, -64] r] -12603] 7706, 575, 8950, 7B26, 0.7, 0.66, 8410, 6342] 4322, 896] 0.87, 0.59]

a4 = [-13408, 22612, 15401, 15161,-28466,

0.4, 0.79, 0.27, 0.45]

16 Matrices 1375 ///.*-

Esto garantiza tambin que cada versin de ConvcrtTo.primitive( ) funcione correctamente.

Ejercicio 11: (2) Demuestre que el mecanismo de conversin automtica (auioboxing) no funciona con las matrices.

Ejercicio 12: (I) Cree una matriz inicializada de valores double utilizando CountingGenerator. Imprima los resul

Ejercicio 13:(2) Rellene un objeto String utilizando CountingGenerator.Character 1376 Piensa en Java tados.Ejercicio 14: CountingGeneralor (6) Cree una matriz de cada tipo primitivo y luego rellene cada matriz utilizando

Impnma cada matriz.

Ejercicio 15: (2) Modifique ConlalncrComparistiLjava creando un generador para BerylliumSpherc y efecte los

cambios necesarios en maln( ) 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 aplicar un incremento que se fijar de acuerdo con un argumento del constructor. Modifique TestArrayGeneration.java para demostrar que la nueva clase funciona correctamente.

Ejercicio 17: (5) Cree y pruebe un objeto Generator para RigDecimal. y compruebe que funciona con los mtodos Generated Utilidades para matrices

En jas a.til, 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 deepKquals( ) para matrices multidimensionales). fill(). del que ya liemos hablado en este captulo. sort( ). para ordenar una matriz: binary Search( ), para encontrar un elemento en una matriz ordenada: toString(). para generar una representacin de tipo String para una matriz: y liasliC ode( ). para generar el valor hash de una matriz (veremos que significa esto en el Capitulo 17. Anlisis detallado de los contenedores). Iodos estos mtodos estn sobrecargados para poder usarlos con todos los tipos primitivos y con objetos. Adems, Arrays.asLst() toma cualquier secuencia o matriz y la transforma en un contenedor de tipo List: ya hemos hablado de este mtodo en el Capitulo 11. Almacenamiento de objetos.

Ejercicio 13:(2) Rellene un objeto String utilizando CountingGenerator.Character 1377 Piensa en Java Antes de analizar los mtodos de Arrays, existe otro mtodo til que no forma pane de Arrays. Copia en una matriz

La biblioteca estndar de Java proporciona un mtodo esttico. System.arraycopy( ), que permite copiar matrices de fonna bastante ms rpida que se si utiliza un bucle for para realizar la copia manualmente. System.arraycopy( ) est sobrecargado para aceptar todos los tipos. He aqu un ejemplo donde se manipulan matrices de valores nt: //: arrays/CopymgArrays. java // Utilizacin de System.arraycopy() import java.util.*; import static net .mindview.util. Print. , public class CopyingArrays ( public static void main(StringU argal ( int Hi* new int[7]; int[] j * new int [10] ; Arrays.fill(i, 47); Arrays.fill(j, 99); print("1 = " * Arrays. toString 11) ); printI" j = " Arrays.toStringI j )); System.arraycopy(i,0, j, 0, i.length); print("j " +Arrays.toString(j)); int n k * new int [5] ,* Arrays.fill<k, 103); System.arraycopy(i.0, Arrays.fill(k, 103); System.arraycopy(k,0, // Objetos: Integer!) u new Integer[10 j; IntegerII v * new Integer[5]; Arraye.f ll lu, new Xnteger(47)) ,* Arrays.flll(v, new Integer<99)); printcu * " i, 0, k.length); print("i = " * Arrays.toString(i)); k. 0. k.length); print Ck * ** * Arrays. toString (k) ) ;

Ejercicio 13:(2) Rellene un objeto String utilizando CountingGenerator.Character 1378 Piensa en Java + Arrays-toString(u) ) ; prlnt("v " Arrays.toString(v)J System.arraycopy<v, 0, u, u.length/2, v.length); print"u * n + Arrays.toStrmaful) ; ) ) / Output: ///:-

Los argumentos de arraycopy() son la matriz de origen, o el desplazamiento dentro de la matriz de origen 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 generar una excepcin.

El ejemplo 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 superficial (consulte los suplementos en linea del libro para conocer ms detalles

System.arraycopy() no realiza conversiones automticas para los tipos primitivos: las dos matrices deben tener exactamente el mismo tipo.

Ejercicio 18: (3) Cree y rellene una matriz de objetos BeryliiumSphere. Copie esta matriz en otra nueva y demuestre

que se trata de una copia superficial. Comparacin de matrices

Arrays 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 iguales, las matrices deben tener el mismo nmero de elementos y cada elemento tiene que ser equivalente al elemento correspondiente de la otra matriz, utilizndose el mtodo e<|uals( ) para cada elemento (para las primitivas, se utiliza el mtodo cquuis( ) de la correspondiente clase envoltorio, por ejemplo. lnteger.equals( ) para int). Por ejemplo:

Ejercicio 13:(2) Rellene un objeto String utilizando CountingGenerator.Character 1379 Piensa en Java //: arrays/ComparingArrays.j ava // Utilizacin de Arrays.equalsl) import java.util.*; import static net.mindview.util.Print.*; public class ComparinaArrays ( public static void main(String[] args) ( int [] al * new int [10]; int [] a2 a new int[10]; Arrays.fill(al, 47); Arrays.fil1(a2, 47); print IArrays.equals(al, a2)); a2 [3] 11; print (Arrays. equals (al, a2 > ) String[] si new Stringt4]j Arrays.fill(si, "Hi"); StringU s2 * ( new String("Hi" i , new Stringi "Hi") , new Stringi"Hi"), new String(MHiM) ); print(Arrays.equal8(si. s2JJ; } } / Out put : tru e fal se tru e *// /;-

Originalmente, al y ai son exactamente iguales, por lo que la salida es true, pero despus se cambia uno de los elementos. lo que hace que el resultado sea false" En el ltimo caso, todos los elementos de si apuntan al mismo objeto, pero s2 tiene cinco objetos diferentes. Sin embargo, la igualdad de matrices esta basada en los contenidos (a travs de Object.etjualM )). 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 constructor. Cree dos

Ejercicio 13:(2) Rellene un objeto String utilizando CountingGenerator.Character 1380 Piensa en Java matrices de estos objetos, utilizando valores de inicializacin idnticos para cada matriz, y demuestre que Arrays.equals( ) dice que son distintas. Aada el mtodo equals( ) a la clase para corregir el problema.

Ejercicio 20: (4) Ilustre multidimcnsionales.

mediante

un

ejemplo

el

uso

de

deepF.quals(

para

matrices

Comparaciones de elementos de matriz

Las operaciones de ordenacin deben realizar comparaciones basadas en el tipo real de los objetos. Por supuesto, una solucin consiste en escribir un mtodo de ordenacin distinto para cada uno de los posibles tipos, pero dicho cdigo no ser reutilizable 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 varia 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 comparacin en muchas rutinas de ordenacin diferentes, se utiliza el patrn de diseo basado en estrategia. Con una Estrategia, la parte del cdigo que varia 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 forma podemos hacer que los diferentes objetos expresen diferentes formas de comparacin y entregarles a todos ellos el mismo cdigo de ordenacin.

Java dispone de dos maneras para proporcionar la funcionalidad de comparacin. La primera es con el mtodo de comparacin naturar 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 positivo si el objeto actual es superior al argumento

He aqu 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.til.*; import net.mindview.util.*; import static net.mindview.util.Print. ;

Ejercicio 13:(2) Rellene un objeto String utilizando CountingGenerator.Character 1381 Piensa en Java public class CompType implements Comparable<CompType> { int i; int j; prvate static int count = 1; public CompType(int ni, int n2) { i * ni; j = n2; i public String toStringO ( String result = "[i=w+i + ,4 j * w *4- j. ^ x : .hfsign Patterns,. Ench Gamma cl al. I Addison-Weslcy. 19951. Consulte Thinking in Patterns (with Javal en wM.\fin<ilie*.nct. ifcounc* % 3 == 0) result "\nM/ return result;

) public int compareTo(CompType rv) { return ti < rv.i ? -1 : (i == rv.i 7 0 : 1));

) prvate static Random r = new Random(47) ,* public static Generator<CompType> generatorO { return new GeneratoreCompType>() { public CompType next() { return new CompType(r.nextlnt(100),r.nextlnt(100))/

} } public static void main (String[] argsJ { CompType f] a Generated.array(new CompType[12], generatorO); print["before sorting:"); print(Arrays.toString(a) ) ; Arrays.sort(a); print("after sorting:"); print(Arrays.toString(a));

Ejercicio 13:(2) Rellene un objeto String utilizando CountingGenerator.Character 1382 Piensa en Java ) } /* Output: before sorting: [U = 58, j = 55], [i = 93, j , [i * 68, j * 01, [i = 22,j . [i = 51, j = 89], [i = 9. 61] , [i=20, j * 58],ti*16, j = 1 after sorting: C Ei 9, j = 78), [ i n 11. j * 22]. [i = 16. j 40] . ti=20, j * 58] .ti =22. j = 61], 7], ti = 51, j [i *98, j = 89] 0] = 61] . [i=58, j = 55],ti=61, j = , ti=88, j * 28).[i*93. j ] *///:29] , [i = 68,j = * j 40] , ti - 11.j 61], [i = 61, 7], u [i = 88, j = 29] j = 28] j *

78], [i * 98, 22]

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 generatnr( ) produce un objeto que impleinenta la interfaz Generator creando una clase interna annima, lista construye objetos C'omp lype 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 ClassCastExeeption en tiempo de ejecucin cuando tratramos de invocar sort( ) F.sto 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 si que implementa Comparable, pero decidimos que no nos gusta la forma en que funciona y que preferiramos disponer de un mtodo de comparacin distinto 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 Capitulo 11. Almacenamiento Je objetos). Se trata de un ejemplo del patrn de diseo 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 Objeet. que tiene un mtodo equals( ). Por tanto, podemos limitamos a utilizar el mcrodo equals( ) predeterminado de

Ejercicio 13:(2) Rellene un objeto String utilizando CountingGenerator.Character 1383 Piensa en Java Objeet 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: //: arrays/Reverse.java // El comparador Collections.rsverserderU import j ava.ut11.r import net.mindview.til. ; import static net. mindview.util.Print.; public class Reverse | public static void main(StringU args) ( CompTypeU a Generated.array( new CompType[12], CompType.generator O); print("before sorting:"); print(Arrays.toString(a)); Arrays.sort(a. Collections.reverseOrder()); print (after sorting.-"); print (Arrays.toString(a));

) ) / Output: before sorting: Mi = 5B, j = 55], [i = 93, j = 61), [i * 61, j * 29] , [i 6B, 7], [i B8, j - 28] , [i- 51. 78], [ ^ 98, j - 611 , [i- 20, 40], li = 11. j * 22] ] after sorting: 3 *///:j j j = * 0], [i = = 22, 9, 16, j j j = =

89], [i 58], [i

Ejercicio 13:(2) Rellene un objeto String utilizando CountingGenerator.Character 1384 Piensa en Java Tambin podemos escribir nuestro propio objeto Comparator Lil siguiente ejemplo compara objetos CompType basados en sus valores j, en lugar de en sus valores : //; arrays/ComparatorTest-java // Implementacin de un comparador para una clase. import java,til.*; import net. .mindview.til ; import static net.mindview.util.Print; class CompTypeComparator implements ComparatorCompType> { public int compare(CompType ol, CompType o2) ( return lol.j < o2.j ? -1 : tol.j == o2.j ? 0 : 1)); } ) public class ComparatorTest { public static void main(String[] args) { CompTypef] a = Generated.array( new CompType[12] , CompType.generator()) ; print(before sorting:H >; print(Arrays.toString(a)); Arrays.sort(a, new CompTypeComparator()); print("after sorting:"); print <Arrays.toString(a)); ) ) / Output: before sorting: Hi = 58, ] a 55]. [i = 93. j = 61] . [i - 61, j = 29] , ti = 68. j = 01, (i = 22, j 7), [i * 88. j = 28] , [i SI. j -89] ,[i = 9. j = 78], Ti = 98, J - 61)

, U .98. j 61] , [i * 9, j * 78J. li 51. j 891 ) *///:-

Ejercicio 21: (3) Trate de ordenar una matriz de objetos del Ejercicio l 8. Implemcnte Comparable para corregir el pro

Ejercicio 13:(2) Rellene un objeto String utilizando CountingGenerator.Character 1385 Piensa en Java 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 implcmenten Comparable o dispongan de un objelo Comparator asociado.35 He aqu un ejemplo que genera objetos String aleatorios y los ordena: //: arrays/StringSorting.java // Ordenacin de ana matriz de objetos String. import java.til.*; import net.mindview.utll.; import static net.mindview.til.Print.*; public class StringSorting ( public statte void main(StringU args) j String[J sa = Generatea.array(new StringL20], new RandomGenerator.String(5)}; print ("Befare sort: + Arrays .toString (sa)) ; Arrays.sort(sa); printl"After sort: " Arrays.toString(sa))? Arrays.sort(sa, Collections.reverseOrderI)); print>"Reverse sort: " + Arrays.toString(sa)\; Arrays.sort(sa, String. CASE_INSENSnTVE_ORDER); print("Case*insensitive sort: " + Arrays.toString(sal); ) ( /* Output: Before suEcU, WHkj. sort:IYNzbr, nyGcF,OWZnT,cQrGs, eGZMm, Mesbt, JMRoE, OneOE,dLsmw,HLGEa,hKcxr,EqUCB,bklna, rUkZP,gwsqP.zDyCy,RFJQA,HxxHv1 (EqUCB, HLGEa, HxxHv, JMRoE, Mesbt, OWZnT, eGZMm,

After sort: gwsqP,

OneOE, F.FJQA,WHkjU,YNzbr,bklna,cQrGs,dLsmw, hKcxr,nyGcF,rUkZP,suEcU,zDyCy]

Reverse sort:[zDyCy, suEcU, rUkZP, nyGcF, hKcxr, gwsqP,


35

' Sorprendentemente. Java 1.0 y 1.1 no incluan ningn sop<ntc para la ordenacin de objetos String

Ejercicio 13:(2) Rellene un objeto String utilizando CountingGenerator.Character 1386 Piensa en Java eGZMm, OWZnT, gwsqP, OWZnT, *///: dLsmw,cQrGs.bklna,YNzbr,WHkjU,RFJQA, Mesbt,JMRoE,HxxHv,HLGEa,EqUCB] hKcxr,HLGEa,HxxHv,JMRoE,Mesbt,nyGcF, RFJQA,rUkZP,suEcU,WHk^U,YNzbr, OneOE, OneOE,

Case-insensitivo sort:(bklna,cQrGs,dLsmw,eGZMm, EqUCB. zDyCy]

Una de las cosas que observamos al analizar la salida en los algoritmos de ordenacin para objetos String es que la ordenacin es lexicogrfica, por lo que se ponen primero todas 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 maysculas o minsculas, se utiliza String.CASEJNSENSITIVEjORDER. como se muestra en la ltima llamada a sort( ) del ejemplo anterior.

El algoritmo de ordenacin que se utiliza en la biblioteca estndar de Java est diseado para comportarse de manera ptima para el tipo concreto que se est ordenando: un algoritmo Quicksort para primitivas y una ordenacin estable por combinacin 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 cuello de botella. Bsquedas en una matriz ordenada

Una vez ordenada una matriz, podemos realizar una bsqueda rpida de un elemento concreto invocando Arrays. binarySeareh( ). Sin embargo, si tratamos de utilizar bmarySearch( ) en una matriz desordenada los resultados sern impredecibles. El siguiente ejemplo utiliza un objeto RHndomGenerator.Integer para rellenar una matriz y luego emplea el mismo generador para producir valores de bsqueda: //: arrays/ArraySearching.j ava // Utilizacin de Arrays.binarySearch(). import java.til.*; import net.mindview.util.*; import static net.mindview.util.Print.*; public class ArraySearching { public static void main{String[] args? ( Generator<Integer> gen * r.ew RandomGenerator. Integer(1000) ; int n a =

Ejercicio 13:(2) Rellene un objeto String utilizando CountingGenerator.Character 1387 Piensa en Java ConvertTo.primitive( Generated.array(new Integer[25], gen)); Arrays.sort(al; print("Sorted array: * Arrays.toString(a)); whiie(true) ( int r * gen.nextt); int location * Arrays.binarySearchla, r); if(location >=0) { print t "Location of " -* r + " is " location + ", a[" location + "] " + a [location]); break; f f Salir del bucle while ) i ) / Outpuc: Sorted array; 1128,140. 200, 207, 258, 258, 322, 429, 511, 520, 522, 551, 555, 589, 693. 704, 809,861, 361, 868, 916, 961. Location of 322 ia 8. a[8] * 322 ///:998] 278, 2B8,

En el bucle While. se generan elementos de bsqueda con valores aleatorios hasta que se encuentra uno de ellos.

Arrays.binaryScarch( ) 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 manteniendo 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 superior a la clave, o bien a.size(). si todos los elementos de la matriz son inferiores a la clave especificada.

Ejercicio 13:(2) Rellene un objeto String utilizando CountingGenerator.Character 1388 Piensa en Java Si una matriz contiene elementos duplicados, no hay ninguna garanta en lo que respecta a cul de esos duplicados se encontrar. El algoritmo de bsqueda no est diseado para soponar elementos duplicados, aunque si que los tolera. Si necesitamos una lista ordenada de elementos no duplicados, hay que emplear TreeSet (para mantener la ordenacin) o

16 Matrices 1389 nkedHasliSet (para mantener el orden de insercin). Estas clases se encargan de resolver por nosotros todos los detalles, de manera automtica. Slo en aquellos casos en los que aparezcan cuellos de botella de rendimiento debera sustituirse una de estas clases por una matriz cuyo mantenimiento se realizara de forma manual.Si ordenamos una matriz de objetos utilizando un objeto Comparator (las matrices primitivas no permiten realizar ordenaciones con un objeto Comparator). hay que incluir ese mismo objeto Comparator cuando se invoque a binarySearch( ) (empleando la versin sobrecargada de este mtodo). Por ejemplo, podemos modificar el programa StrngSorting.jnva para realizar una busqueda. //: arraya/AiphabeticSearch.java // Bsqueda con un comparador, import java.til; import net.mindview.util. *; public class AlphabetcSearch { public static void main(String[] args) ( String] sa = Generated.array(new String(30], new RandomGenerator.String(5)); Arrays.sort(sa, String.CASE_INSENSITIVK_ORDER) ; System.out.println(Arrays.toString (sa)); int index = Arrays.binarySearch(sa, sa[10l, String.CASE_INSENSITIVE_ORDER); System.out.println( * Index: index ) ) /* Output: lbklna, cQrGs, cXZJo, dLsmw, eGZMm, EqUCB, gwsqP, hKcxr, HLGEa, HqXum, HxxHv, JMRoE, JmzMs, Mesbt. MNvqe,nyGcF,ogoYW, sgqia, slJrL, suEcU, uTpnX, vpfPv, WHkjU,xxEAJ,YNzbr, OneOE, zDyCy3 OWZnT. RFJQA. rUkZP, t H\n"4- sa[index]);

Index: 10 HxxHv ///*El objeto Comparator debe pasarse como tercer argximcnto al mtodo hnarySearch( ) sobrecargado. En este ejemplo, el xito de la operacin de bsqueda est garantizado porque el elemento de bsqueda se selecciona a partir de la propia matriz Ejercicio 22: (2)Demuestre que los resultados de realizar una bsqueda binaria con binarySearch( ) en una matriz desordenada son mpredeeibles. Ejercicio 23: (2) Cree una matriz de objetos Integer. rellnela con valores int aleatorios (utilizando conversin autom tica) y dispngala en orden inverso utilizando Comparator. Ejercicio 24: (3)Demuestre que se pueden realizar bsquedas en la clase del Ejercicio 19.

16 Matrices 1390 Resumen En este capitulo, hemos visto que Java proporciona un soporte razonable para las matrices de tamao fijo y bajo nivel Este tipo de matriz pone nfasis en el rendimiento ms que en la flexibilidad, de forma similar al modelo de matrices de C y C++. En la versin inicial de Java, las matrices de tamao lijo y bajo nivel eran absolutamente necesarias, no slo porque los diseadores de Java eligieron incluir tipos primitivos (tambin por razones de rendimiento), sino tambin porque el soporte para contenedores en dicha versin era minimo. Por esa razn, en las primeras versiones de Java siempre era razonable elegir las matrices como forma de implementacin. En las versiones subsiguientes de Java, el soporte de contenedores mejor significativamente y ahora los contenedores tienden a ser preferibles a las matrices desde todos los puntos de vista, salvo el de rendimiento. Incluso en lo que al rendimiento respecta, el de los contenedores ha mejorado significativamente. Como hemos indicado en otros puntos del libro, los problemas de rendimiento nunca suelen presentarse, de lodos modos, en los lugares donde nos los imaginamos. Con la adicin del mecanismo de conversin automtico de tipos primitivos y del mecanismo de genricos, resulta muy sencillo almacenar primitivas en los contenedores, lo que constituye un argumento adicional para sustituir las matrices de bajo nivel por contenedores. Puesto que los genricos permiten producir contenedores seguros en lo que respecta a los tipos de objetos, las matrices han dejado tambin de suponer una ventaja a ese respecto. Como se ha indicado en este capitulo, y como podr ver cuando trate de utilizarlos, los genricos resultan bastante hostiles en lo que a las matrices se refiere. A menudo, incluso cuando conseguimos que los genricos y las matrices funcionen conjuntamente de alguna manera (como veremos en el siguiente captulo), seguimos teniendo advertencias no comprobadas** durante la compilacin.

En muchas ocasiones, los diseadores del lenguaje Java me han dicho directamente que e deberan utilizar contenedores en lugar de matrices; esos comentarios surgan a la hora de discutir ejemplos concretos (yo he estado empleando motrices para ilustrar tcnicas especificas y no tenia, 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 este demostrado que el rendimiento constituye un problema (y que utilizar una matriz permitir resolverlo) deberamos recurrir a la utilizacin de matrices

Puede ser una afirmacin demasiado categrica, pero algunos lenguajes no disponen en absoluto de matrices de lamao fijo y bajo nivel. Slo disponen de contenedores de tamao variable con una funcionalidad significativamente superior a la de las matrices estilo C C++- Java. Python. 36 por ejemplo, tiene un tipo list que utiliza la sintaxis bsica de matrices, pero dispone de una funcionalidad muy
36

I 'use wh*u' Python. orjg.

16 Matrices 1391 superior; podemos incluso heredar a partir de ese tipo; #: arrays/PythonLists.py aLiat print *- [1,2, 3, 4, 5] 51 bsica puedecambiar el tamao de las print type(aList) # <type 'liat'> aList P tl# 2, 3# 4. # 5 Indexacin lista Ae printaList(4) de la

aList.append(6) # listas print

aList 7, 8] # Aadir una lista a una lista aList # [1, 2 , 3, 4, 5, 6, 7, 8] aSlice = aList2:4] print aSlice # [3, 4J class MyList(list): # Heredar de una lista Definir un mtodo, puntero 'this' es explcito: def getReversed(self) :
#

reversed * self[:) # Copiar lista utilizando fragmentos reversed.reverse() # Mtodo predefinido de la lista retum reversed list2 = MyList(aList) fc No hace falta 'new' para crear obietos 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 insertando una secuencia de objetos separados por comas entre corchetes. El resultado es un objeto cuyo tipo en tiempo de ejecucin es list (la salida de las instrucciones print se muestra en forma de comentarios en la misma linea) F1 resultado de imprimir un objeto list es el mismo que si utilizramos Arrays.toString() en Java.

La creacin de una subsecuencia de un objeto list se lleva a cabo a partir de los fragmentos*, incluyendo el operador *:* dentro de la operacin de ndice. El tipo list tiene muchas otras operaciones predefinidas.

MyList es una definicin de class; las clases base se indican entre parntesis. Dentro de la clase, las instrucciones def especifican mtodos y el primer argumento al mtodo es automticamente equivalente a this en Java, salvo porque en Python es explicito y se utiliza el identificador self por convenio (no se

16 Matrices 1392 trata de una palabra clave). Observe que el constructor se hereda automticamente.

Aunque todo en Python e s realmente un objeto ( incluyendo los tipos enteros y de comaflotante), seguimos disponiendo de

una puerta de escape, en el sentido de que se pueden optimizar partes del cdigo cuyorendimiento sea critico extensiones en C. C++ o con una herramientas especial llamada Pyrex. que est diseada para acelerar fcilmente la ejecucin del cdigo. De esta forma, podemos mantener la pureza de la orientacin a objetos sin que ello nos impida realizar mejoras de rendimiento.F.l lenguaje PHP va un paso mas alia todava al disponer de un nico tipo de matriz, que acta tanto como una matriz con ndices de ripo 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 dejaran 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 lia ido mostrando una evolucin en el sentido de alejarse de esta idea para utilizar componentes de mas nivel, como los contenedores. Y hay que tener en cuenta, que si se pudieran incluir los contenedores en el cuerpo principa] del lenguaje, como sucede en otros lenguajes, entonces el compilador dispondra de mejores oportunidades para mejorar el cdigo.

Dejando aparte estas especulaciones tericas, lo cierto es que las matrices siguen estando presentes y que nos encontraremos 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

PythonLists.py en Java.

Puede encontrar las soluciones n los ejercicios seleccionados en el documemo eleeironico The Timking inJava AnnonuedSolutiun Cuide, disponible paro lj venta en mmh51 MintMtfwnt

tAnlisis detallado de los contenedores En el Captulo 11, Almacenamiento de objetos, heii.os introducido los conceptos y la funcionalidad bsica de la biblioteca 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 presentaron en el Capitulo 11. Almacenamiento Je 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 hash('ode( ) y equals( ) que funcionen con los contenedores a los que se les haya aplicado un mecanismo de Itash. Veremos tambin por qu hay diferentes versiones de algunos contenedores y que criterios usar para elegir una versin u otra. El capitulo 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 Captulo 11. Almacenamiento Je 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 J Iterator - Map * Collection J-

r----------- ---/ SortedSet J! AbstractSet | HasMap TreeMap

I IdentityHashMap Abstraen ist A

LinkedHashMap Hashtable (heredado) l^iashSet^j 1 Tree Set WeakHashMap

LinkedHashSet Vector (heredado) 1 Stack (heredado)

Comparator r Utilidades

Taxonoma completa de los contenedores

Java SE5 aade:

La interfaz Queue (para implemcntarla se ha modificado LinkedLIst como vimos en el Capitulo II. Almacenamiento de objetos) y sus implemcmacioncs PriortyQueue (cola con prioridad) y diversas variantes de BlockingQuctic (cola bloqueante) que se vern en Captulo 21. Concurrencia.

Una interfaz ConcurrentMap s su implementacion ConcurrentHasliMap (mapa haslt concurrente), que tambin se utiliza para el mecanismo de hebras de programacin que se aborda en el Capitulo 21. Concurrencia. CupyOnW riicArrayUst y CopyOnWrlteArraySet. tambin para concurrencia.

EnumSet y EnumMap. implementacioncs especiales de Set y Map para utili/ar con enumeraciones, como ^ explica en el Captulo 19. Tipos enumerados.

Varias utilidades en la clase Colleetons

Los recuadros de trazos representan clases abstractas y podemos ver vanas clases cuyos nombres comienzan por Abstract". Estas clases pueden resultar un poco confusas al principio, pero son simplemente herramientas que implcmcn- tan parcialmente una interfaz concreta. Si estuviramos creando nuestra propia clase Set, por ejemplo, no comenzaramos con la interfaz Set e implcmentariamos todos los mtodos: en lugar de ello heredaramos de AbstractSct \ haramos el trabajo mnimo 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 normalmente 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 Colleetons que contiena- mtodos de utilidad estticos, incluyendo uno que se llama fill( ) y que sirve para rellenar colecciones. Al igual que la versin 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 lista resultante puede pasarse a un constructor o a un mtodo addAll( ):

//: contaners/FillingLists.java // Los mtodos Colleetons.E*ll(I y Collections.nCopies!>. import java.util.*; class StrmgAddress ( prvate Striug s; public St_ringAddress(String s) { this.s * s,- } public fitring toStrmgO ( return super.toString ( ) *- " s t

i ) public class FillingLists { public static void main(String[] args) { List<StrmgAddress> list new ArrayList<StrmgAddress> ( Colleetons.nCopies(4, new StringAddress("Helio"))); System.out.printIn(list) Colleetons.fill(list, new StringAddress("Worldi*) 1 ; System.out.println(llst ) ; ) ) /* Output: tSample) [StringAddressft82ba41 Helio, StringAddress(i82ba41 Helio, StringAddress$82ba41 Helio, StringAddress*82ba41 HelloJ [StringAddressc923e30 World 1, StringAddress923e30 World!, StringAddressa923e30 World!, StrngAddress6923e30 World!) *///:-

Este ejemplo muestra dos formas de rellenar un objeto Collection con referencias a un nico objeto. La primera, Collections.nCopieM ), crea un objeto List que se pasa al constructor; el cual rellena el objeto ArrayLst.

El mtodo toStrin( ) en StringAddress llama a Objcct.toStringt ). que genera el nombre de la clase

seguido por la repre- sentacin hexadecimal sin signo del cdigo hash del objeto (generada por un mtodo hashC'ode)) Podemos ver. analizando la salida, que todas las referencias apuntan al mismo objeto y ste tambin se cumple despus de invocar un segundo mtodo, Collections.fll( ). El mtodo fill() es todava menos til debido al hecho de que slo puede sustituir elementos que ya se encuentren dentro del objeto I.ist no permitiendo artadir nuevos elementos Una solucin basada en generador

Casi todos los subtipos de Collcclion tienen un constructor que toma como argumento otro objeto Collection. 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 Capitulo 15. Genricos, y analizado con ms detalles en el Capitulo 16. Matrices) y un valor quantity (cantidad) //: riet/mindview/util/CollectionData .java // Una coleccin rellena con datos utilizando un objeto generador, packaae net.mindview.til; import j ava.Util.*; public class CollectonData<T> extends ArrayList<T> ( public CollectionData(Generator<T> gen, int quantity) { forint i = 0 ; i < quantity; i++) add(gen.next O); i // Un mtodo genrico de utilidad: public static <T> CollectionData<T> list(Generator<T> gen, int quantity) { retum new CollectionData<T>(gen. quantity); ) ) m-.-

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 instancia. Tambin puede utilizarse el mtodo addAIH ) que forma parte de todos los subtipos de Collection para rellenar un objeto Collection existente

El mtodo genrico de utilidad reduce la cantidad de texto que hay que escribir a la hora de utilizar la clase

CollectionData es un ejemplo del patrn de diseo Adaptador37; adapta un objeto Generator al


37

Puede que esto no encaje estrictamente en In definicin de adaptador, tal como se define en el libro

constructor de un tipo Collection.

He aqu un ejemplo que inictaliza un contenedor LinkedHashSct: //: containers/ColiectionDataTest.java import java.til.*; import net.mindview.util.*; claas Government implements Generator<String> ( StringU foundation = ("strange women lying in ponds " * "distributing swords is no basis for a system of " + "government *) . split () ; private int index; public String next 0 { retum foundation [index-r+1 ,* ) ) public class CollectionDataTest ( public static void raairi (String [] args) ( Set<String> set = new LinkedHashSet<String>( new CoIlectionData<String>(new Government O, 15)); // Utilizacin del mtodo de utilidad: set.addAII(CollectionData.list(new Government(), 15)); System.out.println(set); ) ) / Cutput: [strange, women, lying, in, ponds, distributing, swords, is, no, basis, for, a, systera, of, govemment] *///:-

Los elementos estn en el mismo orden en que se insertaron, porque un contenedor LinkedlIashSet mantiene una lista enlazada que preserv a el orden de insercin.

Todos los generadores definidos en el Captulo 16. Matrices, estn ahora disponibles a travs del adaptador CollectionData He aqui un ejemplo donde se utilizan dos de ellos: //t containers/CollectionDataGeneration.java de Design Pane rus, pero creo que cunrnle con el espritu de este patrn

// Utilizacin de los generadores definidos en el Capitulo 16, Matrices. import java.util.*; import net.. mindview. til. ; public class ColleccionDataGeneration ( public static void raain(String[] args) ( System.out.prntlnnew ArrayList<String> ( CollectionData.list( // Convenience method new RandomGenerator.String(9) , 10)) ) ; System.out.printlntnew HashSet<Integer>( new CollectienData<Inteaer>( new RandomGenerator.Inteqer(), 10))); ) } /* Output: [YNzbmyGc, FOWZnTcQr, GseGZMmJM, RoEsuEcUO, neOEdLsmw, HLGEahKcx, rEqUCBbkl, naMesbtWH, kjUrkZPg, wsqPzDyCyJ [573, 4779, 871, 4367, 6090, 7882, 2017, 8037, 3455, 299] ///;-

La longitud de la cadena de caracteres producida por RandomGenerator.Slring se controla mediante el argumento del constructor. Generadores de mapas

Podemos usar la misma tcnica para un objeto Map. pero eso requiere una clase Pair (par), ya que es necesario producir una pareja de objetos (una clave y un valor) en cada llamada ai mtodo next( ) de un generador, con el fin de rellenar un contenedor Map //: net/mindview/util/Pair.java package net.mindview.til; public class Pair<K.V> \ public final K key; public final V valu; public Pair<K k, V v) { key * k; valu = v; ) ) ///:-

17 Anlisis detallado de los contenedores 1400 Los campos kev (clave) y valu (valor) son de tipo pblico y final, de modo que Pair se convierte en un objeto Je transferencia Je Jatos de slo lectura (o mensajera).El adaptador para Map puede ahora utilizar distintas combinaciones de generadores, itcrables y valores constantes para rellenar objetos de tnicializacin de Map: //; net/mindview/uti1/MapData.java // Un mapa relleno con datos utilizando un objeta generador, package net.mindview.utilj import java.til.*; 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 ia 0;i < quantity; ( Pair<K,V> p = gen.next() put(p.key, p.value); } ) // Dos generadores separados: public MapData(Generator<K> genK. Generator<V> genV. int quantity) ( for (int i = 0;i <quantity,* if-O ( put{genK.next(), genV.next()); ) ) // Un generador de clave y un nico valor: public MapData(Generator<K> genK, V value, int quantity){ for (int * 0;i < quantity;i+-f) { put(aenK.next() value); i public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1401 ) //Un iterable y un generador de valor: public MapData(Iterabie<K> genK, Generator<V> genV) ( for(K key : genKJ { out( key, genV.next() ) ; } } // Un iterable y un nico valor: public MapData(Iterable<K> genK, V value) { for(K key : aenKI { put(key, value); ) } // Mtodos genricos de utilidad: public static <K,V> MapData<K,V> map(Generaror<Pair<K,V gen, int quantity) { return new MapDatacK,V>Igen, ouantity); } public static <K,V> MapData<K,V> map(Generator<K> genK, Generator<V> genV, int quantity) { return new MapDatacK,V>(genK, genV, quantity); ) public static <KV> MapDatacK,V> map(Generator<K> genK, V value, int quantity) { retum new MapData<K,V>(genK, value, quantity); } public static <K.v> MapData<K,v> map i Iterable<K> genK, Generator<V> genV) { retum new MapData<K, V> (genK, genV) ; )map(Iterable<K> aenK, V valu) public static <K,V> MapData<K,V* (

17 Anlisis detallado de los contenedores 1402 return new MauDataclC,V> H H1 genK., valu); ) } ///:-

Esto nos permite decidir si queremos utilizar un nico objeto Generafor<Pair<K,Vt dos generadores separados, un generador y un valor constante, un objeto Itcrablc (lo que incluye cualquier tipo Collcction) y un generador, o un objeto Iteruhle 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 MapData.

He aqu un ejemplo en el que se utiliza MapData. El generador Letters tambin implementa Iterable produciendo un objeto Iterator; de esta forma, puede utilizarse para probar los mtodos MapData.m:p() que funcionan con un objeto Iterable //: contamers/MapDataTest.java import java.util.*; import net.mindview.til ; import static net.mindview.til.Print.*; class Letters implements Generator<Pair<Integer,String>>, Iterable <Integer > ( prvat e int size = 9; prvate int number prvate char letter 'A* ; public Pair<Integer,Strin g nextO { return new Pair<Integer,Strin g( number++, "" letter++); ) public Iterator<Integer> iteratorO { return new public static <K,V> MapData<K,V* * 1;

17 Anlisis detallado de los contenedores 1403 Iterator<Integer(I ( public Integernext{)( returnnumber--*;} public boolean hasNextO (returnnumber < size; ) public void remove i) ( ) }i ) throw new UnsupportedOperationException();

) public class MapDataTest ( public static void main(String[] args) ( // Generador de un par: print<MapData.map(new Letters(J, 11)); // Dos generadores separados: print(MapData.map(new CountmgGenerator.Character () , new RandomGenerator.String(3), B)); //Un generador de clave y un nico valor: print () , (MapData.map (new CountmgGenerator.Character

"Valu". 6)); // Un Iterable y un generador de valor: print(MapData.map(ne w Letters(), new RandomGenerator.Stri ng(3))); //Un Iterable y un nico valor: print(MapData.map(new Letters(), "Pop")); ) } /* Output: public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1404 (l=A, 2=B. 3*C, 4=D,5-E, 6=P, 7=G, 11=K} {a=YNz, b=brn,c*yGc,d=F0W, h=GZM) {l=mJM, 2=RoE,3=SuE,4*cU0. B=HLG) (lcPop, 2=Pop,3=Pop,4=Pop, BPop| *///:B=H, 9=1, 10=J,

e=ZnT.=cQr,

g-Gse,

{a=Value, b=Value, cValue# d=Value, e=Value, fValu} 5=ne0,6=EdL, 5=Pop,6 = Pop, 7=smw. 7aRop.

Este ejemplo tambin utiliza los generadores del ('api mo 16, A tutrices.

Podemos crear cualquier conjunto de datos generados para mapas o colecciones usando estas herramientas, y luego inicia- lizar un objeto .Map o Collection utilizando el constructor o los mtodos Map.put \ll( ) o Collection.addAII(). Utilizacin de clases abstractas

Una solucin alternativa al problema de generar datos de prueba para contenedores consiste en crear implementaciones personalizadas de Collection y Map. Cada contenedor de ja\a.util tiene su propia clase abstracta que proporciona una implementacin parcial de dicho contenedor, por lo que lo nico que hace falta es implementar los mtodos necesarios para obtener el contenedor deseado. Si el contenedor resultante es de slo lectura, com suele suceder para los datos <le prueba, el numero de mtodos que es necesario proporcionar se minimiza.

Aunque no resulta particularmente necesario en este caso, la siguiente solucin tambin proporciona la oportunidad de ilustrar otro patrn de diseo: el patrn de diseo denominado Peso mosca. Utilizamos este patrn de diseo cuando la solucin normal requiere demasiados objetos, o cuando la produccin de objetos normales requiere demasiado espacio. El patrn de diseo Peso mosca extemaliza parte del objeto de modo que, en lugar de que todo lo del objeto est contenido en el propio objeto, parte del objeto o la totalidad del mismo se busca en una tabla extema, ms eficiente (o se genera mediante algn otro clculo que permita ahorrar espacio). public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1405 Un aspecto importante de este ejemplo consiste en demostrar lo relativamente simple que es crear sendos objetos Map y Collection personalizados heredando a partir de las clases de java.util.Abstraer Para crear un mapa Map Je slo lectura heredamos de AbstractMap e implcmentamos entrySet( ). Para crear un conjunto Set de slo lectura, heredamos de AbstractSel e implcmentamos terator( ) y size( ).

Ll conjunto de dalos de este ejemplo es un mapa de los pases del mundo y sus capitales2. El mtodo capitals( ) genera un mapa de pases y capitales. El mtodo names( ) genera una lista de los nombres de pases. En ambos casos, podemos obtener un lisiado parcial proporcionando un argumento int que indique el tamao deseado: / / : net/mindview/util/Countries.java // Mapas y listas "Peso mcsca" de datos de ejemplo. package net.mindview.utl; import java.til.38; import static net.mindview.til.Print.; public class Countries ( public static final StringH l] DATA ( // frica { "ALGERIA" , "Algiere"}, ( ANGOLA". "Luanda" ), {"BENIN"."Porto-Novo"). (BOTSWANA"."Gaberone" J, ("BULGARIA","Sofia"}, {"BURKINA FASO","Ouagadougou"), {"BURUNDI","Buj umbura"}, ("CAMEROON",-Yaounde"), {"CAPE VERDE","Praia")( (MnCENTRAL AFRICAN REFBLIC","Bangui"), {"CHAD","N' d j amena"}, ("COMOROS","Moroni"), ("CONGO"."Brazzaville"). {"DJXBOUTI","Dijibouti"}, ("EGYPT"."Cairo"). {"EQUATORIAL GUINEA","Malabo"), ("ERITREA","Asmara"}r {"ETHIOPIA","Addis Ababa"), ("GABON","Librevilie"), {"THE GAMBIA"."Banjul"}, ("GHANA,"Accra"). ("GUINEA","Conakry"}, ("BISSAU","Bissau"), 38 fotos dalos se lian extrado de Internet. A lo laryo del tiempo diverso lectores rae lian enviado correcciones. public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1406 ( "COTE D' IVOIR (IVORY COAST) " , " Yamoussouk.ro" ), { KENYA" , "Nairobi" }, { "LESOTHO**, "Maseru" ), ("LIBERIA","Monrovia"}, ("LIBYA","Tripoli"), (nMADAGA3 CAR","Antananarivo"), {"MALAWI","Lilongwe"), ["MALI","Bamako"), {"MAURITANIA"."Nouakchott"}, (MAURITIUS"."Port Louis"), {"M0R0CC0","Rabat"} ,{"MOZAMBIQUE"."Maputo"). ("NAMIBIA","Windhoek"), {"NIGER","Niamey"). ("NIGERIA"."Abuja"). "RWANDA","Kigali*), ("SAO TOME E PRINCIPE"."Sao Tome"}, ("SENEGAL","Dakar"), {SEYCHELLES"."Victoria"), ("SIERRA LEONE","Freetown", {"SOMALIA","Mogadishu"). ("SOUTH AFRICA","Pretoria/Cape Town"), {*SUDAN-,"Khartoum"}, ("SWAZILAND","Mbabane"J, {"TANZANIA"."Dodoma"). TOGO", "Lome"). ("TUNISIA","Tunis"), ("UGANDA"."Kampala"), ("DEMOCRATIC REPUBLIC OF THE CONGO {ZAIRE)", "Kinshasa"), ("ZAMBIA"."LuBaka"}, ("ZIMBABWE","Harare"}# // Asia ( "AFGHANISTAN" , "Kabul"] , ("BANGLADESH","Dhaka"}, ( "BAHRAIN", "Manama" ), ("BHUTAN","Thimphu"],

{"BRUNEI",Bandar Seri Begawan"}, (CAMBODIA0,"Phnom Penh"), ("CHINA","Beijing"). ("CYPRUS","Nicosia"), ("INDIA","New Delhi"}, {"INDONESIA","Jakarta"), ("IRAN","Tehran"). ("IRAQ","Baghdad). j"ISRAEL","Jerusalem"}, {"JAPAN","Tokyo"), ("JORDAN"."Amman"), ("KUWAIT","Kuwait City"), ("LAOS","Vientiane"), ("LEBANON","Beirut"),

public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1407 ("MALAYSIA", "Kuala Lumpur"), ("THE MALDIVES" , "Male*), {"MONGOLIA","Ulan Bator"), (MYANMAR (BURMA)",*Rangoon"), {"NE PAL 0,"Katmandu"}, ("NORTH KOREA","Pyongyang"}, ("OMAN","Muscat"|, ("PAKISTAN"."Islamabad"). ("PHILIPPINES","Manila"}, {"QATAR","Doha"), ("SAUDI ARABIA"."Riyadh"), ("SINGAPORE","Singapore"}. ("SOUTH KOREA","Seoul"). ("SRI LANKA"."Colombo"), {"SYRIA","Damascus"). ("TAIWAN (REPUBLIC OF CHINA)","Taipei"), ("THAILAND"."Bangkok"), {"TURKEY","Ankara"), ("UNITED ARAB EMIRATES","Abu Dhabi"), ("VIETNAM"."Hanoi"}, (YEMEN."Sanaa"). // Australia y Oceania { "AUSTRALIA" , " Canberra"), ( " FIJI". "Suva " } , {"KIRIBATI","Bairiki"). ("MARSHALL ISLANDS","Dalap-Uliga-Darrit"), ("MICRONESIA","Palikir"), ("NAURU","Yaren"), {"NEW ZEALAND","Wellington"), ("PALAU","Koror"), {"PAPUA NEW GUINEA"."Port Moresby"). {"SOLOMON ISLANDS","Honaira"), ("TONGA"."Nuku'alofa"), ("TUVALU","Fongafale"), {"VANUATU"," Port-Vila"), ("WESTERN SAMOA","Apia"}, // Europa del Este y la Unin Sovitica ("ARMENIA"."Yerevan"), ("AZERBAIJAN","Baku"), {"BELARUS (BYELORUSSIA) *\ "Minsk"}. {"GEORGIA","Tbilisi"), {"KAZAKSTAN","Almaty"), f"KYRGYZSTAN","Alma-Ata"). {"MOLDOVA","Chisinau"}, {"RUSSIA","Moscow"}, ("TAJIKISTAN"."Dushanbe"), ("TURKMENISTAN"."Ashkabad"), ("UKRAINE","Kyiv"), {"UZBEKISTAN","Tashkent"), public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1408 // Europa {"ALBANIA"."Tirana"), ("ANDORRA","Andorra la Vella"), ("AUSTRIA","Vienna"), j"BELGIUM","Brussels"), ( "BOSNIA" , }, { "HERZEGOVINA", "Sarajevo" ), {"CROATIA"."Zagreb"}, j"CZECH REPUBLIC","Prague"), {"DENMARK "."Copenhagen"}, {-ESTONIA",* Ta11 inn"), {"FINLAND","Helsinki"). |"FRANCE","Paris"), {"GERMANY","Berlin"), {"GREECE","Athens"), {"HUNGARY","Budapest"}, ("ICELAND"."Reykjavik"), (IRELAND"."Dublin"), {"ITALY","Rome"). j"LATVIA","Riga"), {"LIECHTENSTEIN"."Vaduz"), {"LITHUANIA","Vilnius"). ("LUXEMBOURG","Luxembourg"), {"MACEDONIA","Skopje"), {"MALTA"."Valletta"), {"MONACO","Monaco"), ("MONTENEGRO"."Podgorica"), {"THE NETHERLANDS","Amsterdam"), {"NORWAY"."Oslo"J, {"POLAND","Warsaw"), {"PORTUGAL","Lisbon"), ("ROMANIA","Bucharest"), ("SAN MARINO","San Marino"), {"SERBIA","Belgrade"), ("SLOVAKIA","Bratislava"), ("SLOVENIA","Ljuijana"}, ("SPAIN"."Madrid"}, {"SWEDEN","Stockholm"), ("SWITZERLAND",Berne"}, ("UNITED KINGDOM","London"), {"VATICAN C I T Y " | , Amrica del Norte y Amrica Central ("ANTIGUA AND BARBUDA"."Saint John1sN), {"BAHAMAS","Nassau"), {"BARBADOS","Bridgetown"), {"BELIZE","Belmopan"), {CANADA,"Ottawa"), ("COSTA RICA."San Jose"), {"CUBA","Havana"),("DOMINICA","Roseau"), {"DOMINICAN REPUBLIC"."Santo Domingo"), ("EL SALVADOR"."San Salvador"), {"GRENADA"."Saint George's"), ("GUATEMALA","Guatemala City"), ("HAITI","Port-au-Prince"), ("HONDURAS"."Tegucigalpa"}. {"JAMAICA"."Kingston"), ("MEXICO"."Mexico City"), {"NICARAGUA"."Managua"), public static <K,V> MapData<K,V* //

17 Anlisis detallado de los contenedores 1409 ("PANAMA","Panama City"), ("ST. KITTS","-"), (NEVIS",Basseterre"), (ST. LUCIA",Castries"). ("ST. VINCENT AND THE GRENADINES", "Kingstown" ), ("UNITED STATES OF AMERICA"."Washington, D.C."), // Amrica del Sur {"ARGENTINA","Buenos Aires"), ("BOLIVIA"."Sucre (legal)/La Paz(administrative)"), { "BRAZIL","Brasilia"}, ("CHILE","Santiago"}. ("COLOMBIA","Bogota"), ("ECUADOR"."Quito"), )GUYANA,"Georgetown"}, ("PARAGUAY","Asuncion"), ("PERU","Liraa"), {"SURINAME","Paramaribo"), ): ("TRINIDAD AND TOBAGOPort of Spain"), j"URUGUAY","Montevideo), ("VENEZUELA","Caracas"),

// Utilizacin de AbstractMap implementando entrySet() private static class FlyweightMap extends AbstractMap<String,String { private static class Entry implements Map.Entry<String,Strina> ( int index; Entry{int index) ( this.index index; ) public boolean equals(Object o) { return DATA[index] [0].equals io) / } public String getKeyo { return DATA [index] [0] ; ) public String aetValue() { return DATA(indexJ [1]; ) public String setValue(String value) ( throw new UnsupportedOperationException (I .* ) public int hashCodef) ( return DATA [index] [o] .hashCodeO ; i )

// Utilizar AbstractSet implementando sized e iterator() static class EntrySet extends AbstractSet<Map.Entry*String,String { private int size; EntrySetIint size! ( if (size < 0) this.size 0; public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1410 // No puede tener un tamafio mayor que ia matriz: else if(size > DATA.length) this.size DATA.length; else this.size = size; ) public int sizeO ( return size; ) private class Iter implements Iterator<Map.Entry<String. Strina { // Slo un abjeto Entry por cada Iterator: private Entry entry new Entry(-1); public boolean hasNextO ( return entry.index < size - 1; ) public Map.EntrycString,Strings next U { entry. index*-* ; return entry; ) public void remove() ( throw new UnsupportedOperationExceptionO/ ) ) public Iterator<Map.Entry<String.String iterator!) { retum new Iter ( ) ; } ) private static Set<Map.Entry<Strina,String entries = new EntrySet(DATA.length); public Set<Map.Entry<String,String entrySetO { return entries; ) ) // Crear un mapa parcial de un numero 'size' de paises: static Map<String,String> select I final int size) { retum new FlyweightMap ( ) { public Set<Map.Entry<String,String entrySetO { retum new EntrySet (size) ; ) 1; public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1411 } static Map<Strlng.String> map = new FlyweightMap(J; public static Map<String#String> capitals 0 ( retum map; // El mapa completo ) public static Map<String,String> capitals(int size) { retum select (size) ; // Un mapa parcial }3 t.atic L3t<String names * new ArrayListcStrmg(map.keySet()); // Todos los nombres: public static List<String names() { return names; } // Un lista parcial: public static List<Strmg names tint size) ( return new ArrayList<String>(select(aisei .keyset I)); ) public static void main (String [] args) ( print(capitals(10J J; print(namesf10)); print (new HashMap<Stnng,String (capitals (31J) ; print(new LinkedHashMap<String,String(capitals(3 I)J; print(new TreeMap<String,String(capitals(3) 1); print(new Hashrable<Stnng,String(capitals(3))); print(new HashSet<String>(names(6))); print(new LinkedHa3hSet<String>(names (6))) print(new TreeSet<String(names 16 ))); print(new ArrayLlst<Strmg (names (6))), print(new LlnkedList^String(names(6))J j print (capitals () . get ('BRAZIL' > ) ,* l ) ( * Output: {ALGEP.IA=Algiers, ANGOLA=Luanda, BENTN=Porto-Novo, public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1412 BOTSWANAGaberone, BULGARIA=Sofia, BURKINA FASO ^Ouagadougou, BURUNDI =Bujumbura, CAMEROON=Yaounde , CAPE VERDE=Praia, CENTRAL AFRICAN REPUBLIC^Bangui) [ALGERIA, ANGOLA.BENIN.BOTSWANA. FASC, BULGARIA. BURKINA

BURUNDI. CAMEROON. CAPE VERDE. CENTRAL AFRICAN REPUBLIC! (BENIN=Porto-Novo. ANGOLALuanda. ALGERIA*Algiers) {ALGERIA^Algiers, ANGOLA*Luanda, BENIN=Porto Novo) (ALGERIA=Algiers, ANGOLA= Luanda, BENIN=Porto-Novo) (ALGERlA=Alg:ers, ANGOLA-Luanda, BENlN=Porto-Novo) [BULGARIA. ALGERIA] BURKINA FASO. BOTSWANA, BENIN, ANGOLA,

[ALGERIA, ANGOLA.BENIN.BOTSWANA, FASO] [ALGERIA, ANGOLA,BENIN,BOTSWANA, FASO] [ALGERIA, ANGOLA,BENIN,BOTSWANA, FASO] [ALGERIA. ANGOLA.BENIN.BOTSWANA. FASO] B r a s i l i a / / / t -

BULGARIA, BULGARIA, BULGARIA, BULGARIA.

BURKINA BURKINA BURKINA BURKINA

public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1413 La matnz bidimensional de cadenas de caracteres DATA es pblica, por lo que se la puede emplear en cualquier otro lugar. Flyweight Map debe implementar el mtodo enfrySct( ). que requiere tanto una implementacin personalizada de Set como una clase Map.Entry personalizada. I le aqui parte de la solucin peso mosca": cada objeto Map.Entry simplemente almacena su Indice, en lugar de almacenar la clave y el valor reales. Cuando invocamos getKeyl ) o get\ alue( ), utiliza el ndice 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.Iterator. F.n lugar de crear un objeto Map.Entn para cada pareja de datos en DATA, slo existe un objeto Map.Entry por cada iterador. El objeto Entry se utiliza como una ventana a los datos: slo contiene un ndice (inde\) a la matriz esttica de cadenas de caracteres. Cada vez que invocamos ne*t( ) para iterator, se incrementa la variable ndice de un objeto Entry, de modo que apunte a la siguiente pareja de elementos y luego el nico objeto Entry de ese objeto Iterator es devuelto por next( ).39

El mtodo select ) genera un contenedor Flyweight Map que contiene un conjunto EntrySet del tamao deseado, el cual se usa en los mtodos capitals( ) y names( ) sobrecargados que se ilustran en nain( ).

Para algunas pruebas, el tamao limitado de Countries es un problema. Podemos usar la misma tcnica para generar contenedores personalizados inicializados que tengan un conjunto de datos de cualquier tamao. A continuacin se muestra una clase de tipo List que puede tener cualquier tamao y que se preinicializa con datos de tipo Integer. //: net/mindview/util/CountinglntegerL ist.java // Lista de cualquier longitud con datos de ejemplo, package net.mindview.util; import j ava.ut il.*; public class Countinglntege rList extends AbstractList<I nteger> { private int size; 1 tos mapas de ja\a.ufil realizan copia* masivas utilizando get Key( ) > gct\alue( >, de modo que esta solucin funcin. Si un nuipa personal izado fucta u copiar simplemente el objeto Map.F.nlr* completo entonces cstu tcnica causara problemas public static <K,V> MapData<K,V*
39

17 Anlisis detallado de los contenedores 1414 public CountinglntegerList{int size) { this.size = size < 0 ? 0 : size; ) public Integer get tint index { return Integer.valueO f(index); ) public int sizeO ( return size; ) public static void main(String[1 args) { System.out.printIn(new CountinglntegerList{30)); ) ) / * Output: TO, 1. 2. 3. 4. 5, 6. 7, 0. 9, 10, 11. 12, 13. 14. 15, 16, 17, 18, 19, 20, 21, 22, 23, 24. 25, 26, 27, 28. 29] *///:-

Para crear una lista de slo lectura a partir de AbstractList. hay que mplementar get( ) y sze( ). De nuevo, se utiliza una solucin de tipo peso mosca: get( ) produce el valor cuando lo pedimos, por lo que la lista no tiene, en la prctica, que estar rellena.

He aqu un mapa que contiene valores de tipo Integer y String unvocos premie iali/ados; puede tener cualquier tamao: //; net/mindview/uti1/CountingMapData. java // Mapa de longitud limitada con datos de ejemplo, package net.mindview.util; import java.util.*; public class CountingMapData extends AbstractMapInteger,String> { private int size; private static String[J chars = public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1415 " A 3 C D E F G H I J K L M N O P Q R S T U V W X Y Z "

.split{" "); public CountingMapData(int size) ( if (size < 0) this.size = 0; this.size = size; ) private static class Entry implements Map.EntryInteger,String> { int index; Entry (int index) { thiB.index * index; [ public boolean equals(Object o) ( return Integer.valueOf(index).eouals{o); ) public Integer getKeyO ( return index; ) public String getValueO { return chars [index % chars .length] * Integer.toStringfindex / chars.length); ) public String setValue(String valu) ( i throw new UnsupportedGperationException(); public int hashCodeM { i i public Set<Map.Entry<Integer.String entrySetO { t LinkedHashSet mantiene el orden de inicializacin: SetcMap.Entrydnteger,String entries * new LinkedHashSet<Map. Entry< Integer, String () ; for(int i * 0; i < size; i++) entries.add(new Entry(i)l,* retum entries; ) public static void main (String [J args) ( System.out.printlntnew CountingMaoDataI60) ) ; public static <K,V> MapData<K,V* retum Integer.valueOf(Index).hashCode();

17 Anlisis detallado de los contenedores 1416 i } /* Output: {0=A0, 1=B0, 2=CQ. 3=D0, 4=E0, 5*FQ, 6=G0, 7=H0, 8=10. 9=JO. 10=KO. 11-L0, 12=M0, 13=N0, 14-00, 15 = P0, 16*Q0, 17=R0.18=S0. 19-TO,20=U0,21=V0,22=W0, 23=X0, 24=Y0,25=Z0, 26=A1.27-B1.28-C1. 29-D1,30-E1,31-F1,32-G1. 34=11,3SJ1, 36=K1.37*L1. 38=M1,39*N1.40=01, 41=P1,42=Q1.43=R1,44=S1, 01, 47-V1,48=W1, 49=X1. 50-Y1.51*21, 52=A2. 53=B2,54=C2,55D2,56*E2, 58=G2,59=H2} */ / / : Aqu, se utiliza un contenedor LinkedHashSet en lugar de crear una clase Set personalizada, por lo que la solucin de tipo peso mosca" no est completamente implementada. Ejercicio 1: ti) Cree una lista (intntelo tanto con ArrayList como con LinkedList) y rellnela utilizando Countrics Ordene la lista e imprmala, luego aplique Collcctons.shuffle() a la lista repetidamente, imprimindola cada vez de modo que pueda ver cmo el mtodo shuffle ) aleatoriza la lista de manera diferente cada vez. Ejercicio 2: (2) Defina un mapa y un conjunto que contengan todos los pases que comiencen por *A\ Ejercicio 3: (1) Utilizando Countrics. rellene un conjunto mltiples veces con los mismos datos y verifique que el con junto termina conteniendo una nica copia de cada instancia. Pruebe a hacer lo mismo con HashSet. I inkedHashSct y TreeSet Ejercicio 4: (2) Cree un inicial ador de Collection que abra un archivo y lo descomponga en palabras usando TextFile, y luego emplee las palabras como origen de los datos para el contenedor de Collcctinn resultante. Demuestre que la solucin funciona. Ejercicio 5: (3; Modifique CountingMapData.java para public static <K,V> MapData<K,V* unplementar 33=H1, 45-T1, 4657F2,

17 Anlisis detallado de los contenedores 1417 completamente la solucin de tipo peso mosca" aadiendo una clase EntrvSct personalizada como la de Countrles.java Funcionalidad de las colecciones La tabla de la pgina siguiente muestra todo lo que se puede hacer con un contenedor de tipo Collection (sin incluir los mtodos que provienen automticamente de Object). y. por tanto, todo lo que se puede hacer con un contenedor de tipo Set o IJst (Lis! tiene tambin funcionalidad adicional). Los contenedores de tipo Map no heredan de Collection, por lo que los trataremos por separado. Observe que no existe ningn mtodo gct( ) para seleccionar elementos mediante acceso aleatorio. Eso se debe a que Collection tambin incluye Set. que mantiene su propia ordenacin interna (y que hace, por tanto, que las bsquedas por acceso aleatorio no tengan ningn significado). Por tanto, si queremos examinar los elementos de un contenedor Collection. debemos utilizar un iterador. El siguiente ejemplo ilustra todos estos mtodos. Aunque estos mtodos funcionen con cualquier cosa que implemente Collection. se utiliza un contenedor ArrayList como mnimo denominador comn":

public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1418

//i concainera/Collectioni-tee hos.3 ava // Cosas que se pueden hacer con todas las colecciones. import j ava.util.* ; import net.mindview.util.; import static net.mindview.util.Print.; public class CollectionMethods { public static void main(Stringi] args) ( Collection<String> c - new ArrayList<String>I ) ; c.addAllCountries.names;6 )/ c.addi"ten); c.addl"eleven"); print(c); // Hacer una matriz a parcir de la lisca: Object H array = c.toArray( ) ; // Hacer una matriz de objetos String a partir de la lista: Stringi) str = c.toArray(new StringiO]); // Enontrar elementos max y mm elements; esto significa // diferentes cosas dependiendo de la forma // en que est implementada la interfaz Comparable: print public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1419 "Collections .max (c) = " Collections .max {el : print I"Collections.min(c) = " + Collections.min(c)); t f Aadir una coleccin a otra coleccin Collection<String> c2 = new ArrayList<String> O ; c2.addAll(Countries.names(6)); c.addAll(c2); print(c); c.remove(Countries.DATA[0] [0])? print(c); c.remove(Countries.DATA[1]10]) ; print(cj; // Eliminar todos los componentes contenidos // en la coleccin proporcionada como argumento: c.removeAil(c2) ; print(c); c.addAll(c2) print(c); // Est un elemento en esta coleccin? String val - Countries.DATA(3][0]; print("c.contains( val Mu ) = ** r c.contains (val i ) ; // Est una coleccin dentro de esta coleccin? print ("c.containsAll (c2) * M + c.containaAlHc2) ) ; Collection<String> c3 * (List<String>)c.subList(3, 5 ) ; // Conservar todos los elementos que estn tanto // en c2 como en c3 (una interseccin de conjuntos) : c2.retainAll(el); print(c2) ; Eliminar todos los elementos de // c2 que tambin aparezcan en c3: public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1420 c2.removeAl1(c3 ); print("c2.isEmpty0 = " + c2.isEmpty()); c = new ArrayList<String>0; c.addAll(Countries.names(6)); print(c); c. clear O; // Eliminar todos los elementos print "after c.clearO:" + c) ; j ) /* Output; [ALGERIA. ANGOLA, BENIN, BOTSWANA, BULGARIA. BURKINA FASO, ten, eleven] Collections.max (cj - ten Collections.rain(O = ALGERIA [ALGERIA. ANGOLA, BENIN, BOTSWANA, BULGARIA. BURKINA FASO, ten, eleven, ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO) [ANGOLA. BENIN, BOTSWANA, BULGARIA, 3URKINA FASO, ten, eleven, ALGERIA. ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO] [BENIN, BOTSWANA, BULGARIA, BURKINA FASO. ten. eleven. ALGERIA, ANGOLA. BENIN, BOTSWANA, BULGARIA, 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 c.clear 1 1: [] ///:-

public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1421 Sc crcan contenedores Array List que contienen diferentes conjuntos ele datos y se los generaliza a objetos Collection, por

lo que queda claro que no se est utilizando ms que la interfaz Collection. main( ) utiliza una serie de ejercicios simples para ilustrar todos los mtodos de Collection.

Las siguientes secciones del capitulo descrtben las diversas iraplemeniaciones de List. Set y Map y se indica en cada caso (con un asterisco) cul debera ser la opcin preferida. Las descripciones de las clases heredadas Vector. Stack y I las lita ble se dejan para el final del captulo; 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 forma bastante inusual de definir una interfaz. Como hemos visto, una interfaz es una especie de contrato dentro del diseo orientado a objetos. Ese contrato dice: Independientemente de cmo decidas implementar esta interfaz, te garantizo que puede enviar estos mensajes al objeto40 Pero el hecho de que exista una operacin opcional viola este principio fundamental; ya que implica que al invocar algunos mtodos no 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 asi. Si una operacin es opcional, el compilador sigue imponiendo la restriccin de que slo se puedan invocar los mtodos especificados 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 t'lilizu aqu el trmino interfaz tanto pata describir la palabra clave nterface como el dignificado ms general de Ion mtodo oportadot por una clase o subclase" public static <K,V> MapData<K,V*
40

17 Anlisis detallado de los contenedores 1422 llamada concreta funciona41 Adems, la mayora de los mtodos que toman un contenedor Collection como argumento slo leen de dicha coleccin. y todos los mtodos de lectura de Collection no son opcionales.

Para qu quemamos definir mtodos como 'opcionales"? Al hacerlo asi, evitamos una explosin de interfaces en el diseo. Otros diseos de bibliotecas de contenedores siempre parecen terminar en una confusa pltora de interfaces, para describir 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 permite 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 l nsupportedOperationException debe ser un suceso raro. En otras palabras, para la mayora 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. llashSet y IlastiMap. asi 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 excepcin l NsupportedOperationException en tiempo de implementacin. en lugar de despues de haber enviado el producto al cliente. Despus de lodo, 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 C++, 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, Aunque esto suene exir.io y posiblemente sea intil ul ser descrito de esta forma, liemos visto, especialmente en el Capitulo 14. Informacin de tipos. que esta especie de comportamiento dinmico puede *er importante public static <K,V> MapData<K,V*
41

17 Anlisis detallado de los contenedores 1423 por lo que tesulta difcil 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. Operaciones no soportadas

Un ongen bastante comn de la aparicin de operaciones no soportadas es cuando disponemos de un contenedor que est respaldado por una estructura de datos de tamao fijo. Obtenemos dichos contenedores cuando transformamos una matriz en una lista con el mtodo Arrays.asList( ). Tambin podemos decidir que algn contenedor (incluyendo los mapas) genere la excecpin UnsupportedOperaflonException utilizando los mtodos "no modific-ables* de la clase Collections Este ejemplo ilustra ambos casos: //: containers/Unsupported.java // Operaciones no soportadas en los contenedores de Java, import java.util.*; public class Unsupported { static void test(String msg, List<String> list] ( System. out. print In f MM msg * " M) ; Collection<String> c = list; Collection<String> subList = list.subList(1,8)j
II

Copia de la sublista:

Collection<Stnng> c2 = new ArrayList<String>(subList); try ( c.retainAll(c2l; ) catch(Exception e> { System.out.printlnI"retainAll0: " e) ; ) try c.removeAll(c21; } catch(Exception e) { System-out .println ("removeAll () : " e) ,* } try ( c.clear(); ] catch(Exception e) { System.out.println("clearI): " + e); ) try ( c.add(" X *); ) catch(Exception e) { System.out.println("add IJ : " e); public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1424 } try ( c.addAll(c2); ) catch(Exception e) { System.out.println("addAll(): 0 - e) ; try { c.remove("C"> ; ) catch(Exception e) ( System.out.println("remove(1: " + e) ; } // El mtodo List.set () modifica el valor pero no // cambia el tamao de la estructura de datos: try ( list.set(0, "X"); ) catch(Exception e) ( System.out.println("List.set(); " + el; } ) public static void main (String[] args) ( List<String> list * Arrays. asListf "A B C D E F G H I J K L".split(" ") latest ("Modifiable Copy", new ArrayList<Strmg>(list)) ; test("Arrays.asList0M, list); test ("unmodif iableliist () ", Collections.unmo difiableList( new ArrayList<Stri ng>(list))); ) ) /* Output: Modifiable Copy Arrays.asList()

retainAll(): java.lang.UnsupportedOperati onException removeAll(): java.lang.UnsupportedOperati public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1425 onException clear i ) : java. lana .UnsupportedOperationExceptio n add () : java. lang.'JnsupportedOperationExc eption adAii(): java.lang.UnsupportedOperat tonException remove( ) : Java.lang.UnsupportedOperatio nException --unnodifiablaList {) retainAHO : java.lang.UnsupportedOperatio nException removeAll(): java.lang.UnsupportedOperatio nException clear(): java.lang.UnsupportedOperatio nException add(): java.lang.UnsupportedGperatio nException addAlI () : java. lang.UnsupportedOperationExce ption remove{ ) i java.lang.UnsupportedOperatio nException List.set<): java.lang.UnsupportedOperatto nException ///:-

Como Arra>s.asList( ) produce un contenedor List que est respaldado por una matriz de tamao fijo, tiene bastante sentido que las nicas operaciones soportadas sean aquellas que no cambien el tamao de la matriz. Cualquier mtodo que provoque un cambio del tamao de la estructura de datos subyacente generar una excepcin UnsuppnrtedOperalionExccption. 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.asLlst() como argumento de un constructor de cualquier coleccin (o utilizar el mtodo add.AUl ) o el mtodo esttico Collccton$.addAl!()) para crear un contenedor normal que permita el uso de todos los mtodos; esta tcnica se muestra en la primera llamada a test< ) de main(). Dicha llamada produce una nueva estructura de datos subyacente de tamao variable.

Los mtodos no modificables" de la clase Collections envuelven el contenedor en un proxy que genero una excepcin InsupportedOperationLxcepiion si se realiza cualquier public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1426 operacin que modifique el contenedor de alguna forma. F.l objetivo de utilizar estos mtodos es producir un objeto contenedor constante". Posteriormente, describiremos la lista completa de mtodos no modificables" de Collections

El ltimo bloque try de test( ) examina el mtodo sett ) que forma parte de List. Este bloque es interesante, porque podemos 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.asLst( ) y el que devuelve Collections.anmodifiablelJst( ) Arravs.asList( ) devuelve una lista de tamao tijo, mientras que Collections.unnudifiablt*Lis<( ) genera una lista que no se puede modificar. Como puede verse analizando la salida, resulta posible modific ar los elementos de la lista devuelta por Arrays.asList( ), porque esto no viola la caracterstica de tamao fijo de dicha lista. Pero es obvio que el resultado de unmodifiablrl.isN ) no debe ser modificable de ninguna forma. Si usramos interfaces, esto habra requerido dos interfaces adicionales; una con un mtodo sct( ) funcional y otra sin dicho mtodo. Asimismo, podran requerirse interfaces adicionales para diversos subtipos no modificables de Collection.

La documentacin de un mtodo que tome un contenedor como argumento deber especificar cuales de los mtodos opcionales debe implcmcntarse.

Ejercicio 6: (2) Observe que List tiene operaciones opcionales" adicionales que no estn incluidas en Collection.

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 invocar add( ) para insertar los objetos, o a utilizar geU ) para extraerlos de uno en uno o a llamar a iteratorf ) para obtener un objeto licrator para la secuencia

public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1427 Los mtodos del siguiente ejemplo cubren, cada uno de ellos, un grupo distinto de actividades las cosas que todas las listas pueden hacer (basicTest( )); el desplazamiento por el contenedor con un iterador (iter\Iotion( )) comparado con la modificacin de valores mediante un iterador (iter\lauipularion()). la comprobacin de los efectos de la manipulacin de una lista (testVisualt )); y las operaciones disponibles nicamente para contenedores de tipo LinkedLists: //: containers/Lists.java
II

Cosas que se pueden hacer con las listas.

import java.til.*? import net.mindview.til.*

public static <K,V> MapData<K,V*

17 Anlisis detallado de los contenedores 1428 ;import s t a t i c net. mlndview. util. Print. *; public class Lists { private static boolean b; private static String s; private static int i; private 3tatic Iterator<String> it; private static ListIterator<String> lit; public static void basicTestIListcString a) { a.addfl, "x"); // Agregar en La posicin 1 a.add"x"); // Agergar al final // Agregar una coleccin: a.3ddAll(Countries.names(25)>; // Agregar tina coleccin comenzando en la posicin 3: a.atdAll(3, Countries.names 25)1; b = a.contains("1*); // Is it in there? // Est toda la coleccin contenida? b = a.containsAll(Countries.names(25)J; // Las listas permiten el acceso aleatorio, que es poco // costoso para ArrayList, y caro para LinkedList: s = a.get(l); // Obtener objeto (con tipo) en la posicin 1 i = a.indexOf("1 ")/ // Determinar ndice del objeto b - a.isEmpty() ; / / Hay algn elemento? it = a.iterator() //Iterador normal lit e a.listlterator); // Listlterator lit = a.listlterator(3); // Empezar en la posicin 3 i * a.lastlndexOf("1"); // ltima correspondencia a.removed); // Eliminar posicin 1 a.remove("3)/ // Eliminar este objeto a.setd, Hyl; // Asignar y" a la posicin 1 // Conservar todo lo que forme parte del argumento i i (la interseccin de dos conjuntos): a .retainAll (Countries .names (25)) .* // Eliminar todo lo que forme parte del argumento: a.removeAll(Countries.names 1 25)); i * a.sized r // Qu tamao tiene? a.clearOj // Eliminar todos los elementos ) public static void iterMotion(List<String> a) ( ListIterator<5tring> it = a. list Iterator (I b

17 Anlisis detallado de los contenedores 1429 it.hasNext(); b = it hasPrevious(); s it. next O ,* i = it.nextlndex(); s = it.previous();
i

a it.previouslndex 0 ,*

> public static void iterMampulationList<String> a) { ListIterator<String> it = a.listlterator(); it.add("47*) // Hay que desplazarse a un elemento despus de add(): it.nextl); // Eliminar el elemento generado: it.remove(); situado despus del recin

// Hay que desplazarse un elemento despus de remove O : it.next(1; // Cambiar el elemento situado despus del borrado: it.set t"47M t ) public static void testVisual(List<String> a) { print(a) ,* List<Strmg> b = Countries.names(25) ; print ("b = " + b) ; a.addAll(bl; a.addAll(bi ; print la) // Insertar, eliminar y reeemplazar elementos // utilizando Listlterator: ListIteracor<String> x * a.listlteratorla.size()/2); x.add("one") ,* print(a) ; print(x.next O); x.remove(); print(x.next0 ) X.set ( M 4 7 ) ; print(a); // Recorrer la lista hacia atrs: x = a.listlterator(a.size( ) ) ; while(x.hasPrevious )) printnb(x.previous{)" "),* print(); print(MtestVisual finished*4); i // Hay algunas cosas que slo los contenedores // tipo LinkedLists pueden hacer: public static void

17 Anlisis detallado de los contenedores 1430 testLinkedList() { LinkedList<String> 11 = new LinkedList<String>{) / 11.addAll<Countries.names(25)); print(111; // Tratarlo como una pila, insertando: 11.addFirst{"one"i; II.addFirstI"two"); print(11Jj / / Como si se 'consultara" la cima de la pila: print(11.getFirst{ ) ) // Como si se extrajera de una pila: print(11.removeFi rst()); print(11.removePirst( ) ) // Tratarlo como una cola, extrayendo elementos // del final: print(11.removeLast()); print(11); } public static void main(String[] args) { // Crear y rellenar una nueva lista cada vez: basicTest( new LinkedLst<String>(Countries.names(25))); basicTest( new ArrayList<String>(Countries.names{25))); iterMotion new LinkedList<String>(Countries.names t25)) ) ,iterMotion( new ArrayList<String>(Countries.names(25))); iterManipulation( new LinkedList<5tnng> (Countries .names (25) )) ; iterManipulation( new ArrayList<String>(Countries.names(25))); testVisual (new LinJcedList<Strmg>(Countre3 .ames (25J )) ; testLinkedList. () ; ) ) /* Execute to see oucputl *///:-

En basicTest( ) e iter\lotion( ). las llamadas se realizan en orden para mostrar la sintaxis apropiada, y aunque se captura el valor de retomo, dicho valor no se utiliza. En algunos

17 Anlisis detallado de los contenedores 1431 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.

Ejercicio 7: (4) Creetantouncontenedor rellnelos utilizando el genera

ArrayList como otro de tipo LlnkcdList y

dor Countrles.names( ) Imprima cada lista utilizando un iterador normal y luego inserte una lisia 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 lisia y desplazndose hacia aras.

Ejercicio 8: (7) Cree una clase que represente una lista genrica simplemente enlazada denominada SList. la cual, para

hacer las cosas simples, no implemento la interfaz List Cada objeto Link (enlace) de la lista puede contener una referencia al siguiente elemento de la lista, pero no al anterior (LinkedList, por contraste, es una lista doblemente enlazada, lo que significa que mantiene enlaces en ambas direcciones). Cree su propio iterador SListlterator que. de nuevo por simplicidad, no implemente Listlterator El nico mtodo de SList aparte de toStrng( ). debe ser lterator( ), que generar un elemento SListlterator. La nica manera de insertar y eliminar elementos de un contenedor SList es mediante SListlterator Escriba el cdigo necesario para ilustrar el uso de SList. Conjuntos y orden de almacenamiento

Los ejemplos del contenedor Set del Captulo II, Almacenamiento Je objetos, proporcionan una buena introduccin a las operaciones que pueden realizarse con los conjuntos bsicos. Sin embargo, dichos ejemplos utilizan, por comodidad, tipos de Java predefinidos como Integer y Strinjt. que estaban diseados para poderlos utilizar dentro de contenedores. A la hora de crear nuestros propios tipos, debemos tener en cuenta que un contenedor Set necesita una forma de mantener el orden de almacenamiento. El cmo se mantenga ese orden de almacenamiento varia de una implemeniacin de Set a otra.

17 Anlisis detallado de los contenedores 1432 Por tanto, las diferentes implementaciones de Set no slo tienen diferentes comportamientos, sino tambin diferentes requisitos, adaptados al tipo de objeto que puede introducirse dentro de un contenedor Set concreto:

El asterisco en HashSet indica que. en ausencia de otras restricciones, sta debe ser la opcion pa'ferida. porque est optimizada para conseguir la mxima velocidad.

La definicin de hashCode() se describir ms adelante en el capitulo. Es necesario crear un mtodo cquals() para el almacenamiento tanto de tipo hash como de tipo rbol, pero el mtodo hashC'ode( ) 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 Linked HashSet Sin embargo, con el fin de mantener un buen estilo de programacin, conviene sustituir siempre hastiCodc( ) cada vez que se sustituya equals( ).

Fstc ejemplo ilustra los mtodos que deben definirse para utilizar convenientemente un lipo de datos con una implementa- cin concreta de Set: //: containers/TypesForSets.java // Mtodos necesarios para almacenar nuestro propio // tipo de datos en un conjunto, tipos de datos, import j ava. ut i 1. ; class SetType f int i f public SetType(int n) ( i * n; } public boolean equals(Object o) (

17 Anlisis detallado de los contenedores 1433 return o instanceof SetType & & . (1 == ((SetType)o).i); public String costringi) ( return Integer. toString(i) ; ) ) class HashType extends SetType { public HashType(int nl { super In); J public int hashCode(> ( return i; ) } class TreeType extends SetType implements Comparable<TreeType> ( public TreeType (int n) { super (n) j public int compareTo(TreeType arg) ( return (arg.i < i ? -1 : (arg.i i ? 0 : 1)) ; ) } public class TypesForSets { static <T> Set<T> fill(Set<T> set, Class<T> typei { cry ( for (int i - 0; i < 10; i++) set.add( type.getConstructor(int.class).newlnstance(i)); J catch(Exception e) { threw new Runt imeExcept ion (e) ,* ) return set; > static <T> void test(Set<T> set. ClassT> typei ( fill(set, type); fill(set, type); // Intentamos aadir duplicados fill(set, type); System.out.printIn(set);

17 Anlisis detallado de los contenedores 1434 } public static void main(Stringi) args) ( test(new HashSet<HashType>(), HashType.class); test(new LinkedKasnSet<HashType>(), HashType.classJ; test(new TreeSet<TreeType>!) , TreeType.class); / / Cosas que no funcionan; test(new HashSetcSetType(), SetType-class); test(new HashSet<TreeType>(), TreeType.class); test(new LinkedHashSet<SetType>0, SetType.class); testnew LlnkedHashSet<TreeType> ( ) t TreeType.class); try { testinew TreeSet<SetType>(), SetType.class); } catch(Exception e) ( ) System.out.println(e.getMessage( ) } ;

try { test(new TreeSet<HaflhTyps>(). HashType.class); } catch(Exception e) ( System.out.printIn(e.getMessage()); i

) ) /* Output: (Sample) (2, 4, 9, 8, 6 , 1, 3, 7, 5r Oj (0. 1. 2, 3, 4, 5, 6, 7. 6. 9] (9,8, 7.6, 5. 4.3. 2, 1,

01

9, 0 r 1, 2. 3. 4, 5/ 6, 7, 8, 9] java.lang.ClassCastxception: SetType cannot be cast to java.lang.Comparable Para demostrar que java.lang.ClassCastException; HashType mtodos son necesarios cannot be cast to java.lang.Comparable : - para un contenedor Set concreto y para evitar, al mismo tiempo, la duplicacin de cdigo, hemos creado tres clases. La clase base, SetType. simplemente almacena un valor int y lo imprime mediante foStringt) Puesto que todas las clases almacenadas en conjuntos deben tener un mtodo equa!i(). tambin incluimos dicho mtodo en la clase base. 1.a igualdad est basada en el valor de la variable int I.

17 Anlisis detallado de los contenedores 1435 HashType hereda de SetType y aade el mtodo hash(*ode( ) necesario para insertar un objeto en una implementacion hash de un conjunto.

La interfaz Comparable, implementada mediante TreeType. es necesaria st vamos a usar un objeto en algn tipo de contenedor ordenado, como por ejemplo SortedSet (del cual TreeSet es la nica implementacin). En compare lo( ). observe que nu hemos usado la forma "simple y obvia* rcturn i-i2. Aunque se trata de un error de programacin comn, slo funcionara adecuadamente si i e 12 fueran valores int sin signo (si Java tuviera una palabra clave "unsigned, que no es el caso). La expresin no funciona para los valores nt 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, l-j producir un desbordamiento y devolver un valor negativo, lo cual es incorrecto.

Normalmente, lo que queremos es que el mtodo cumpareTo ) permita obtener una ordenacin natural que sea coherente con el mtodo equals( ). Si cqualM ) devuelve true para una comparacin concreta, entonces comparelo( ) debera devolver un resultado igual a cero para dicha comparacin, y si equals( ) devuelve false para una comparacin, entonces cninparcToi ) deberia dar un resultado distinto de cero pan dicha comparacin

Ln TypesForSets. tanto fill( ) como test( ) se definen utilizando genricos, con el fin de evitar la duplicacin de cdigo. Para verificar el comportamiento de un conjunto, test() invoca fill( ) Mbre el conjunto de prueba set tres veces, tratando de introducir objetos duplicados. El mtodo fll( ) loma un contenedor Set de cualquier tipo y un objeto Class del misino tipo. Utiliza el objeto Class para descubrir el constructor que admite un argumento int. e invoca dicho constructor para aadir elementos al conjunto.

Analizando la salida, podemos ver que llashSet mantiene los elementos en alguna especie de orden misterioso (que entenderemos claramente ms adelante en el capitulo). LinkedllashSet mantiene los elementos en el orden en que fueron insertados y TreeSet mantiene los elementos ordenados (debido a la forma en que se mplementa compareTo ). dicho orden resulta ser descendente).

Si tratamos de utilizar tipos de datos que no soporten apropiadamente las operaciones necesarias con conjuntos que requieren dichas operaciones, el funcionamiento es incorrecto. Al insertar un objeto SetType o TreeType, que no incluye un mtodo hashCode( ) redcfiuido. en una implementacin hash se generan valores duplicados, con lo que se viola la caracterstica principal de un conjunto. Este error es bastante molesto,

17 Anlisis detallado de los contenedores 1436 porque ni siquiera se produce un error en tiempo de ejecucin; sin embargo, el mtodo hashCode< ) predeterminado es legitimo, v por tamo es un comportamiento legal, aun cuando sea incorrecto. La nica forma fiable de garantizar la correccin de ese programa consiste en incorporar cdigo de pruebas en el sistema final de produccin, {consulte el suplemento en http Snd\riew.nei/Books/BetierJva para obtener ms informacin).

Si tratamos de utilizar un tipo de datos que no implemento Comparable en un contenedor TreeSet, se obtiene un resultado ms definido: se genera una excepcin cuando el contenedor TreeSet trata de utilizar el objeto como si fuera de tipo Comparable. SortedSet

Los contenedores SortedSet garantizan que sus elementos estn ordenados, lo que permite proporcionar funcionalidad adicional mediante los siguientes mtodos, definidos en la interfaz SortedSet:

Comparator comparator ): genera el objeto Comparator utilizado para este contenedor Set. o nuil para el caso de una ordenacin natural.

Object frst( ). devuelve el elemento ms bajo Object last( ): devuelve el elemento ms alto.

SortedSet siibSet(fromElcment. toElcment): genera una vista de este contenedor Set de los elementos comprendidos entre from Element. incluido, y toElement. excluido.

SortedSet headSet(toElement): genera una vista de este contenedor Set con los elementos inferiores a toElement

SortedSet tailSet(fromClement): genera una vista de este contenedor Set con los elementos superiores o iguales a fromElement.

17 Anlisis detallado de los contenedores 1437 He aqui un ejemplo simple: //: containers/SortedSetDemo.java // Lo que se puede hacer con un contenedor TreeSet. import j ava.util.*; import static net.mindview.util.Print; public class SortedSetDemo ( public Btatic void main(Stringf] args) ( SortedSet<String> sortedSet = new TreeSetString*0; Collect ions.addAl1(sortedSet, "one two three four five six seven eight" . spl it( * *) ); pri nt( sor ted Set ); String low = sortedSet.first i ); String high = sortedSet.last 0 print(low); print(high); Iterator<String> it = sortedSet.iterator() ? for{int i s 0; i < 6; i-nO ( i(i M 3) low =* it.nextO; if fi *- 6) high => it.nextO,* else it.nextO? } print (low) print (high) ,* print(sortedSet.subset(low, high)); print(sortedSet.headset(high)); orint (sortedSet. tailSet (low) ) ,* ) | / Output: {eight, five, four, one, seven, six, three, two) eight tw

17 Anlisis detallado de los contenedores 1438 oone two [one, seven, six, three] [eight, five, four, one, seven, six, three] [one, seven, six, three, two] ///:-

Observe que SortedSct quiere decir ordenado de acuerdo con la funcin de comparacin del objeto*, no orden de insercin. El orden de insercin puede conservarse utilizando LinkedHasliSet

Ejercicio 9: (2) Utilice Randomfienerator.String para rellenar un contenedor TreeSet, pero empleando ordenacin

alfabtica. Imprima el contenedor TreeSet para verificar la ordenacin.

Ejercicio 10:(7) Utilizando un contenedor LinkedList como implementacin subyacente, defina su propio contenedor

Sorted Set. Colas

Dejando aparte las aplicaciones de concurrencia, las dos nicas implementaciones de colas en Java SE5 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: //: containers/QueueBehavioroava // Compara el comportamiento de algunas de las colas import java.util.concurrent; import java.util; import net.mindview.util.*;

17 Anlisis detallado de los contenedores 1439 public class QueueBehavior ( private static int count 10; static <T> void test(Queue<T> queue, Generator<T> gen) { for {int i = 0; i < count; i++) queue.offer Igen.next 01; whilefqueue.peek 0 != null) System.out.print(queue.remove() " H); ) System.out.printIn();

static class Gen implements Generator<String> { String [] s * ("one two three four five six seven M "eight nine ten).split(M "J; int i; public String next() { return s[ir+J; ) } public static void main(String[] args) { test(new LinkedList<String>(), new Gen(H; test (new PriorityQueue<Stnng> () , new GenO); test (new ArrayBlockingQueue<String>(count), new GenO); test (new ConcurrentLinkedQueue<5tring> 1 1 , new GenO); test (new LinkedBlockingQueue<Stnng>O . new GenO); test (new PriorityBlockingQueue<String> (} f new GenO); i ) / 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 ///:Podemos ver que, con la exception de las colas eon prioridad, un eontenedor Queue devuelve I os clemcnios cxactamente en el mismo orden en que fucron insertados en la cola. Colas con prioridad

Ya hemos proporcionado una breve introduction a las colas con prioridad en cl Capitule 11. Alnuuenantitnfo de obfetas Un problema ms interesante que los que alii analizamos sria el de una lista de tareas que hacer. en la que cada objeto con tenga una cadena de caractres y sendos valores tie prioridad principal v secundaria. La ordenactn de esta lista estT de nuevo. controlada por la mplementacion de Comparable:

17 Anlisis detallado de los contenedores 1440 /fi containers/ToDoList.java ft Un uao m3 coTplajo de PriorityQueue. import java.util. ; class ToDoList extends PriorityQueuecToDoList.ToDoItem ( static class ToDoItem implements Comparable<ToDoItem> ( private char primary/ private int secondary; private String item; public ToDoItem (String td, char pri, int sec) { primary = pri; secondary sec ; item * td; } public int compareTo(ToDoItem argl ( if(primary > arg.primary) return >1; if (primary arg.prlmaryj 11(secondary > arg.secondary) return ^1; else if(secondary -= arg.secondary} return Oj return -1;

public String toStringO ( return Character.toString(prima ry) secondary " : " f item;

) public void add(String td, char pri, int seel { super.add(new ToDoItemitd, pri, sec)); } public static void main(String I] argsl ( ToDoList toDoList * new ToDoList(); toDoList.add("Empty trash', 'C*. 4); toDoList.add("Feed dogM, 'A*. 2 ) j toDoL1st.add("Feed bird". 0, 71 j toDoList.add("Mow lawnM, 'C*. 3); toDoList.add I "Water lawn", 'A*, 1); toDoList.add(MFeed cat", *B', I); while i1 toDoList.isEmpty() 1 System.out.printin(toDoList.remove0); >

17 Anlisis detallado de los contenedores 1441 } / Output: Al: Water lawn A2 : Feed dog B1: Feed cat 37 : Feed bird C3: Mow lawn C4: Enpty trash

Ejercicio 11: (2) Cree una clase que contenga un objeto Integcr que se imcialice con un valor comprendido entre 0 v

I00 utilizando java.ufil.Randoni Implemente Comparablc empleando este campo Integer. Rellene una cola de tipo PriorityQueue con objetos de dicha clase y extraiga los valores usando poll( ) para demostrar que se obtiene el orden deseado. Colas dobles

Una cola (loblc es similar a una cola normal, 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 efectuar una generalizacin a una interfaz Deque {cola doble), a diferencia de lo que podramos hacer con Queue en el ejercicio anterior. Sin embargo, podemos crear una clase Deque empleando el mecanismo de composicin y exponer, simplemente, los mtodos relevantes de LinkedList: //: net/mindview/util/Deaue.java // Creacin, de una cola doble a partir de LinkedList. package net.mindview.util; impert java.til.*; public class Deque<T> ( prvate LinkedList<T> deque = new LinkedList<T>(J; public void addFirst(T e) ( deque.addFirst{el ; ) public void addLast(T e) ( deque.addLast(e); ) public T getFirstl) ( retum deque.getFirst () ; ) public T getLa3t() { return deque.getLast0; } public T removeFirst( ( return deque.removeFirst(); ) public T removeLastO { return deque.removeLast(); ) public int slzeU | return deque .size <) ;

17 Anlisis detallado de los contenedores 1442 ) public Strina toString) { retum deque.toString0; | // Y otros mtodos segn sean necesarios... ) ///:-

Si utilizamos esta clase Deque en nuestros propios programas, es posible que descubramos que necesitamos aadir otros mtodos para que la clase resulte prctica. HL* aqu un ejemplo simple de prueba de la clase Deque //: containers/DequeTest.java import net.mindview.util.*; import static net.mindview.util.Print; public class DequeTest {

static void fillTest(Deque<Integer> deque) { fordnt i * 20; i < 27; -m-) deque.addFirs t{i); forint i = 50; i < 55; -m-J deque.addLast(i); ) public static void mam(String [] args) ( Deque<Integer di = new Deque<Integer>() ; fillTest(di); print(di); while(di.sizeO 1= 0 )

1443 Piensa en Java printnb(di.removeFirst() "); print(); fillTest(di); while(di.size(I != 0)printnb(di.removeLast () + n)j ) ) /* Output: [26. 25. 24, 23. 22, 21. 20, 50. 51, 52. 53. 54] 26 25 24 23 22 21 20 50 51 52 54 53 52 51 50 20 21 22 23 24 ///.53 54 25 26

Resulta poco probable que tengamos que insertar y extraer elementos por ambos extremos, por lo que Deque no se emplea tan comnmente como Queue Mapas

Como vimos en el Capitulo 11. Almacenamiento de objetos, la idea bsica de un mapa (tambin denominada matriz asociativa) es la de almacenar asociaciones clave-valor (pares), de modo que se pueda buscar un valor utilizando una clave. La biblioteca estndar de Java contiene diferentes implementacioncs bsicas de mapas: HashMap. TreeMap. I.inkedHashMap. WeakHashMap. ConcurrentHashVlap y IdentityHashMap Todas tienen la misma interfaz bsica Map. pero difieren en cuanto a su comportamiento, incluyendo la eficiencia, el orden en el que se almacenan v se presentan los pares, el tiempo que el mapa conserva los objetos, el funcionamiento de los mapas en los programas multihebra y el

modo de determinar la igualdad de claves. implementaciones de indica la

La gran cantidad de lainterfazMap nos impor

tancia de este tipo de contenedor.

return result.toString(;

1444 Piensa en Java Para comprender mejor los mapas resulta til ver cmo se construye una matriz asociativa. He aqui una implementacin extremadamente simple: //: containers/AssociativeArray.java // Asocia claves con valores. import static net.mindview.util.Print. *; public class AssociativeArray <K,V> { private ObjectU pairs; private int index; public AssociativeArray (int length) ( pairs = new Object[length! f2] ; ) public void put(K key, V value) { it(index >= pairs.length) throw new ArraylndexOutOfBoundsEx ception 0; pairs (index-*- *] = new Object [J ( key, value J;

tSuppressWaming s ("unchecked") public V get(K key) { for (int i - 0; i < index; i-M-) if(key.equals(pairs[i] 10])) return (V)pairs[i][1]; return null; // Clave no encontrada } public String toStringO ( StringBuilder result = return result.toString(;

1445 Piensa en Java new StringBuilder0; for(mt i = 0; i < index; i++) { result append (pairs li] [0] . toString .) ) ; result.append(M : M); result.appendIpairs Ii] [1].toString()); if(i < index - 1) result.append\" \n}; )pubiic static void tnain (String (J arge) (

AssociativeArray<String. Scnng> map * new AssociativeArray<String,5tring >(6); raap.put("sky", "blueM)i raap.put("grass", "green"); map.put ("ocean" , dancing"),* raap.put ("tree", ,,tallM); map.puc"earth", "brown"); raap.put|"sun", "warm'M; try {

map.put("extra", "object"!; // Past the end } catch(ArraylndexOutOfBound3Exce pton e) { print ("Too many objects!");

return result.toString(;

1446 Piensa en Java }

print(map);

print (map.get < "ocean*')) ;

} /* OUtpUt:

return result.toString(;

1447 Piensa en Java Too many objec ts! sky : blue grass s green ocea n : danci ng tree : tall earth : brow n sun : war m danci ng ///:-

Los mtodos esenciales en una matriz asociativa son put( ) y get( ). pero para facilitar la visualizacin se ha sustituido toString( ) con el fin de imprimir los pares clave-valor Para demostrar que funciona. main( ) carga una matriz AssociativeArray con pares de cadenas de caracteres e imprime el mapa resultante, extrayendo a continuacin con get( ) uno de los valores,

Para utilizar el mtodo gct( ). se pasa la clave (key) que queremos buscar y el mtodo devuelve como resultado el valor asociado, o bien devuelve nuil si no se puede encontrar esa clave ti mtodo gef( ) utiliza la que posiblemente sea la tcnica menos eficiente return result.toString(;

1448 Piensa en Java imaginable con el fin de localizar el valor: comienza por la parte superior de la matriz y usa cquals( ) para comparar las claves. Pero el objetivo del ejemplo es la simplicidad no la eficiencia.

Por tanto, la versin anterior es instructiva, pero no resulta muy eficiente y tiene un tamao fijo, lo que da como resultado una implementacin poco flexible. Afortunadamente, los mapas de java.til no tienen estos problemas y pueden emplearse perfectamente en el ejemplo anterior. Ejercicio 12: (l)Utihce mapas de tipo HashMap. TreeMap y LinkedlIashMap en el mtodo muin( ) 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 valor de tipo InteRcr. Empleando la utilidad net.mind- view.util.TextFile de este libro, abra un archivo de texto y extraiga las palabras de dicho archivo utilizando los espacios en blanco y los signos de puntuacin como delimitadores. Cuente el nmero de veces que cada palabra aparece en dicho archivo. Rendimiento return result.toString(;

1449 Piensa en Java El rendimiento es una de las cuestiones fundamentales en los mapas, y resulta demasiado lento utilizar una bsqueda lineal en get( ) a la hora de intentar localizar una clave. Es en este aspecto donde HashMap permite acelerar las operaciones En lugar de realizar una lenta bsqueda de la clave, este contenedor utiliza un valor especial denominado cdigo hash. E1 cdigo hash es una forma de tomar una cierta informacin contenida en el objeto en cuestin y transformarla en un valor int "relativamente univoco que se utilizar para representar dicho objeto. hashCode( ) es un mtodo de la clase raiz

Objecf. por lo que todos los objetos Java pueden generar un cdigo hash Un contenedor HashMap toma el cdigo hash devuelto por hashC*odc( ) y lo utiliza para localizar rpidamente la clave. listo permite mejorar enormemente el rendimiento.0

He aqu las implementaciones bsicas de Map. 1:1 asterisco situado junto a HashMap indica que. en ausencia de otras restricciones. esta debera ser la opcin preferida, ya que est optimizada para maximizar la velocidad. Las otTas implementa- ciones enfatizan oirs caractersticas, por lo que no resultan tan rpidas como HashMap

return result.toString(;

1450 Piensa en Java

El almacenamiento hash es la forma que ms comnmente se utiliza para almacenar elementos en un mapa. Posteriormente veremos cmo funciona este tipo de almacenamiento.

Los requisito para las claves utilizadas en un mapa son iguales que para los elementos de un conjunto. Ya hemos visto una ilustracin de estos requisitos en TvpesForSets.java Todas las claves deben disponer de un mtodo equnls( ) Si la clave se utiliza en un mapa hash, tambin debe disponer de un mtodo hashC wle( ) apropiado. Si la clave se utiliza en un mapa de tipo TrecMap, deber implementar Comparable.

El siguiente ejemplo muestra las operaciones disponibles en la interfaz Map. utilizando el conjunto de datos de prueba CountingMapData anteriormente definido: //: cantamers/Maps. java // Cosas que se pueden hacer con mapas. import j ava. ut il. concurrent. * ,* import java .til. , import net.mindview.til.; import static net.mindview.til.Print public class Maps { return result.toString(;

1451 Piensa en Java Si te tipo Je utilizacin sigue iin satisfacer su requisitos de rendimiento. puede tolerar todava na la bsqueda en tabla* escribiendo *u propio contenedor Map y personalizndolo para los tipos particulares de dalos que cs utilizando, con el fui de evitar los retardos asociados con las proyecciones de lipo hacia y dcuic Object. Iara obtener niveles lodavia mejores de rendimiento, lo* eutusiastai. le la velocidad pueden consultar el liHro de ouuld Knuth, Vu trt o] Computer Programming. Ibhtmr J.Sorring and Seanhmg, Segunda adicin, con el fin de \utitmr la listas de >eymentus con desbordamiento por matrices que tienen dos ventajas adicionales: pueden optimizarse de acuerdo con las caractersticas de almacenamiento del disco y permiten ahorrar la mayor pane del tiempo invertido en crear y depurar los registros individuales.
6

public static void printKeys(Map<Integer,String> map) ( printnb t wSize * + map. sized + % "); printnb ("Keys: ") ; print I map. keySet 0 ) .* // Generar vm conjunto con las claves

) public static void test(MapInteger,String> map) { print (map.getClass { ) .getSimpleName ()); map. put All (new CountmgMapData (25) ) > // El mapa presents el comportamiento de un con}unco para las claves: map.putAll(new CountingMapData(25) ) ; printKeys(map) ; // Generaci6n de una coleccion con los valores: printnb("Values: "J r print(map.valuesO) f print Imap); print I "map. contains Key (11J : " map. containsKey (11)) ; print["map.get(11) : " map.get(11)); print I"map.contains'Value(\"P0\") : " map.containsValue("FO")); Integer key = map.keySet0.iterator().next()? print ("First key in map: " * key) j return result.toString(;

1452 Piensa en Java map. remove (key) .* printKeys(map); map. clear () ,* print ("map.isEmptyO : M * map.isEmpty0); map.putAl11 new Count1ngMapDa t a(25)) ; // Las operaciones efectuada3 sobre el conjunto modifican el mapa: map.keySet().removeAll(map.keyset()); print("map.isEmpty(): " - map. isEmpty ()) ;

) public static void main(Stringt] args) { test(new HashMap<Integer,String>()i; test(new TreeMap<Integer,String> ( ) ) ; test (new LinkedHashMap<Integer,Strir.g>()) ; test(new IdentityHashMap<Integer.String>()); test(new ConcurrentHa3hMap<Integer,String())? te3t(new WeakHashMapdnteaer,String>());

) ) / Output: HashHap Size = 25, Keys: [15, 8, 23, 16, 7, 22. 9, 21. 6. 1, 14. 24, 4, 19, 11, 18, 3, 12. 17, 2 , 13, 20. 10. 5. 0] return result.toString(;

1453 Piensa en Java Values: [P0, 10. X0, Q0. HO, WO. JO, VO, GO, BO, 00, Y0, E0. TO. L0, SO. DO, MO. P.0, CO, NO, U0, K0, FO. AO) {15s*PQ, 8=10, 2 3=X0, 16=Q0. 7=H0. 22=W0. 9=JO. 21=V0, 6=G0, 1=B0, 14=00, 12=M0. 24=Y0, 4=E0, 19-T0, 11-L0, 18=S0, 3=D0,

17=R0, 2=C0, 13=N0, 20=U0. lO^KO. 5=P0, 0=*A0) map.containsKey(11): true map.get(11): L0 map.containsValue(HF0"): true First key in map: 15 Size * 24, Keys: 4, [8, 23, 16, 7, 22, 9, 21, 6. 1, 14, 24,

19. 11. 18, 3, 12. 17. 2. 13. 20. 10, 5. 0] map.isEmpty(): true map.IsEmpty0: true

///:-

El metodo printKeys( ) muestra como generar una vista de tipo Collection para un mapa. LI inetodo keySet() genera un conjunto con las claves del mapa. Debido a las mejoras en el soporte de iinprestfn introducidas en Java SE5t podcmos impri-

return result.toString(;

1454 Piensa en Java mir los resallados del mtodo values( ). que genera una coleccin con todos los valores del mapa (observe que las claves debe ser univocas, 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 v prueba cada tipo bsico de mapa.

Ejercicio 14: (3)Demuestreque anterior. SortedMap

java.til.Properties funciona en el programa

Si utilizamos un contenedor SortedMap (del cual la nica implementacin disponible es TreeMap). se garantiza que las claves estarn ordenadas, lo que permite proporcionar funcionalidad adicional con los siguientes mtodos de la interfaz SortedMap

return result.toString(;

1455 Piensa en Java Comparator comparator( ) genera el comparador utilizado para este mapa o nuil si se utiliza la ordenacin natural.

T f!rstKey( ): devuelve la clave ms baja.

T last Key( ): devuelve la clave ms alta.

SortedMap su b M a p(fromKey, toKey); genera una vista de este mapa, con las claves comprendidas entre fromKey. incluido, > toKey, excluido

SortedMap head.Map(toKey) genera una vista de este mapa con las claves que sean inferiores a toKey. SortedMap tailMap(fromKev): genera una vista de este mapa con las claves que sean iguales o superiores a fromKey

return result.toString(;

1456 Piensa en Java He aqu un ejemplo similar a SoredSetl)emo.ja\a donde se ilustra este comportamiento adicional de los mapas TreeMap //: containers/SortedMapDemo.java // Lo que se puede hacer con TreeMap. import java.til.*; import net.mindview.util.; import static net.mindview.til.Print. ; public dass SortedMapDemo ( public static void main(StringU args) { TreeMap<Integer,Strino> sortedMap = new TreeMapInteger, String> (new Count 1 ngMapData I l) ) ; print sortedMap); Integer low * sortedMap.firstKey(); Integer high - sortedMap.lastKeyw ; print(low); print(high)r Iterator<Integer> it = sortedMap.keySet().iteratorO; fortint i * 0; i <~ 6; i*+) ( if(i 3) low = it.nextl); if(i 6) high = it.nextOi eise it.next();

) print(low); print(high); print(sortedMap.subMap(low. high)); print (sortedMap.headMap (high)) ; print(sortedMap.taiIMap(low));

) ) /* Output: {O^AO, 1=B0, 2*CO, 3-DO. 4-E0, 5*F0, 6*G0, 7-H0, 8-10, 9=J0}

0 return result.toString(;

3 7 17 Anlisis detallado de los contenedores 1457 9

'///?-

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 submapas y tambin se puede determinar el primer elemento y el 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.prinlln( ) 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 algoritmo LRl: (leasi-recently-useJ) basado en el acceso a los elementos, de modo que los elementos a los que se haya accedido menos y sean, por tanto, candidatos a la eliminacin) aparezcan al principio de la lista. Esto permite crear fcilmente programas que realizan una limpieza peridica con el fin de ahorrar espacio. Me aqu un ejemplo donde se ilustran ambas caractersticas: //: containers/LinkedHashMapDemo.java // Lo que se puede hacer con LinkedHashMap. import java.util; import net.mindview.util.*; import static net.mindview,util.Print. */ public class LlnkedHashMapDemo ( public static void main(String(3 args) ( LinkedHashMapInteger,String> lnkedMap = new LinkedHashMap<Integer, Stnng> ( new CountingMapData(91) ; print(lnkedMapi

3 7 17 Anlisis detallado de los contenedores 1458 // Orden LR: iinkedMap = new LinkedHashMap: Integer, Strina> (16, 0.751*, true) ; 1 inkedMap. putAl 1 < new Count IngMapData (9)) ,* print(IinkedMap}; forint i * 0; i < 6/ i++> // Provocar accesos: 1inkedMap.get(1) print(IinkedMap1/ iinkedMap.get(0); print(1inkedMapJ;

j } / Output:

(6*G0.7=H0,8*10, 1*80.2=C0.3D0,

4=E0.

5F0,

0*A0j

*///:-

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 "0, dicho elemento se desplaza al final de la lista. Almacenamiento y cdigos hash

Los ejemplos del Capitulo 11. Almacenamiento Je 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.

3 7 17 Anlisis detallado de los contenedores 1459 l no de los problemas ms comunes es el que se produce cuando creamos nuestras propias clases para utilizarlas como claves para mapas de tipo liashMap. y tos olvidamos de aadir los elementos necesarios. Por ejemplo, considero un sistema de prediccin meteorolgica basado en el estudio del comportamiento de las marmotas donde se hagan corresponder objetos Groundhog (marmota) con objetos Prediction (prediccin meteorolgica). La tarea parece sencilla: basta con crear las dos clases y usar Groundhog como clave \ Prediction como valor //: containers/Groundhog.java // Parece plausible, pero no funciona como clave para HashMap. public class Groundhog { protected int number; public Groundhog (int n) ( number = n,- ) public String toStringO | return "Groundhog #" number;

) } ///://: containers/predict ion.java // Prediccin del clima mediante marmotas, import java.util-*; public class Prediction ( private static Random rand = new Random(47); private boolean shadow = rand.nextDouble() 0.5; public String toStringO { if(shadow) return "Six more weeks of Winter else return Early Spring I*4;

) } III:II : container3/SpringDetector.java // Qu tiempo har? import java.lang.reflect. import java.til.*; import static net.mindview.util.Print.*; public class SpringDetector ( // Utiliza Groundhog o una clase derivada de Groundhog; public static <T extends Groundhog void detectSpring(CiasscT type) throws Exception (

3 7 17 Anlisis detallado de los contenedores 1460 Constructor<T ghog = type.getConstructor(int.class); MapGroundhog,Prediction map = new HashMapGroundhog,Prediction(); for(int i = 0; i < 10; i++) map.put(ghog.newlnstance(i) , new Prediction0); print("map = H map); Groundhog gh ghog.newlnstance(3) ; print (Looking up prediction for " * gh) ; if (map. containsKey (gh)) print(map.get(gh)); else print("Key not found: " + gh);

i public static void main(String[J args) throws Exception { detectSpring(Groundhog.class);

) ) /* Output; map * (Groundhog #3eEarly Sprinai, Groundhog 47*Barly Springt Groundhog #5=Early Spring!, Grc-undhog 9=Slx more weeks of Winter t, Groundhog #eSix more weeks of Winter,Groundhog *0*5ix more weeks of Winter!, Groundhog #6=Early SpringI,Groundhog #4*5ix mor weeks of winter!, Groundhog #l=Six more weeksof 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 identifieador, de modo que se puede buscar un objeto Prediction en el contenedor HashMap diciendo: "Dame el objeto Prediction asociado con el objeto asociado Groundhog #3" La clase Prediction contiene un valor de tipo boolean que se tnicializa utilizando java.util.random( ) y un mtodo toString ) que interpreta el resultado por nosotras. El mtodo detcctSpring( ) (detectar la primavera) se crea empleando el mecanismo de reflexin para instancias y usa la clase Groundhog o cualquier clase derivada de Groundhog. Esto nos ser til posteriormente. cuando heredemos una nueva clase a partir de Groundhog para resolver el

3 7 17 Anlisis detallado de los contenedores 1461 problema ilustrado en este ejemplo

Rellenamos un objeto llash.Map como objetos Groundhog y sus objetos Prediction asociados. El mapa HashMap se imprime para poder ver que lia sido rellenado. Despus, se utiliza un objeto Groundhog con nmero identifieador igual A

como clave para buscar la prediccin para Groundhog #3 (que. como vemos, debe encontrarse en el mapa.
3

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 clase raz comn Object, y se est utilizando el mtodo ltashCode() de Object para generar el cdigo hash correspondiente a cada objeto. De manera predeterminada, dicho mtodo se limita a utilizar la direccin de su objeto. Por tanto, la primera instancia de Grr>undhog(3) n o produce un cdigo hash igual al cdigo hash de la segunda instancia de Groundhog(3) que hemos tratado de utilizar como clave de hsqueda.

Podramos pensar que lo nico que hace falta es escribir un mtodo de sustitucin apropiado para hashCodef ) Sin embargo. esta solucin seguir sin funcionar hasta que hagamos una cosa ms: sustituir el mtodo equals( ) que tambin forma parte de Object. El mtodo equals( ) es utilizado por HashMap a la hora de determinar si la clave es igual a cualquiera de la claves contenidas en la tabla.

3 7 17 Anlisis detallado de los contenedores 1462 Un mtodo equals( ) apropiado deber satisfacer las siguientes cinco condiciones:

1.

Reflexiva: para cualquier x. x.equals(x) debe devolver true.

Simtrica para cualesquiera \ e y, x.equals(y) debe devolver true si y slo si y.equais(x) devuelve true.
2.

3.

Transitiva: para cualesquiera x. y y z, si x.equals(v) devuelve true e y.eqnals(z) devuelve truc, entonces x.equa)s(z) devolver true.

Coherencia: para cualesquiera x o y, mltiples invocaciones de x.equals(y) debern devolver continuamente true
4.

3 7 17 Anlisis detallado de los contenedores 1463 o continuamente false, en tanlo que no se modifique ninguna informacin utilizada en las comparaciones de igualdad de los objetos. Para cualquier x distinta de nuil, \.equals(null) debe devolver false

5.

De nuevo, el mtodo predeterminado Ohject.equaUt ) 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 HashMap. debemos sustituir tanto hashCode( ) como equals( ). como se muestra en la siguiente solucin al problema de las marmotas: //i ccntainers/Groundhog2.java // Una clase utilizada como clave en un contenedor HashMap // debe sustituir hashCode() y equalsl). puble class Groundhog2 exter.ds Groundhog ( public Groundhog2(int n) { super(n); ) public int hashCode O ( retum number; ) public boolean equals(Object o) ( return o instanceof Groundhog2 (number =*= ((Groundhoq2)o) .number); i ) /////: containers/SpringDetector2.java // Una clave adecuada. public class SpringDetector2 ( public static void main{Stringt) argsi throws Exception ( SprmgDetector.detectSpring(Groundhog2.class);

) } /* Output; map * (Groundhog #2=Early Spring*., Groundhog #4=sSix more weeks of Winter I, Groundhog #9*Six more weeks o Winter I, Groundhog #8=Six more weeks of Winter I, Groundhog #6=Early Spring!, Groundhog #l=Six more weeks of WinterI , Groundhog #3=Early Spring!, Groundhog #7=Early

3 7 17 Anlisis detallado de los contenedores 1464 Spring!, Groundhog #5aEarly Spring!, Groundhog #0=Six more weeks of Winter!} Looking up prediction for Groundhog #3 Early Spring!

///:-

Groundhog2.hash(ode( ) devuelve cl nmero de marmota corno valor de hash. En este ejemplo, el programador es responsable de garantizar que no existan dos marmotas con el mismo nmero identificador. El mtodo hashCode( ) no tiene por que devolver un identificador unvoco (esto es algo que explicaremos con ms detalle ms adelante en el captulo), pero el mtodo equals( ) debe determinar de manera estricta si dos objetos son equivalentes. Aqu. equals( ) se basa en el nmero de marmota, por lo que existen como claves dos objetos Groundhog2 en el mapa HashMap que tengan el mismo nmero de marmota, el mtodo fallar.

Aunque parece que el mtodo equals( ) se limita a comprobar si el argumento es una instancia de Groundhog tusando la palabra clave instanceof. de la que hemos hablado en el C apitulo 14. informacin Je tipos), instaneeof realiza, en realidad, una segunda comprobacin automtica para ver si el objeto es nuil, ya que instanceof devuelve false si el argumento de la izquierda es nuil. Suponiendo que el objeto sea del tipo correcto v distinto de nuil, 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 Hash Set, deber prestar atencin a los

3 7 17 Anlisis detallado de los contenedores 1465 mismos problemas que cuando se utiliza como clave en un mapa de tipo HashMap Funcionamiento de hashCode() 1:1 ejemplo anterior es slo un primer paso en la resolucin correcta del problema. Demuestra que si no sustituimos hashC'odet l y equals( ) para nuestra clave, la estructura de datos hash (HashSet. HashMap. LinkedlIashSet o Linked HashMap) probablemente no podr gestionar nuestra clave apropiadamente. Sin embargo, para obtener una buena solucin del problema, necesitamos comprender que es lo que sucede dentro de la estructura de datos hash.

F.n primer lugar, consideremos cul es la motivacin para utilizar almacenamiento hash: lo que queremos es buscar un objeto empleando otro objeto. Pero tambin podramos hacer esto con un contenedor TreeMap. o incluso podramos implementor nuestro propio contenedor MapPor contraste con una implementation hash, el siguiente ejemplo implemento un mapa

usando una pareja de contenedores incluye una

VrrayList A diferencia de AssociativcArray.java, implementacin

completa de la interfaz Map. para poder disponer del mtodo entrySeM ): //: containers/SlowMap.java // Un mapa implementado con ArrayList. import java.util.; import net.mindview til.*; public class SlowMapcK,V> extends AbstractMap<K,V> ( private List<K>

3 7 17 Anlisis detallado de los contenedores 1466 keys = new ArrayList<K>(j;

private Liat<V> values = new ArrayList<V> ( ) j public V put(K key, V value) { oldValue * get(key); // El valor anterior c null if(keys.contains(key)J { keys.add<key); values.add(value); ) else
V

values.set(keys.indexOf(key), value); return oldValue;

) public V get(Object key) { // La clave es de tipo Object, no K if(tkeys.contains(key)) return null; return values.get(keys.indexOf(key));

) public Set<Map .EntrycK, V entrySetO { Set<Map.Entry<K, V set= new HashSet<Map.Entry<K,v> > ( ) Iterator<K> ki = keys.iterator(); Iterator<V> vi = values.iteratorf); while (ki .hasNext ()) set.add(new MapEntry<K.V>(ki.next() , vi.next())); return set;

3 7 17 Anlisis detallado de los contenedores 1467 } public static void main(StringI] args) ( SlowMap<String,String> m new SiowMapcString,String(); m.putAll(Countries.capitals(15)); System.out.println(m) ; System.out.printIn im.get("BULGARIA")); System.out.printIn(m.entrySet());

) } /* Output: {CAMEROON=Yaounde, CHAD-=N' djamena, CONGO*Brazzaville, CAPE VERDE-Praia, ALGERIAAlgiers, COMOROS=Moroni, CENTRAL AFRICAN REPUBLIC=Bangui, BOTSWANAGaberone, BURUNDI=Bujumbura, BENIN=Porto-Novo, BULGARIASofia, EGYPT=Cairo, ANGOLA*Luanda, BURKINA FASO=Ouagadouaou. DJIBOUTI=Dijibouti) Sofia (CAMEROONYaounde, CHAD=N'djamena, CQNGO=Brazzaville, CAPE VERDE=Praia. ALGERIA=AIgiers, COMOROS=Moroni, CENTRAL AFRICAN REPUBLIC=Bangui. BOTSWANA=Gaberone, BURUNDIBujumbura, BENIN=Porto-Novo, BULGARIA*Sofia, EGYPT*Cairo, ANGOLA=Luar.da, BURKINA FASO=Ouagadougou, DJTBOUTI=Dijibouti]

///:-

3 7 17 Anlisis detallado de los contenedores 1468 El mtodo put( ) simplemente coloca las claves y valores en sendos contenedores Array List relacionados. De acuerdo con la interfaz Map. tiene que devolver la clave anterior o nuil ai no haba clave anterior.

De acuerdo tambin con las especificaciones de Map. get( ) devuelve nuil si la clave no se encuentra en el mapa SiowMap Si la clave existe, se utiliza para buscar el ndice numrico que indica su posicin dentro de la lista de claves keys, y este nmero se emplea como ndice para generar el valor asociado a partir de la lista values Observe que el tipo de key es Object en get(). en lugar de ser del tipo parametrizado k como cabria esperar (y que se utilizaba en AssodativeArrav.java). Esto es a consecuencia de la introduccin de los genricos dentro del lenguaje Java en una etapa tan tarda: si los gencricos hubieran sido una de las caractersticas originales del lenguaje. get( ) podra haber especificado el tipo de su parmetro.

El mtodo Map.entrySet( ) debe generar un conjunto de objetos Map.Entry Sin embargo. Map.Entry es una interfaz que describe una estructura dependiente de la implementacin, por lo que si queremos hacer nuestro propio tipo de mapa, deberemos tambin definir una implementacin de Map.Entry: //: contamers/MapEntry. java // Una definicin simple de Map.Entry para implementaciones t t de ejemplo de mapas, import java.utii.; public class MapEntrycK,V> implements Map.EntrycK,V> ( private K key; private V value; public MapEntry(K key, V value) ( this.key = key; chis.valu = valu;

3 7 17 Anlisis detallado de los contenedores 1469 ) publicK getKeyO ( publicV getValuel publicV setValue(V
V

returo key; ) ( recuxnvalu; vi ( )

result = valu; valu * V retum reeult; i public int hashCode() ( retum (key^=null ? 0 : key. hashCode 11\ value**null ? 0 : value.hashCodeO 1 ;

public boolean equals(Object o) { if(l(o instanceofMapEntryJ) retum falae; MapEntry me = (MapEntry)o; retum (key *= nuil ? me.getKeyO ** nuil : key.equals(me.getKey))) && (valu *= nuil ? me.qecValu O nuil : value.equalsime.getValueO) 1 ;

} public String toStringO ( return key " = " * valu; ) J ///:-

Aqu, una clase muy simple denominada MapEntry almacena y extrae las claves y valores. sta se usa en entrySeK) para generar un conjunto de parejas clave-valor. Observe que entr\Set( ) utiliza un conjunto HashSet para almacenar las parejas, y MapEntry adopta una solucin sencilla consistente en limitarse a utilizar el mtodo hash('ode( ) de la clave key. Aunque esta solucin es muy simple y parece funcionaren la

3 7 17 Anlisis detallado de los contenedores 1470 prueba trivial realizada en SlowMap.main(). no es una implc- tnentacin correcta porque se realiza una copia de las claves y valores. Una implementacin correcta de entrySet() proporcionara una vista del mapa en lugar de una copia, y esta vista permitira la modificacin del mapa original (lo que una copia no permite). El Ejercicio 16 proporciona la oportunidad de corregir este problema.

Observe que el mtodo equals() en MapEntry debe comprobar tanto claves como valores, t! significado del mtodo hashCode( ) se describir en breve.

La representacin del tipo Siring 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.

Ejercicio 15: (I) Repita el Ejercicio 13 utilizando un mapa SlowMap.

3 7 17 Anlisis detallado de los contenedores 1471 Ejercicio 16: (7) Aplique las pruebas de Maps.java a SlowMap para verificar quefunciona. Corrija cualquier cosa de

SlowMap que no funcione correctamente.

Ejercicio 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 difcil producir un nuevo tipo de mapa. Pero, como su nombre en ingls sugiere, SlowMap no es muy rpido, por lo que lo ms normal 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 remedio que usar una simple bsqueda lineal. La bsqueda lineal es la forma ms lenta de encontrar algo.

3 7 17 Anlisis detallado de los contenedores 1472 El almacenamiento hash tiene como nico objetivo la velocidad: este almacenamiento permite realizar las bsquedas rpidamente. Puesto que el cuello de botella se encuentra en la velocidad de bsqueda de las claves, una de las soluciones al problema consiste en mantener las claves ordenadas y luego utilizar Collections.blnarySearch() pai realizar la bsqueda (analizaremos el proceso correspondiente en un ejercicio).

El almacenamiento hash va un paso ms all presuponiendo que en realidad lo nico que queremos hacer es almacenar la clave en algn lugar de forma tal que pueda .ser encontrada rpidamente. La estructura ms rpida en la que se puede almacenar un grupo de elementos es una matriz, asi que eso es lo que se utilizar para representar la informacin de claves {observe que decimos informacin de claves*, y no la> claves mismas). Pero, como una matriz no puede cambiar de tamao, tenemos un problema queremos almacenar un numero indeterminado de valores en el mapa, pero si el nmero de valores est fijado por el tamao de la matriz, cmo podemos solucionar el problema '

La respuesta es que la matriz no almacena las claves. A partir del objeto clave, determinaremos un nmero que servir como ndice dentro de la matriz.. Este numero es el cdigo hash generado por el mtodo hashCode( ) (en trminos de la jerga informtica, este mtodo seria la /'uncin tic hash) definido en Object y. normalmente, sustituido en la clase que estemos utilizando.

Para resolver el problema de la matriz de tamao lijo, 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.

3 7 17 Anlisis detallado de los contenedores 1473 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 una funcin hash perfecto, pero eso es un caso especial 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 valores. Estos valores se exploran de forma lineal utilizando el mtodo equal*( ). 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 hash simple //: containers/SimpleKashMap.java ff Un mapa hash de demostracin, mport java.til.; import net.mindview.utll.; public class SimpleHashMapcK,V> extends AbstractMapcK,V> ( // Seleccione un nmero primo como tamao de la tabla hash, ff para conseguir una distribucin uniforme: static final int size = 397; // No se puede tener una matriz fsica de genricos, // pero si que podemos generalizar para obtener una: SuppressWarningsi"unchecked") LinkedList<MapEntry<K. V N buckets = new LinkedListISIZE); public V put(K key, V valu) {
V

oldValue = nuil;

int index = Math.absikey.hashCode()) % SIZE? if (buckets (ndex) .*= nuil) buckets lindex] = new LinkedList <MapEntry<K,V>>(); LinkedList<MapEntry<K, V bucket buckets tindex]; MapEntry<K, V> pair =- new MapEntrycK,V>(key, valu); boolean found = false; L8tIterator<MapEntry<K,V>> it = bucket.listlteratorO; while(it.hasNext()) ( MapEntry^K,V> iPair t.nextO; if(iPair.getKey(> .equals(key)) ( oldValue = iPair.getValue();

3 7 17 Anlisis detallado de los contenedores 1474 El caso de una [Uncin hash pertccLi est implcmentado en bis estructura KuuniMup y KnumSc de Java SE5, porque las cnuineracior.es definen un nmero fijo de valores Consulte el Capitulo 19. Tipos vnunu;rado\ it-sec(pair); // Sustituir antiguo por nuevo found = true; break;

} if{1 found) buckets[index].ada(pair); return oldValue;

} public V get(Object key) ( int index = Math.aba(key.haahCode()) % SIZE; if (buckets[index1 == null) return null; for(MapEntrycK,V> iPair : buckets[index]) if(iPair.getKey().equals(key)) return iPair.getValue(); return null;

3 7 17 Anlisis detallado de los contenedores 1475 ) public Set<Map.Entry<K,V>> entrySett) ( Set<Map.Entry<K, V set= new HashSet<Map.Entry<K, V () ; for (LinkedList<MapEntrycK, V bucket : buckets) ( if(bucket == null) continue; for(MapEntrycK,V> rapair : bucket) set.addimpair); i return set;

) public static void main(String[] argsj ( SimpleHashMapcString,String> m = new SimpleHashMapcStrmg, String () ; m.putAll(Countries.capitals(25)J; System.out.println(m); System.out.printlnim.get("ERITREA"1); System.out.println tm.entrySet()); l ) /* Output: (CAMEROON*Yaounde, CONGO=Brazzaville, CHAD=N*djamena, COTE D'TVOIR (TVORY COAST)=Yamoussoukro, CENTRAL AFRICAN REPUBLlC=3angui, GUINEA-Conakry, BOTSWANA=Gaberone, BISSAU^Bissau. EGYPT-Cairo, ANGOLALuanda, BURKINA FASO*Ouagadougou. ERITREA=Asmara. THE GAMBIA=Ban}ul. KENYA*Nairobi, GABON=Libreville, CAPE VERDE=Praia, ALGERIA*Algiers, COMOROS~Moroni, EQUATORIAL GUINEA Malabo, BURUNDI=Bujumbura. 3ENIN*Porto-Novo. BULGARIA=Sofia, GHANAAccra, DJIBOUTI*Oijibouti, ETHIOPIA-Addis Ababa) Asmara [CAMEROQN=Yaounde, CONGO=Brazzaville,

3 7 17 Anlisis detallado de los contenedores 1476 CHAD=N'djamena, COTE B'lVOIR (IVORY COAST)Yamoussoukro, CENTRAL AFRICAN REPUBLIC=Eangui, GUINEA=Conakry, BOTSWANA=Gaberone, BISSAU=Bissau, EGYPT=Cairo, ANGOLA=Luanda. BURKINA FASO=Ouagadougou, ERITREA=Asraara. THE GAMBIA=Banjul, KENYA=Nairobi, GABON=Libreville, CAPE VERDE=Praia. ALGERIA=Algiers, COMOROS=Moroni, EQUATORIAL GUlNEA=Malabo, BURUNDI Bujumbura, BENINPorto-Novo, BULGARIA=Soia, GHANA=Accra, DJIBOUTI*Dijibouti, ETHIOPIA=Addis AbabaJ *///:Como las posiciones de una tahla hash sc denominan a menudo segmentos (buckets), la matriz quc representa a la labia sc dcnomina buckets. Para conscgmr una distribution uniforme el numero de segmentos es. normaLmente, uu numero

primo.M Observe que se traa de una matriz de lipo LinkedList, que se encarga automticamente de las colisiones. Cada nuevo elemento se aade simplemente al final de la lista 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. Aqui resulta til hacer una generalizacin de dicha matriz, para evitar 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 tamao de la matriz. Si dicha posicin es nuil, quiere decir que no hay ningn elemento cuyo cdigo hash 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 normal consiste en examinar la lista para ver si hay duplicados y. en caso de que los haya, el valor antiguo se almacena en oldValue y el valor nuevo sustituye 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.

3 7 17 Anlisis detallado de los contenedores 1477 El mtodo get( ) calcula el ndice para la matriz buckets de la misma forma que put( ) (esto es importante con el fin de garantizar que terminemos en la misma posicin). Si existe un objeto LinkedList. se le explora en busca de una correspondencia.

Observe que no pretendemos decir que esta implementacin est optimizada para obtener el mejor rendimiento posible: slo tratamos de ilustrar las operaciones realizadas por un mapa hash. Si examinamos el cdigo ftientc de java.util.HashMap. podremos ver la implementacin realmente optimizada. Asimismo, por simplicidad. SmpleHashMap usa la misma tcnica para entrySet( ) que ya se utiliz en SIowMap. la cual es demasiado simplista y no funciona para los mapas de propsito general.

Ejercicio 19: (I) Repita el Ejercicio 13 utilizando un contenedor SimplellashMap

Ejercicio 20: (3) Modifique SimplellashMap para que informe de las colisiones pruebe el sistema aadiendo el

mismo conjunto de datos dos veces, con el fin de que se produzcan colisiones.

3 7 17 Anlisis detallado de los contenedores 1478 Ejercicio 21: (2) Modifique SimplellashMap para que informe del nmero de "consultas necesarias cuando se produ

cen colisiones En otras palabras, informe del nmero de llamadas a next( ) que hay que realizar en los iteradores que recorren las listas enlazadas. Ejercicio 22: (4) Implemente los mtodos clear( ) y remove{ ) para Simplellash.Map

Ejercicio 23: (3) Implemente el resto de la interfa? Map para SmpleHashMap

Ejercicio 24: (5) Siguiendo el ejemplo de SlmpleHashMap.java. cree \ pruebe un contenedor SimpleHashSet

Ejercicio 25: (6) En lugar de usar un iterador Ltstlterator para cada segmento, modifique MapEntry para que sea una

3 7 17 Anlisis detallado de los contenedores 1479 nica lista enlazada autoeontenida (cada objeto MapEntry debe tener un enlace directo al siguiente objeto MapEntry). Modifique el resto del cdigo de SimplellashMap.java para que funcione correctamente esta solucin Sustitucin de hashCode()

Ahora que comprendemos cmo funciona el almacenamiento hash. tiene ms sentido escribir nuestro pnjpio 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 factor de carga (describiremos este trmino ms adelante). Por tanto, el valor generado por nuestro mtodo hashCnde( ) se procesar ulteriormente para crear el ndice de la matriz de segmentos (en SmpleHashMap, el clculo consiste simplemente en hacer una operacin de mdulo segn el tamao de la matriz de segmentos).

F.l factor ms importante a la hora de crear un mtodo hasliCodc( ) es que. independientemente de cundo se invoque hastiC OIIM ), este debe producir el mismo valor para un objeto concreto cada ve/ que sea invocada. Si tuviramos un objeto que produjera un valor hashC'ode ) al insertarlo con puf( ) en un contenedor HashMap y otro valor distinto al extraer

3 7 17 Anlisis detallado de los contenedores 1480 locon get(). no podramos nunca extraer los objetos. Por tanto, si nuestro mtodo liashC ode() depende de datos del objeto que varen, es necesario informar al usuario de que al modificar los datos se generara una clave diferente, porque se tendr un cdigo hashCode( i diferente

Ademas, norma/mente no conviene generar un valor hnsh(ode( ) que est basado en informacin de los objetos de carcter distintivo, en concreto, el valor de Ibis genera un cdigo hashC'odet ) no muy bueno, porque entonces es imposible generar una nueva clave idntica a la que se ha asado para insertar con put( ) la pareja original clave-valor. ste era el problema que ya detectamos en SpringDetector.java. porque la implementacin predeterminada de liasfiC ode( ) utilizo precisamente la direccin del objeto. Por tanto, lo que conviene es utilizar informacin del objeto que le identifique de alguna mnnera significativa.

Podemos ver un buen ejemplo en la clase String. Las cadenas de caracteres tienen la caracterstica especial de que si un programa dispone de varios objetos String que contienen secuencias de caracteres idnticas, entonces lodos eso* objetos String se corresponden con la misma zona de memoria. Por tanto, tiene bastante sentido que el cdigo ImshCodef) producido por do> instancias diferentes de la cadena de caracteres helio" deba ser idntico. Podemos ver esto en el siguiente programa: //: containers/StnngHashCode.-java public class StringHashCode l public static void mainiScring[] argsi { StringL] helios = "Helio Helio".split(" System.out.println(helios 10).hashCode()}; System.out.orintln(helios l].hashCodei ) ) :

3 7 17 Anlisis detallado de los contenedores 1481 ) /* Output: (Sample) 69605650 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 efectivo, debe ser rpido > adems significativo: en otras palabras, debe generar un valor basado en el contenido del objeto. Recuerde que este valor no tiene por qu ser univoco (lo que nos interesa es la velocidad no la unicidad) pero entre hashCodet ) y equalst ). la identidad del objeto debe ser completamente especificada.

Puesto que el cdigo generado por liashCndc( ) 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 valores. Si los valores tienden a estar agrupados, entonces el contenedor HashMup o HashSct estar ms cargado en unas reas que en otras, y no ser tan rpido como podra serlo si se dispusiera de una funcin hush uniformemente distribuida.

3 7 17 Anlisis detallado de los contenedores 1482 En el libro Effective Java1Xi Progromming Language Cuide (Addison-Weslcy. 2001). Joshua Bloch nos da una receta bsica para generar un mtodo hashC ide( ) aceptable:

Almacene algn valor constante distinto de cero, como por ejemplo 17, en una variable int denominada result
1.

2.

Por cada campo significativo f del objeto (es decir, cada campo que sea tenido en cuenta por el mtodo equals( )). calcule un cdigo hash c de tipo int correspondiente a ese campo.

3.

Combine el cdigo hash recin calculado: result = 37 * result + e;

4.

Devuelva result.

3 7 17 Anlisis detallado de los contenedores 1483 Examne el cdigo hashCode( ) resultante y asegrese de que instancias iguales tengan cdigos hash iguales.
5.

lie aqui un ejemplo construido sobre estas directrices: J~ container3/CountedString.java // Creacin de un mtodo hashCode i adecuado, import j ava. u 1 11. *; import static net .mindvievi.util .Print ; public class CountedStrng { private static List<String> created * new ArrayLlst<String>O i private String s; private int id 0; public CountedString(String str) ( s * str; created.add is); // id es el nmero total de instancias // de esta cadena que CountedStrng est usando: for(String s2 : created) ifls2.equals is)) id-f-f i

) public String toStrlngf) ( return "String; " -* s * id; " H hashCode I) : M hashCode {I ; id

3 7 17 Anlisis detallado de los contenedores 1484 ) public int hashCode IJ ( // La tcnica simple; // return s.hashCodef) * id; f ( Utilizacin e la receta de Joshua Bloch: int result = 17; result = 37 result s.hashCode<); result * 37 result + Id; return result;

) public boolean equals(Object o) ( return o instanceot CountedStrng u u s.equals { I (CountedStrng I o) .s) Li* id ==* ({CountedString)o) .id; i public static void maintString{] args) { Map<CountedString, Integer map new 9ashMap<CourtedString,Integer(); CountedStrngTI cs - new CountedStringT5]; for (int i = 0; i < cs. length; i+<*-> ( csiij new CountedStrng("hi") ; map.put(cs[i] , il ; // Autobox int -> Integer

3 7 17 Anlisis detallado de los contenedores 1485

printimap); for(CountedString cstring : cs) { print ("Look mg up " + cstring); print(map.qet(cstringlJ;

) ) /* Output: (Sample) (String: hi id: 4 hashCodeI): 146450*3, String: hi id: 1 hashCodeO: 146447*0, String: hi id: 3 hashCode 0 : 146*149=2,

String: hi id: 5 hashCode(): 146451=4. String: hi id: 2 hashCodeO: Looking up String:

3 7 17 Anlisis detallado de los contenedores 1486 0 Looking up String: 1 Looking up String: 2 Looking up String: 3 Looking up String: 4

*///:-

CountedString incluye un objeio Siring y un id que representa el nmero de objetos CountedString que contienen un objeto String idntico. El recuento se realiza en el constructor, iterando a travs del contenedor Array List 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. habra correspondencias duplicadas para valores diferentes

3 7 17 Anlisis detallado de los contenedores 1487 En ni a in (). se crean varios objetos CountedString utilizando el mismo objeto String, con el tin de demostrar que los duplicados crean valores distintos, debido al campo id que se utiliza como recuento. El ejemplo muestra el contenedor llashMap. 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 definida en el Capitulo 14, Informacin de tipos. La clase Individual se usaba en dicho capitulo, pero habamos retardado su definicin hasta este captulo con el fin de que el lector comprendiera la implementacin: //: typemfo/pets/Individual .java package typeinfo.pets; public class Individual implements ComparableIndividual> ( private static long counter 0; private final long id = counter*--*-; private String name; public Individual(String name) ( this.name * name; ) // 'name' es opcional: public Individalo (} public String toStringO ( return getClass().getSimpleName( ) + (name = null ? "" : " " + name);

} public long idO { return id; J public boolean equals(Object o) ( return o instanceof Individual && id ((Individual)o) . id; i public int hashCodeO ( int result 17; if(name I= null)

3 7 17 Anlisis detallado de los contenedores 1488

result = 37 * result + name.hashCode(J; result = 37 * result (int)id; return result;

) public int compareToI Individual ara) { // Comparar primero por el nombre de la clase: String first = getClass{).getSimpleName( ) ; String argFirst = arg.gerClass).getSimpleName(); int firstCompare = first.compareTo(argFirst); if(firstCompare i* 0) return firstCompare; if(name [= null && arg.name != nuil) ( int secondCompare * name.compareTo{arg.name); if (secondCompare != 0) return secondCompare;

) return (arg.id < id ? -1 ; (arg.id = id ? 0 : 1)); )

} ///:-

3 7 17 Anlisis detallado de los contenedores 1489 til mtodo compare I'o( ) tiene una jerarqua de comparaciones, de modo que producir una secuencia que estar ordenada primero por el tipo real, y luego por name si es que existe y finalmente por orden de creacin. He aqui un ejemplo que demuestra cmo funciona: //: containers/IndividualTest.java import holding.MapOfList; import typeinfo.pets.*; import j ava.ut i1.; public class IndividualTest { public static void main(String[] args) { Set<Individual pecs = new TreeSet<Individual>(); for(List<? extends Pet> lp : MapOfList.petPeople.values(> for (Pet p : lp) pets.add Id) ; i System.out.printin(pets I;

) /* 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 Fuzzyl *///:-

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 hashCodet ) y equals( ) para una nueva clase puede resultar complicado. Puede encontrar herramientas que le ayudarn a hacerlo en el proyecto Jakarta Commons de Apache, en jakartaMpache.org/commons. en el subdirectory "lang (este proyecto dispone tambin de muchas otras bibliotecas potencialmcnte tiles, y parece ser la respuesta de la comunidad Java a la comunidad www.hoost.org de C++).

3 7 17 Anlisis detallado de los contenedores 1490 Ejercicio 26: (2) Aada un campo char a CountedString que tambin se imcialice en el constructor, y modifique Ios-

mtodos hashCode ) y cquats( ) para incluir el valor de este campo char

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/mindvlew/util/Tuple.java para convertirla en una clase de propsito general aadien

3 7 17 Anlisis detallado de los contenedores 1491 do ha$hCode( ). equals(). e implementando Comparable para cada tipo de Tupie. Seleccin de una implementacin

A estas alturas, el lector debera entender. que aunque slo hay cuatro tipos de contenedores (Map. List. Sel y Querie) hay ms de una implementacin de cada interfaz Si necesitamos utilizar la funcionalidad ofrecida por una interfaz concreta, (.cmo piulemos decidir qu implementacin emplear?

Cada una de las diferentes implementaeiones tiene sus propias caractersticas, ventajas e inconvenientes. Pur ejemplo, puede ver en la imagen incluida al principio de este capitulo que la "ventaja" de Ifashtabie, Vector y Stack cs que son clases heredadas. de modo que el cdigo antiguo no dejara de funcionar (aunque lo mejor es que no utilice esos contenedores en los programas nuevos).

Los diferentes tipo* de Qucuc en la biblioteca Java se diferencian slo en la forma en que aceptan y devuelven los valores (veremos la importancia de esto en el Capitulo 21. Concitnvndav

La distincin entre unos contenedores y otros usualmente reside en el almacenamiento que se utiliza como respaldo, cs decir, en las estructuras de dalos que implementan fsicamente la interfaz deseada. Por ejemplo, como ArrayLM y LinkedList implcmcntan la interfaz List, las operaciones bus ti as de List son iguales independientemente de cul utilicemos. Sin embarco ArravList utiliza como respaldo una matriz mientras

3 7 17 Anlisis detallado de los contenedores 1492 que LinkedList se implemcnta en la forma usual ce las listas doblemente enlazadas, en forma de objetos individuales cada uno de los cuales contiene tanto los datos como sendas referencias a los elementos anterior y siguiente de la lista. Debido a esto, si queremos hacer muchas inserciones y eliminaciones en mitad de una lista, la eleccin apropiada ser LinkedList (LinkedList dispone tambin de funcionalidad adicional que est definida en AbstractSequentialLst). Si no es el caso. ArravList suele ser ms rpido.

Como ejemplo adicional, podemos implemcntar un conjunto mediante los contenedores TreeSei. llashSet o LinkedlIashSct.'* Cada uno de estos contenedores tiene diferentes comportamientos; llasliSel es para uso normal y proporciona una gran velocidad en lus busquedas. LinkedlIashSet mantiene las parejas en orden de insercin y IreeSei est respaldado por un contenedor TreeMap y est disertado para disponer de un conjunto constantemente ordenado. La imple- mentacin se elige basndose en el comportamiento que se necesite.

u ocasiones, las diferentes implementaciones de un contenedor concreto tendrn una serie de operaciones en comn, pero ci rendimiento de esas operaciones ser diferente. En este caso, la seleccin entre unas impementaciones 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 lus maneras de evaluar lus diferencias entre las distintas impementaciones de contenedores es mediante una prueba de rendimiento
I

Un marco de trabajo para pruebas de rendimiento

Para evitar la duplicacin de cdigo y para que las pruebas sean coherentes, he incluido la funcionalidad bsica del proceso de pruebas en un mareo de trabajo que define la parte principal del programa. Ll codigo 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 aadir y eliminar fcilmente distintos tipos de pruebas

3 7 17 Anlisis detallado de los contenedores 1493 Se trata de otro ejemplo del patrn de diseo basado en el Mtodo de plantillas. Aunque se Sigue la solucin tpica del mtodo de plantillas consistente en sustituir el mtodo Tesl.lest( ) para cada prueba concreta, en este caso la parte fundamental del cdigo (que no cambia) se encuentra en una clase separada Tester.10 El tipo de contenedor que estamos probando es el parmetro genrico C: //: containers/Test.java // Marco de Lrabakc para realizar pruebas temporizadas de contenedores. public abstracc class Test<C> ( Strina narae; public TestString ame) { thifi.name ame; } v (> como f-lnumSrt o ( opyOnVNHteArmyStt. que son casos especiales. Aunque somos conscientes de que puede haber muchas oros impementaciones adicionales especiali/odos de diversas interfaces de contenedores, en e>ta seccin estamos tratando de examinar slo tos casos mas generales l" Krzysztof Sobolewski me ayud a disear los genrico< de este ejemplo. // Sustituir este mtodo para las diferentes pruebas. ; Devuelve el numere real de repeticiones da la prueba, abstract me test(C container. TescPararn tpi r

) ///;-

Gula objeto Test almacena el nombre de dicha prueba. Cuando se invoca el metodo test( ). hay que pagarle el contenedor que luiy que probar junio con un elemento de transferencia de datos" o mensajero" que almacene los diversos parmetros correspondientes a dicha pnieba concreta Los parmetros incluyen slze. que indica el nmero de elementos ile! contenedor > loops, que controla el nmero de iteraciones ile la prueba Estos parmetro* pueden o no utilizarle en todas las pruebas

3 7 17 Anlisis detallado de los contenedores 1494 Para eada contenedor se realizar una secuencia de llamadas a lesti ). cada una con un objeto TestParam diferente, de modo que TestParam tambin contiene mtodos array ) esttico que hacen que resulte fcil crear matrices de objetos Test Para ni La primera versin de arrayi ) torna una lista de argumentos variables que contiene valores size y loop aller- nantes. mientras que la segunda versin loma el mismo tipo de lista, aunque con valores almacenados dentro de objetos Strini, de esta forma, puede utilizarse para analizar argumentos de la linea de comandos //: containers TestPaxam.j ava // Un *objeto de transferencia de datos'* * public class TestParam ( public final int eize; public final int loops? public TestParam(int sdxe, int loops' [ thio.size = size; tftis.iooca = locoa;

) U Ctear una matriz de TestParam a partir de una secuencia d* varargs: public static TestPararr.tl array int... values ( int sise = values.length/2j TestParamI] result * new TestParam[ s i ?ej/ int n 0; fori int i *= 0; i c ai2 e i-+J re3Ult li) * new TestParar. values [n+- . values fu-*-1 , retum result/ // Convertir una matriz de tipo String a una matriz TestParam: public atatic TestParamU array (Strmg H values' ( int[J valu <= new int [values. lengthj rorlint 1 = 9; i c vals. length; i*-i vals[ij Integer.dcodvalestil ) ; retum array (valsi i i ///i*

Para utilizai el marco de trabajo, lo que lutcemos es pasar el contenedor que hay que probar junto con una

3 7 17 Anlisis detallado de los contenedores 1495 lista de objetos Test a un mtodo TeMcr.nm( ) (se trata de mtodos genricos de utilidad sobrecargados, que reducen la cantidad de texto que hay que escribir para utilizarlos), Tc5ter.run( ) invoca el constructor sobrecargado apropiado y luego llama a timedTesl( ). que ejecuta para ese contenedor cada una de las pruebas de la lista timcdTcsl( ) repite cada prueba para cada uno de los objetos Test Para m contenidos en paruniIJst. Puesto que parami Jsl se inictaliza a partii de la mairi/ esttica defu ultPar ans, pdenlos cambiar la lista parami.ist para todas las pruebas resaignando defaullParams. o bien podemos modificar la lista parami.ist para una prueba determinada, pasndole para esa prueba una lista paraml.ist personalizada. I l ; containers/Tester.java // Aplica objetos Test a listas de diferentes contenedores, import j ava. utrii. * ; public class Tester<C> { public static int fieldwidth = 6; public static TestParamU defaultParams= TestParam.arrayi 10, 5000. 100. 5000, 1000. 5000. 10000, 500)/ // Sustituir esto para modificar la inicializacin anterior a la prueba: protected C initialize(int size) { return container; ) protected C container; private String headline private List<Test<C>> tests; private static String atringField() { return -f fieldWidth "s";

) private static String numberField() { return "%" 4- fieldWidth -* "d*.

> private static int BizeWidth = 5; private static String sizeField **%" sizeWidth * MsM; private TestParamU paramList -* defaultParams; public Testeree container, List<Test<C tests) ( this.container = container; this.tests = tests; if(container ! null) i headline * container.getClass).getSimpleName();

3 7 17 Anlisis detallado de los contenedores 1496 public Tester(C container, List<Test<C>> tests, TestParamf] paramList) { this(container, tests); this.paramList ^ paramList;

) public void setHeadline (String newHeadlme) { headline * newHeadline;

) // Mtodos genricos de utilidad: public static <C> void run(C cntnr, List<Test<C>> tests)) new Tester<C>(cntnr, tests).timedTest(l;

) public static <C> void run(C cntnr, List<Test<C tests, TestParamU paramList) ( new Tester<C>(cntnr, tests. paramList).timedTest);

3 7 17 Anlisis detallado de los contenedores 1497 private void displayHeader() ( f f Calcular anchura y rellenar con 1-: int width = fieldWidth testa.size O sizeWidth; int dashLength * width headline.length() - 1; StringBuilder head = new StringBuilder(width); for (int i = 0; i < dashLength/2; i+4) head.append(* -'); head.append(* '); head.append(headline); head.append t' 1J; for (int i = 0; i < dashLength/2; i-*---) head.append(*-*)/ System.out.println(head); // Imprimir cabeceras de columnas: System.out.format(sizeField, "size"); for(Test test j tests) System.out.format(stringField(), test.name) j System.out.println()?

) // Ejecutar las pruebas para este contenedor: public void timedTest() ( displayHeader( ) for(TestParam param : paramList) {System.out.format(sizeField, param.sizel ,* or(Test<C> test : tests) ( C kontainer * initializeIparam.sizeJ; long start = System.nanoTime i ' ; // Invocar el mtodo sustituido: int reps * test.test(kontainer, param); long duration = System.nanoTime) - start; long timePerRep * duration / reps; // Nanosegunrios System.out.formatinumberFieidO. timePerRep) ?

3 7 17 Anlisis detallado de los contenedores 1498 ) System.out.println(I;

) ///:-

Los mtodos stringField() y numberFieki( ) producen cadenas de formateo para imprimir los resultados. La anchura estndar de formateo puede variarse modificando el valor esttico fleldWidth. El mtodo displa> Header() da formato e imprime la informacin de cabecera para cada prueba.

3 7 17 Anlisis detallado de los contenedores 1499 Si necesitamos realizar una inicializacin especial, sustituimos el mtodo initialize( ). Esto produce un objeto contenedor micializado con el tamao apropiado: podemos modificar el objeto contenedor existente crear uno nuevo. Como puede

ver en test) el resultado se captura en sustituir el miembro

una referencia local denominada kontainer. que alma

cenado container por un contenedor inicializado completamente diferente.

El valor de retomo de cada mtodo Test.test ) debe ser el nmero de operaciones realizado por dicha prueba, lo que se utiliza para calcular el nmero de nanosegundos requerido por cada operacin. Hay que tener en cuenta que System.nanoTinie() produce normalmente valores con una granularidad mayor que uno (y esta granularidad vanar de una mquina a otra y de un sistema operativo a otro), lo que produce un cierto error estadstico en los resultados

Los resultados pueden variar de una mquina a otra, estas pruebas slo pretenden comparar el rendimiento de los diferentes contenedores. Seleccin entre listas

3 7 17 Anlisis detallado de los contenedores 1500 He aqu una prueba de rendimiento para las operaciones de List ms esenciales. Por comparacin, tambin se muestran las operaciones de Queue ms imporrantes. Se crean dos listas separadas de pruebas, con el fin de probar cada clase de contenedor. En este caso, las operaciones de Queue slo se aplican a listas de tipo LinkedList. //: containero/LiotPerformancc.java // Ilustra las diferencias de rendimiento en las listas. // {Args: 100 500) Pequeo para que las pruebas sean corta3 import java.til.*; import net.mindview.util. public class ListPerformance | static Random rand = new RandoraO; static int reps = 1000; static List<Test-cList<Integer> tests = new ArrayList<Test<List<Integer>>>(); static List<Test<LinkedList<Integer>>> qTests new ArrayList<Test<LinkeLlst<Integer>>>(} ; static ( tests .ada(new Test<List<.Inteaer>> ("add") ( int test{List<Integer> list. TestParam tp) ( int loops = tp.loops; int listSize * tp.size; for(int i = 0; i < loops; i-*} { list.clear(); for(int j = 0; j < listSize; j-M-) i ist.add(j) ;return loops * llotSize;

)); tests .add (new Test<List<IntegerC'get") I

3 7 17 Anlisis detallado de los contenedores 1501 int test List < Int e-ger list, TestParam tp) { int loops tp.loops reps; int listSize =st.BizeH/ for(int i = 0; i < loops; L*-) list.get\ rand.nextInt(listSize) ) j return loops;

})t

test9.add(new Test<List<Integerf uset'*) ( int test lList<Integer> list, TestParam tp) { int loops = tp.loops reps; int listSize = list.size(); for (int i s D; i < loops; I-MO j list.set(rand.nextlnt(listSize), 47) ; return loops;

)). tests.add(r.ew Test<Llst<Integer>(*'iteradd") { int test{List<Integer list, TestParam tp) ( final int LOOPS a 1000000; int half = list.size() / 2 ; ListIterator<Integer* it - list.listlterator(half); foriint i = 0; i < LOOPS; 1++) it.add(47); return LOOPS;

)>. tests.add(new Test<Llat<integer("insert") ( int

3 7 17 Anlisis detallado de los contenedores 1502 test(List<Integer> list, TestParam tp) { int loops = tp.loops; for (int i ^ 0; i < loops; Ut-) list.add(5, 47); // Minimizar el coste del acceso aleatorio return loops;

* tests .add (new Test<List< Integer I*"remove") { int test (Lstdnteaer list, TestParam tp) ( int loops = tp.loops; int size = tp.size; for (int i * 0; i < loops; i+-*-J ( list.clear'); list.add&li(new CountinglntegerList\ a i z e ) ) while(list.size(J 5) i list.remove(5); // Minimizar el coste del acceso aleatorio

return loops * size;

) Hr // Pruebas ae comportamiento de las colas: qTests.add(new Test<LinkedList<Inteaer>("aadFirst") ( int test(LinkedListInteger list, TestParam tp) ( int loops = tp.loops? int size tp.size; forint i = 0; i < loops; i**-) { list.clear(); for lint j * o i j site; list .adFirst. 47) /

3 7 17 Anlisis detallado de los contenedores 1503 ) return loops size;

}) qTests.add(new Tes t<linkedList< Integer >>("addLa3t) ( int test(LinkedList<Integer list. TestParam tp.i | int loops tp.loops; int size - tp.slze; for (int 1 = 0 ; i < loops; i**-) { list .clear () .* for(int j * 0; j c size; list.addLast(471 ;

} ) R; qTests add I new Test<LmkedList<Integert "rmFirst") { int test LLinkedList<Integer> list, TestParam tp) ( int loopn = tp.loops; int 6ize = tp.size; forfint i * 0; i < loops; 14+) ( list clearU; list.addAll(new CountinglntegerList(size) ) j while(list.size() > D) list.removeFirst(); return loops * size;

3 7 17 Anlisis detallado de los contenedores 1504 ) return loops size;

lif qTests.add(new Test<LinkedList<Integer>>("rmLast") { int test <LmkedList<Integer> list, TestParam tp) { int loops * tp.loops; int size = tp.sizef forfint i = 0; i < loops; n-+) | list.clear/ } ; 11st.addAll(new CountinglntegerListisize)); while(list.size() > 01 list.removeLast(); I return loops * si2 e;

}>;

3 7 17 Anlisis detallado de los contenedores 1505 ) static class ListTester extends Tester<List<Integer>> ( public ListTester(List<Integer> container, List-cTest<List<Integer> tests) ( super(container, tests); I // Rellenar con el tamao apropiado antes ae cada prueba: ^Override protected List<Integer> initialize(int size){ container.clear ) ; container.addAll(new CountingtntegerList(size)); return container;

) // Mtodo de utilidad: public static void run(ListInteger> list ,List<Test<List<Integer tests) { new ListTester(list, tests).timedTest();

) public static void main(String[3 args) ( if(args.length > 0) Tester.defaultParams = TestParam.array Iargs); // Slo se pueden hacer estas dos pruebas en una matriz: Tester<List<Integer arrayTest = new Tester<List<lnteger>>(null, teats.subList(1, 31){

3 7 17 Anlisis detallado de los contenedores 1506 // Esto se invocar antes de cada prueba. // Produce una lista de tamao fijo respaldada por una matriz: Override protected List<Integer> initialize Iint size) ( Integerf] ia = Generated.array(Integer.class, new CountingGenerator.Integer(), size); return Arrays asListlia);

j )>

arrayTest.setHeadlineI"Array as List"); arrayTest,timedTest( ) ; Tester.defaultParams= TestParam.array( 10. 5000. 100. 5000. 1000. 1000. 10000. 200); if(args.length 0) Tester.defaultParams TestParam.array(args); ListTester.run(new ArrayList<Integer0, teste); ListTester.run I new LinkedListcInteger(), tests); ListTester.run(new Vector<Integer0 , tests); Tester.fieldWidth = 12; Tester<LinkedList<Integer>> qTest * new TesterLinkedList<Integer ( new LinkedListInteger(), qTests); qTest. setHeadline ("Queue tests ) ; qTest.timedTestI);

} } / Output: (Sample) -- Array as List

3 7 17 Anlisis detallado de los contenedores 1507

Cada una de las pruebas requiere una consideracin cuidadosa para garantizar que estemos produciendo resultados significativos. Por ejemplo, la prueba *add borra la lista y luego la rellena de acuerdo con el tamao de lisia especificado. La llamada clcar( ) para borrar forma parte, por tanto, de la prueba y puede tener un impacto sobre el tiempo de ejecucin, especialmente para las pruebas mas 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 clear()) fuera del bucle de cronometrado.

Observe que, para cada prueba, es necesario calcular con precisin el numero de operaciones que tienen lugar y devolver dicho valor desde test(), para que la temporizacin sea correcta

Las pruebas "get y "sef * 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 A rr ay List, estos accesos son rpidos y muy coherentes independientemente del tamao de la lista, mientras que para un contenedor LinkedList. el tiempo de acceso aumenta de manera muy significativa para las listas de mayor tamao. Claramente, las listas enlazadas 110 representan una buena eleccin si vamos a realizar muchos accesos aleatorios

3 7 17 Anlisis detallado de los contenedores 1508 La prueba ileradd" utiliza un aerador en mitad de la lista para insertar nuevos elementos. Para un contenedor ArrayList. esta operacin resulta costosa a medida que el tamao de la lista crece, pero para otro de tipo LinkedList es relativamente barata y ese coste es constante independientemente del tamao. F.sto 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 LinkcdList, 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 utilizar uno de los extremos de la lista. Un contenedor LinkedList trata los extremos de la lista de manera especial, lo que permite 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 implcmenta- ciones de la lista. Realizando las inserciones y eliminaciones en la posicin 5, el coste del acceso aleatorio debera ser despreciable 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 LinkedLisf es bastante bajo y que no varia con el tamao de la lista, pero con un contenedor ArrayList. las inserciones son especia/mente caras y el coste se incrementa con el tamao de la lista.

A partir de los datos correspondientes n Queue. podemos ver la rapidez con que LinkedList permite insertar y eliminar elementos de los extremos de la lista, lo cual es el comportamiento ptimo para una cola.

Normalmente, podemos limitamos a invocar Tester.run( ). pasando el contenedor y la lista tests. Aqu, sin embargo, debemos sustituir el mtodo initiulize( ) 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 iniciaiizacin empleando CountinglntegerList. El mtodo de utilidad run( ) tambin se sustituye.

3 7 17 Anlisis detallado de los contenedores 1509 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 ntain(), se crea un objeto Test especial utilizando una clase interna annima. El mtodo initialize( ) se sustituye para crear un nuevo objeto cada vez que se le invoca (ignorando el objeto container almacenado, de modo que nuil es el argumento container para este constructor Tester). El nuevo objeto se crea utilizando Generated.arrayf ) (que se ha definido en el Capitulo 16, Matrices) y Arrays.asLisl( ). Slo dos de las pruebas pueden realizarse en este caso, porque no se pueden insertar o eliminar elementos cuando se usa una lista respaldada por una matriz, asi que se emplea el mtodo List.subList() para seleccionar las pruebas deseadas de entre la lista tests.

Para las operaciones get( ) y sct( ) de acceso aleatorio, una lista respaldada por una m3iriz es ligeramente ms rpida que \rra\ List pero esas mismas operaciones son muchsimo ms caras para LlnkedList porque este contenedor no est diseado para operaciones de acceso aleatorio.

lis necesario evitar la utilizacin de Vector: solo se ha incluido en la biblioteca para soporte del cdigo heredado ta nica razn de que funcione en este programa es porque fue adaptado para ser de tipo List por razones de compatibilidad con sucesivas versiones).

La mejor solucin consiste probablemente en elegir ArrayList como contenedor predeterminado > cambiar .* UnkedUst si hace falta su funcionalidad adicional o si descubre problemas de rendimiento 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 lista respaldada por una matriz (como las que produce Arruvs.asList 11. o en caso necesario, una verdadera matriz.

3 7 17 Anlisis detallado de los contenedores 1510 CopvOnW riieArrayList es una implemeutacin especial de LLst que se utiliza en programacin concurrente y de la que hablaremos en el Captulo 21. Coticummcio.

Ejercicio 29: (2) Modifique LlstPerformance.java para que las listas almacenen objetos String en lugar de Inteecr.

Utilice el objeto Cencrator del Capitulo 16, Matrices* para crear valores de prueba. Ejercicio 30: (3) Compare el rendimiento de ColIfctions.sorM ) en ArrayList \ en LlnkedlJsr

Ejercicio 31: (5) Cree un contenedor que cncapsule una matriz de objetos String y que slo permita aadir y extraer

cadenas de caracteres, de modo que no surjan 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 matriz. En main( ). compare el rendimiento de este contenedor con el de ArraylJst<String>

3 7 17 Anlisis detallado de los contenedores 1511 Ejercicio 32: (2) Repita el ejercicio anterior para un contenedor de int y compare el rendimiento con el de Arm>List

<lnteger>. En la comparacin de rendimiento, incluya el proceso de incrementar cada objeto en el contenedor.

Ejercicio 33: (5) Cree una lista FastTraversalLinkcdLixt que utilice internamente un contenedor LinkedList para

conseguir inserciones y eliminaciones rpidas de elementos y un contenedor ArrayList para realizar recorridos rpidos de los elementos y operaciones get( ) rpidas. Pruebe la solucin modificando List Performanee.java Peligros asociados a las micropruebas de rendimiento

A la hora de escribir las denominadas micrupruebas 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 \ hay que tener tambin en cuenta que algunas de las tecnologas llotSpot de Java slo entran en accin cuando un programa se ha estado ejecutando durante un determinado perodo de tiempo (tambin es importante tener esto en cuenta para los

3 7 17 Anlisis detallado de los contenedores 1512 programas de corta duracin).

Los resultados sern distintos, dependiendo de la computadora y de la maquina JVM que estemos utilizando, por lo que conviene que ejecute estas pruebas por s mismo con el fin de verificar que los resultados son similares a los que se muestran en csie libro. No deben preocuparle tanto los valores absolutos como las comparaciones de rendimiento entre un tipo de contenedor y otro.

Asimismo, una herramienta de perfiloJ puede realizar un mejor anlisis de rendimiento del que nosotros podemos llevar a cabo. Java incluye un perfilador (consulte el suplemento en h t t p / Mindilcyv.net Books.Better./crvo) y tambin hay perfiladores de otros fabricantes, tanto gratuitos/cdigo abierto como comerciales.

Un ejemplo relacionado es el que afecta a Math.i andomi ). Este mtodo produce un valor comprendido entre cero y uno. pero eso incluye o excluye el valor I? En lenguaje matemtico, se trata del intervalo (O.l). o (0.1], o (0.1] o [0.1 V? (el corchete indica "inclusin, mientras el parntesis indica no inclusin). Un programa de pruebas podri proporcionar la respuesta: //: contalnera/RandomBounds.java // Permite Math.randoml) generar 0.0 y 1.0? // (RunfiyHand) lmport static net .tnindview.til. Print ; public claas RandomBounds ( stat;c voi usagei i { printi"Usage:"); print " \tRandomSounde Icw-r " I print fn\tRandomBounds upper"); Syetem.exit CU ;

3 7 17 Anlisis detallado de los contenedores 1513 ) pubLic snatic void mainlStringH args) { If (aros .lengtli l= 1J usage0; ftargs(0].equais(Hlower M> ( whie(Math.random{) 5= 0.0) ; // continuar intentndole print{"Produced Q.fl!");

) else if (args [0] .equais i "upper") ( while (Math.random() != 1.0) ; II Continuar intentndolo print{"Producen 1.0iHJ;

) else i usage{)

i tir.i-

3 7 17 Anlisis detallado de los contenedores 1514 Para ejecutar el programa, hay que escribir la linea de comandos: java RandomBounds icwer

O java RandomBounds upper

En ambos casos, estamos obligados a interrumpir 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 tipo de experimentos pueden resultar engaosos. Si tenemos en cuenta que la cantidad de nmeros fraccionarios comprendidos entre 0 y 1 son unas 262 'raccto- nes diferentes, la probabilidad de obtener cualquiera de esos dos valores experimentalmente podra exceder el tiempo de vida de una computadora, o incluso del propio experimentador. En realidad. 0.0 si que est incluido en el rango de salida de Math.randnnU ). U. dicho en jerga matemtica, el intervalo es [0,1). Por tanto, debemos tener cuidado a la hora de realizar nuestros experimentos con el fin de aseguramos de que entendemos sus limitaciones. Seleccin de un tipo de conjunto

Dependiendo del comportamiento que deseemos, podemos elegir entre TreeSet. HashSet o LinkedHashSet. El siguiente programa de pruebas proporciona una indicacin de los compromisos de rendimiento que afectan a estas implemcntacione> //: containerE/SetPerfonnance.java
II

Ilustra las diferencias de rendimiento entre conjunto.

// {Aras: 100 5000) pequeo para que las pruebas sean cortas lmport java.til-*; public class SetPerformance (

3 7 17 Anlisis detallado de los contenedores 1515 static List<Test<Set<Integer>>> tests = new ArrayList<Test<Set<Integer>>>il/ static ( tests .add i new Test<Set<Integer | "add" i ( int testlSet<Integer> set. TestParam tpl ( int loops = tp.loops; int size tp.32e;for lint i = 0; i < loops; i++) { sec.clear(); foriint j 0; j size; j+ + ) set.add(j) ;

) return loops * size;

)>; tests.add(new Test<Set<Integer("contains*) | int test(SetInteger set, TestParam tp) { int loops = tp.loops; int span = tp.size * 2; for (int i 0; i < loops; i++) for (int j = 0; j < span; j+f) set.contains Ij); return loops * span;

3 7 17 Anlisis detallado de los contenedores 1516 )J ; tests.add{new Test<Set<Integer("iterate"l { int test(Set<Integer> set. TestParam tp) ( int loops tp.loops * 10; for (int i = 0; i < loops; i--*-J { Iterator<Integer> it * set.iterator(); while(it.hasNext()J it.next()i

) return loops * set.sized;

})!

) public static void main(String 13 args) ( if(args.length > 0) Tester.defaultParams = TestParam. array (args) ; Tester.fieldWidth = 10;

3 7 17 Anlisis detallado de los contenedores 1517 Tester.run(new TreeSet<Inteaer>i), HashSet<Integer(), tests); LinkedHashSetInteger>(), tests) tests); Tester.run(new Tester.run(new

) / Output:(Sample) --- ------ TreeSet -------

------- LinkedHashSet

17 Anlisis detallado de los contenedores 1518

El rendimiento de HashSet generalmente es superior al de TreeSet, pero especialmente a la hora de aadir elementos y de buscarlos, que son las dos operaciones mas importantes TreeSet existe porque mantiene sus elementos en orden, de modo que slo se suele utilizar cuando hace falta un contenedor Set ordenado Debido a la estructura interna necesaria para soportar las ordenaciones y debido a que la iteracin suele ser una operacin muy comn, la iteracin suele resultar ms rpida con TreeSet que con HashSet

Observe que LinkedllashSet es ms caro respecto a las inserciones que HashSet: esto se debe al coste adicional de mantener la lista enlazada adems del contenedor hash.

Ejercicio 34: (1) Modifique SetPerformance.java para que los conjuntos almacenen objetos String en lugar de obje

tos Integer. Utilice el objeto Generator del Capitulo 16, Matrices para crear valores de prueba. return loops map.size()/

17 Anlisis detallado de los contenedores 1519 Seleccin de un tipo de mapa

Este programa nos proporciona una indicacin de los compromisos de rendimiento en las distintas implementaciones de Map: //: containers/Mapperformance.java // Ilustra la diferencias de rendimiento entre mapas. // (Args: 100 5000} pequeo para mantener corto el ciempo de prueba import java.util.*; public class MapPerformance ( static List<Test<Map<Integer,Integer>>> tests = new ArrayList<Test<Map<Integer,Integer(); static ( tests.add(new Test<Map<Integer,Integer:("put") ( int test iMapInteger,Integer map, TestParam tp) { int loops = tp.loops; int size to.size; for (int i * 0; i < loops; -m-J ( map.clear(); for (int j = 0; j < size; j +4*) map.put(j , j);

} return looos * size; return loops map.size()/

17 Anlisis detallado de los contenedores 1520 ) latest s. add (new Test<Map<Integer,Integer>> ("geeH) { int test(Map<Integer.Integer map. TestParam tp) ( int loops = tp.loops; int span = tp.size 2; forint i 0; i c loops; i+f) for (int j 0; j < span; j+-0 map.get(j); return loops * span;

it* tests.add(new Test<Map<Integer.Integer("i terate") ( int test(Map<Integer,Integer map, TestParam tp) { int loops = tp.loops * 10; for(int i * 0; i < loops; i +) { Iterator it = map.entrySet 0. iterator (J ; while(it.hasNext()J it.next(); return loops map.size()/

17 Anlisis detallado de los contenedores 1521 } t

));

] public static void m* in (St ringargs) ( iffargo.length > 0) Tester.defaultParams = TestParam.array(argsj; Tester .run'new TreeMapctnteger, Integer1) . tests) .* Tester.run(new KashMapcInteger,Integer>(). teats// Tester . run i new LinfcedHaahMspdnteger, Integer > IJ , tests I ; Tester.run( new Ident ityHash4ap<Integer, Integer (), testsM Tester.run(new WeakHaahMap<Integer,Integer()f tests I; Tester.run(new Kashtable<Integer, Integer 0 , tests!; i } /* Outputs iSample)

return loops map.size()/

17 Anlisis detallado de los contenedores 1522

l*as inserciones en todas las implementaciones de Map excepto en IdcniitvIlashMap 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 mucha ms frecuencia de la que los insertamos.

El rendimiento de Habitable es aproximadamente igual al de HashMap. Puesto que liashMap pretende sustituir a Hashtablc. y utiliza por tanto la misma estructura subyacente de almacenamiento y el mismo mecanismo de bsqueda (de return loops map.size()/

17 Anlisis detallado de los contenedores 1523 loque hablaremos posteriormente), no resulta demasiado sorprendente que ten^a un rendimiento mejor.

TVceMap es generalmente mas lento que HashMap. Al igual que sucede con TreeSet. TrceMap es una forma de crear una lista ordenada. El comportamiento de un rbol es tal que sus elementos siempre estn en orden, sin que sea necesario ordenarlos especialmente Una vez rellenado un contenedor TreeMap. podemos invocar keySet( ) para obtener una vista de tipo Sel de las claves y luego podemos llamar .1 t<iArray( ) para generar una matriz de dichas claves Podemos a continuacin emplear un meiodo esttico Arrays.hinarySeareh( ) para localizar tapidamente objetos en esa matriz ordenada Por supuesto. esto slo tiene sentido si el comportamiento de un contenedor HashMap no es aceptable, ya que HashMap est diseado para poder localizar rpidamente las claves. Asimismo, podemos crear rpidamente un contenedor HashMap a partir de otro de tipo TreeMup inediunte una nica creacin de objeto o una llamada a pul All ), En resumen, cuando estemos utilizando un mapa nuestra primera eleccin deber ser HashMap. \ slo deberamos recurrir a TrceMap si lo que necesitamos es un mapa que est constantemente ordenado.

L.inki'dlIashMup tiende a ser ms lento que HashMap para las inserciones, porque mantiene la lista enlazada (con el fin de preservar el orden de insercin), adems de mantener la estructura de datos hosh Sin embargo, debido a esta lisia, la nenie ion es ms rpida.

Identityllash.Map nene un rendimiento distinto porque utiliza = en lugar de equals( ) para hacer las comparaciones Weak HashMap se describe ms adelante en el captulo return loops map.size()/

17 Anlisis detallado de los contenedores 1524 Ejercicio 35: (1) Mixtifique 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 pareja y modifique get() para utilizar Collections.hinarySearch( ) con el fin de buscar la clave. Compare el rendimiento de la nueva versin con el return loops map.size()/

17 Anlisis detallado de los contenedores 1525 de las anteriores.

Ejercicio 37: (2)ModifiqueSimpleHashMap para ArrayList en lugar de LinkedUst. Modifique

utilizar contenedores

MapPerformance.java para comparar el rendimiento de las dos implcmentacioncs Factores de rendimiento que afectan a HashMap

Resulta posible optimizar manualmente un contenedor HashMap para incrementar su rendimiento de cara a una aplicacin concreta. Pero para poder comprender las cuestiones de rendimiento a la hora de optimizar un contenedor HashMap. nos hace falta algo de terminologa

Capacidad el nmero de segmentos de la tabla. return loops map.size()/

17 Anlisis detallado de los contenedores 1526 Capacidad inicial el nmero de segmentos en el momento de crear la tabla. HashMap y HashSet tienen constructores que permiten especificar la capacidad inicial.

Tamao: el nmero de entradas que hay actualmente en la tabla.

Factor de carga Tamao capacidad. Un factor de carga de 0 representa una tabla vacia, 0.5 es una tabla medio llena, etc. Una tabla ligeramente cargada tendr menos colisiones y por tanto resulta ptima de cara a las inserciones y bsquedas (aunque ralentizar el proceso de recorrer el contenedor con un iterador) HashMap y HashSet tienen constructores que permiten especificar el factor de carga, lo que significa que cuando se alcanza este factor de carga, el contenedor incrementara automticamente la capacidad (el nmero de segmentos), por el procedimiento 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 predeterminado utilizado por HashMap es 0.75 (no efecta una redistribucin hasta que la tabla est llena en sus tres cuartas partes). Este parece ser un buen compromiso entre los costes de tiempo y de espacio de almacenamiento. 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( )).

return loops map.size()/

17 Anlisis detallado de los contenedores 1527 Si sabemos de antemano que vamos a almacenar numerosas entradas en un contenedor HashMap. crearlo con una capacidad inicial suficientemente grande evitara el gasto adicional del cambio de tamao automtico." *' Bu un memaje privado. Jo>huu Hluch escribi Creo que hcinm cometido un error al incluir detalle* de implcmentacin (como el fccior de carga y el tamaflo de la tabhn haxh) en musirs API El cliente deberla, quiz, decimos el tamao mximo petado de una coleccin y nosotros deberamos actuar

Ejercicio 38: (3) Examine la clase HashMap de la documentacin del JDK Cree un contenedor HashMap. rellnelo

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 HashMap 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 SimplellashMap que se invoque cuando el factor de carga exce

return loops map.size()/

17 Anlisis detallado de los contenedores 1528 da de 0.75. Durante el cambio automtico de tamao, duplique el nmero de segmentos y luego busque el primer nmero primo superior a esc nmero, con el fin de determinar el nuevo nmero de segmentos. Utilidades

Hay diversas utilidades autnomas para contenedores, expresadas como mtodos estticos dentro de la clase java.utiI.Collections. Ya hemos visto algunas de ellas, como addAllt ). revcrseOrder( ) y binarySearch( ) He aqu las otras (las utilidades de tipo synchronized y unmodiflablc se cubrirn en las secciones siguientes). En esta tabla, se usan genricos alli donde son relevantes:

chcckedCollection(Collcctio Produce una vista dinmicamente segura con respecto ? tipos n<T>, de Collection, o de un subtipo especfico de

return loops map.size()/

17 Anlisis detallado de los contenedores 1529

1 '(continuacin) a partir de ah Los dientes pueden, fcilmente, generar mus problemas que beneficios al seleccionar los valores de estos parametri Como ejemplo exagerado, considera el valor cupaciivlncmncnt de Vector Nadie debera configurar este valor y no deberamos haberlo incluido Si se le asignu un valor distinto de cero, el coste asintotico de una secuencia de adiciones pasa de lineal a cuadratn. En otras palabras, el rendimiento se viene abajo Con el paso del tiempo, cada vez tenemos nub experiencia con este tipo de cosa Si examinan ldrntityHashMap, vena* vjuc tu* dispone de ningn parmetro de optimizacin de bajo nivel'*

return loops map.size()/

17 Anlisis detallado de los contenedores 1530 Observe que min( ) y max( ) funcionan con objetos Collection no con listas, por lo que no es necesario preocuparse acerca de si la coleccin ya est ordenada o no (como ya hemos mencionado anteriormente, si que es necesario ordenar con sort() una lista o una matriz antes de realizar una bsqueda binaria con binarySearch( )).

He aqu un ejemplo que muestra el uso bsico de la mayora de las utilidades de la tabla anterior //: containers/utilities,java // Ejemplo simple de las utilidades para colecciones, import java.Util ; import static net .mindview. util. Print.. ; public class Utilities f static List<String> list = Arrays.asLst "one Two three Pour five six one".split(" ")); public static void main(StringU args) ( print(list); print(M* list' disjoint (Four)?: M t Collecticns.dis joint(list. Collections.singletonList("Four")I ); print<"max; " Collections .max (list i print("min: " + Collections.minllist); print ("max w/ comparator: " -* Collections .max(list ,String. CASE_INSHNSITIVE_0P.DE?.) > ; print(min w/ comparator: " - Callections.min(list, String. CASE_INSENITIVE_GRDER) ; List<String> sblist * Arrays. asList l"Four five six *. split I" ")l; print < *indexOfSubList: Collections.indexOfSubldtHist, sublist1I; print"last return loops map.size()/

17 Anlisis detallado de los contenedores 1531 IndexOfSubList: " +Collections.lastlndexOfSubListilist, subliat)); Collections.replaceAll<list, "one, *Yon)j print("replaceAll: H -* list)/ Collections.reverse (list}; print\ Mreverse: " f lisr); Collections.rotate{list, 3)^ print i "rotate: * list); List<String> source * Arrays.asListi"in the matrix".split(" ")); Col lections.copy(list, source; print i "copy: " * list); Collections.swap(list, 0, list.size) - 1)/ print("swap: " 4 list); Collections.shuffle(list, new Random(47)); print ("shuffled: ' f list); Collections.fill(list, "pop"j; print I "fill: " list); print("frequency of pop*: M Collections.frequency(list, "pop")); List<String> dups = Collections.nCopies(3, "snap"); print("dups: " dups); print Clist disjoint dups'?: " + Collections.disjoint(list. dups)); // Obtencin de un objeto Enumeration al estile antiguo Enumeration<Strmg> e = Collections. enumeration (dups) ; Vector<String> v * new Vector<String(); while < e.hasMoreElements()) v.addElement(e.nextElement{)); // Conversin de un vector de estilo antiguo // en una lista a travs de un objeto Enumeration: ArrayList<String> arrayList = Collections.list(v.elements()); print("arrayList: " arrayList);

return loops map.size()/

17 Anlisis detallado de los contenedores 1532 } ) / Output: [one, Two, three, Four, five, six, one] list' disjoint (Four)?; false max: three min: Four max w/ comparator: Two min w/ comparator: five indexOfSubList: 3 lastIndexOfSubList: 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] fill: [pop, pop, pop, pop, pop, pop, pop] frequency of 'pop': 7dupa: lansp, snap, snap] 'Use' disjoint 'dups'?; true arrayList: [snap. snap, snapI ///:-

La salida explica el comportamiento de cada mtodo de utilidad. Observe la diferencia en min( ) y ma\( ) con el objeto String.CASE_INSENSITlVE_ORDER C omparator debido a la utilizacin de maysculas y minsculas. Ordenaciones y bsquedas en las listas

return loops map.size()/

17 Anlisis detallado de los contenedores 1533 Las utilidades para realizar ordenaciones y bsquedas en las listas 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 aqui un ejemplo que emplea la lista de datos list de Utilities, ja va: //; containers/ListSortSearch. java // Ordenacin y bsqueda sn listas con las utilidades de Collections, import java.util.*; import static net.mindview.util.Print; public class ListSortSearch ( public static void malnlStringlJ args) ( List<String> list = new ArrayList<Strinq>(Utilities.list); list.addAll(Utilities.listJ; print (list) ; Collections.shuffle(list, new Random{47)); print(Shuffled; " + list); // Utilizar Listlterator para eliminar los ltimos elementos: LlstIterator<String> it = list.listlterator(10); while(it.hasNext()) { it.next(>; it.remove(); i print ("Trimmed: * -r list); Collections.sort(list)j print("Sorted: f- list); String key * list.get(7); int index = Collections.binarySearchtlist. key); print (Location of " * key + * is M + index + ", list get(" * index " list.get(index)) ; Collect ions.sort(list, String.CASE_INSENSITIVEORDER); return loops map.size()/ * "

17 Anlisis detallado de los contenedores 1534 print{"Case-insensitive sorted; " list); key = list.get(7); index * Collections.binarySearch(list, key, String.CASE_INSENSITTVE_QRDER); print ("Location of " * key + is " index + ", list, get (" (index)) ; findex " list .get

*)"

) } / Output: [one. Two. three. Four, five, six, one, one. Two, three. Four, five, six, one] Shuffled: (Four, five, one, one, Two, six, six, three, three, five. Four, Two, one, one] 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: six, [five. five. Four, one, one,

return loops map.size()/

17 Anlisis detallado de los contenedores 1535

six. three, three. Two] three Location of three is 7, list.get(7) *///:-

Al igual que cuando se realizan bsquedas y ordenaciones con matrices, si ordenamos utilizando un objeto Comparator. debemos efectuar la bsqueda con binarySearch( ) usando el mismo objeto Comparator

Este programa tambin ilustra el mtodo shuffle* ) de Collections, que aleatoriza el orden de una lista. Se crea un objeto Listlterator en una posicin concreta de la lista aleatorizada y se utiliza para eliminar los elementos comprendidos entre dicha posicin y el final de la lista.

Ejercicio 40: (5) Cree una clase que contenga dos objetos String y haga que sea de upo Comparable de modo que la

return loops map.size()/

17 Anlisis detallado de los contenedores 1536 comparacin slo tenga en cuenta el primer objeto String. Rellene una matriz y un contenedor ArrayList con objetos de esa clase, utilizando el generador RandoniGenerator Demuestre que la ordenacin funciona apropiadamente Ahora defina un objeto C omparator que slo tenga en cuenta el secundo objeto String y demuestre que la ordenacin funciona correctamente. Asimismo, realice una bsqueda binaria utilizando ese objeto Comparator

Ejercicio 41: (3)Modifiquela clase del ejercicio anterior para que funcione con contenedores HashSet y como clave

en contenedores HashMap.

Ejercicio 42: (2)ModifiqueelEjercicio alfabtica.

40 para que se utilice una ordenacin

Creacin de colecciones o mapas no modificables

return loops map.size()/

17 Anlisis detallado de los contenedores 1537 A menudo, resulta conveniente crear una versin de slo lectura de una coleccin o mapa. La clase Collections permite hacer esto, pasando el contenedor original a un mtodo que devuelve una versin de slo lectura. Hay diversas variantes de este mtodo, para colecciones (si no se puede tratar un objeto Collection como si fuera de tipo ms especifico), listas, conjuntos y mapas. I:l siguiente ejemplo muestra la forma de construir versiones de slo lectura de cada uno de estos contenedores: //: contamers/ReadOnly. java // Utilizacin de los mtodos Collections.unmodifiable. import java.util.#; import net.mindview.til.*; import static p.et .mindview.til. Print. ; puble class ReadOnly ( static Coliection<String> daca *new ArrayList<Strmg> ICountries.ames (6)); public static vod raain(StringH args { Collection<String> c = Collections.unmodifiableColl ection I new ArrayList<String>(data!)\ print(c); // Se puede leer //l c.add("one"l; // No se puede modificar List<String> a Collections.unmodifiable List new ArrayList<Strmg>{data)) ; ListIterator<String> lit = a.listlteratorl]; print(lit.next()); // Se puede leer // lit.add(uone"); // No se puede modificar 5et<String> s * Collections.unmodifiableSetI new HashSet<Strmg> (data) ) ; prints)j // Se puede leer //i s.add("one"J; // No se puede modificar t Para un contenedor SortedSet: Set<String> ss = Collections.unmodifiableSortedSet return loops map.size()/

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1538 (Map<String,String> m = Collections.unmodifiableMap( new HashMap<String.String>(Countries.capitals(6))1; print(m); // Se puede leer //! m.put("Ralph", "Howdy!"); // Para un contenedor SortedMap: Map<String, Strir.g> sm = Collections. urunodif iableSortedMap ( new TreeMap<String, String:(Countries.capitals(6)));

} ) /* Output: IALGERIA. ANGOLA. BENIN, BOTSWANA. BULGARIA. BURKINA FASO] ALGERIA [BULGARIA, BURKINA FASO, BOTSWANA. BENTN, ANGOLA, ALGERIA] (BULGARIA=Sofia, BURKINA FASO=Ouagadougou, BOTSWANA=Gaberone, BENIN=Porto-Novo, ANGOLA*Luanda, ALGERIA=Alaiers} *///:-

La invocacin del mtodo unmodifiable" (no modificable) para un tipo concreto no hace que se generen advertencias en tiempo de compilacin, pero tina ve/ que la transformacin se ha producido, cualquier llamada a un mtodo que modifique el contenido de un contenedor concreto generar una excepcin DnsupportedOperationExceptun.

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1539 En cada caso, debe rellenar el contenedor con datos significativos antes de hacerlo de slo lectura. Una vez cargado, lo mejor es sustituir la referencia existente por la referencia generada por la llamada al mtodo unmodifiable. De esa forma, no corremos el riesgo de tratar de modificar accidentalmente el contenido una vez que hemos dicho que no es modificable Por otro lado, esta herramienta tambin permite mantener el contenedor modificable como privado dentro de una clase y devolver una referencia de slo lectura a dicho contenedor desde una llamada a mtodo. De este modo, podemos cambiar el contenedor 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 mnltihebra. un tema ms complicado del que no hablaremos hasta el Captulo 21. Concurrencia. Aqu, nos limitaremos a resallar que la clase Collections contiene una forma de sincronizar automticamente un contenedor completo. La sintaxis es similar a la de los mtodos unmodi- ilable": //: containers/Synchroni2 ation.java // Utilizacin de loe mtodos Collections.synchronized. mport java.til.*; public class Synchromzation ( publlc static void mainString [] args) ( Collection<String> c * Collections.synchroni zedCo!lection( new ArrayList<String>(J); List<String> list = Collections .synchronizedLisr. ( new ArrayList<String>{)); Set<String> s = Collections.synchronizedSett new HashSet<String>())/ Set<String> ss Collections.synchronizedSortedSet( new TreeSet<String>() ); Map<String,String> m Collections.synchronizedMap( new HashMap<String,String>()); Map<String,Stnng> sm = ) Collections.synchronizedSortedMap( r.ew TreeMap<String,Strng> {)) ;

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1540 ) ///*-

Lo mejor es pasar inmediatamente el nuevo contenedor a travs del apropiado mtodo synchrumzed'\ 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 contenedor 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 faifa rpala que examina 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 Concun-enlModificalionException. A eso se refiere el trmino de fallo rpido no trata de detectar un problema ms adelante utilizando un algoritmo ms complejo.

Resulta bastante fcil ver el mecanismo de fallo rpido en accin: lo nico que hace falta es crear un iterador y luego aadir algo u la coleccin a la que el iterador est apuntando, como el ejemplo siguiente: //: containers/rail Fas t. java // Ilustracin del comportamiento del fallo rpido", import j aya.til.; public class FailFast {

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1541 public static void main(String[] argsl { Ccllection<String> c = new ArrayList<String>(1; Iterator<String> it * c.iteratorO; c.addCAn object"},* try { String s = it.nextO; | catchtConcurrentModlficationException e) ( System.out.printlnfe);

) } / utput: java.til.ConcurrentModificationExcepti on *///:-

La excepcin se produce porque se ha incluido algo en el contenedor despus de que se haya adquirido el iterador para esc contenedor. La posibilidad de que dos panes del programa puedan modificar el mismo contenedor hace que acabemos en un csiado incierto, por lo que la excepcin nos notifica que es necesario modificar el cdigo; en este caso, habr que adquirir el iterador despus de haber aadido todos los elementos al contenedor. Los contenedores CuncurrentHashMap. CopyOnWriteArrayList y CapyOnWrtcArraySet utilizan tcnicas que impiden que se genere la excepcin Concurrent.VIodificationException.

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1542 Almacenamiento de referencias

La biblioteca java.lang.ref contiene un conjunto de clases que aumentan la flexibilidad del mecanismo 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 PhantoniRefercnce. Cada una de ellas proporciona un nivel distinto de mdireccin para el depurador de memoria si el objeto en cuestin slo es alcanzable a travs de uno de estos objetos Reference

Si un objeto es alcanzabler quiere decir que en algn lugar del programa puede encontrarse el objeto Esto puede querer decir que disponemos de una referencia normal 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 intermedios. Si un objeto es alcanzable. el depurador de memoria no puede ignorarlo, porque sigue siendo utilizado por el programa. Si el objeto no es alcanzable. no hay ninguna forma de que nuestro programa lo use. as que resulta seguro depurar dicho objeto de la memoria.

tildamos objetos Referente cuando queremos continuar almacenando una referencia al objeto (c\ decir, queremos poder alcanzar el objeto), pero tambin queremos permitir que el depurador de memoria libere el objeto De este modo, tenemos una forma de utilizar el objeto, pero si est a punto de agotarse la memoria, dejamos que ese objeto sea eliminado.
l

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1543 Para hacer esto, utilizamos un objeto Referente como intermedian o (proxy) entre nosotros y la referencia normal. Adems, no debe haber referencias normales al objeto (es decir, referencias que no estn envueltas dentro de objetos de tipo Referente). Si el depurador de memoria descubre que un objeto es alcanzabie a travs de una referencia normal, no podr liberar dicho objeto.

Ordenando los distintos tipos de referencias Soft Referente. Weak Referente y PhantumReference, cada uno de ellos es "mas dbil que el anterior y se corresponde con un nivel distinto de alcanzabilidad. Las referencias blandas (soft referen- <v,v) son para implementar caches sensibles a la memoria. Las referencias dbiles {weak refeivnees) sirven para implemen- lar 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 {phantvm nfervnces) sirven para planificar acciones de limpieza pre-mortem de una manera ms flexible de lo que puede conseguirse con el mecanismo de finalizacin de Java

Con SoftRefereuce y WeakReferente, tenemos la opcin de situar esas referencias en una cola de referencias de upo ReferenceQueue (que se utiliza pata acciones de limpieza pre-mortem), pero una referencia PhantomRcferentc tiene obligatoriamente que construirse dentro de una cola ReferenceQueue. He aqu un ejemplo simple: //: containers/References. java // Ejemplo de objetos Reference mport java.lang.re.; import java.Util. *,class VeryBig ( prvate static final int SIZE = 10000; prvate longil ia new long[SIZE] ; prvate String ident; pubiic VeryBig(String id> { ident = id; ) public String toStringO { retum ident; ) protected void finalizeO ( System.out.println"Fnalizing * * ident); i

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1544 i public class References { prvate static ReferenceQueue<VeryBig> rq - new ReferenceQueue<VeryBig> ( ) ; pubiic static void checkOueuet) { Reference<? extends VeryBig> inq = rq.polll); if(ina I* nuil) i System.out.println(MIn queue; * Inq.getO);

public static void main(String[J args) { int size = 10 ; // O bien elegir el tamao a travs de la linea de comandos: if (args.length > 0 ) size = new Integer1 args[0J) ; LinkedList<SoftReference<VeryBig>> sa new LinkedList<SoftReference<VeryBig>>O ; forint i 0 ; i < size; i-*4 ) ( sa.ada 'new SoftReference<VeryBig>( new VeryBig("Soft " + i) r rq) I ; System.out.println("Just created: " + sa.getLastO); checkQueue();

) LinkedList<WeakReference< VeryBig wa = new LinkedList<WeakReerence<VeryBig (} forint i * 0; i < size.wa.add(new WeakReference<VeryBig> new VeryEig("Weak n -> i) , rq) ) ; System.out.println("Just created: M wa getLast () ) ; checkQueue(); SoftP.eference<VeryBig> s * new SoftReference<VeryBig> new VeryBig ("Soft" i - ,WeaXReference<veryBig> w (

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1545 new WeaJtReference<VeryBig> new VeryBig ("Weak") ) ; System.ge O; LinkedList<PhantomReference<VeryBig>> pa = new LinkedList<PhantomReference<VeryBig>>(J; forint i * 0? i < cize; i*+l ( pa.add(new PhantomReference<VeryBig>i new VeryBig("Phantom " + i), rq)); System.out.println{"Just 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 utiliza get( )). Tambin podemos ver que el objeto ReferenccQueue siempre genera un objeto Reference que contiene un objeto nuil. Para usar ste, herede de una clase Reference concreta y aada otros mtodos ms tiles a la nueva clase. WeakHashMap

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1546 La biblioteca de contenedores dispone de un lipo especial de mapa para almacenar referencias dcbiles WeakHashMap. lista clase est diseada para facilitar la creacin de mapas cannicos En dicho tipo de mapas se ahorra espacio de almacenamiento creando una instancia de cada valor concreto. Cuando el programa necesita dicho valor, busca el objeto existente en el inapa y lo utiliza (en lugar de crear uno partiendo de cero). El mapa puede construir los valores como parte de su proceso de inicializacin. pero lo ms probable es que los valores se construyan a medida que son necesarios.

Puesto que se trata de una tcnica de ahorro de espacio de almacenamiento, resulta bastante til que WeakHashMap permita al depurador de memoria limpiar automticamente las claves y los valores. No hace falta hacer nada especial con las claves y valores que se incluyan en el contenedor WeakHashMap: dichas claves y valores son envueltos automticamente por el mapa en referencias de tipo Weak Reference. Lo que hace que quede permitida la tarea de limpieza del depurador de memoria es que la clave ya no est siendo utilizada, como se ilustra en el siguiente ejemplo //: containers/CanonicalMappin g.java // Ilustra el contenedor WeakHashMap. import java.til.; class Eiement { prvate String ident; public Eiement(String id) ( ident = id; ) public String toStringO { return ident,* ) public int hashCodeO ( return ident.hashCode(); ) public boolean equals(Object r) ( return r instar.ceof Eiement && ident.equals f((Eiement)r).ident);

) protected void final izeO ( System.out.println{"Finalizing " + getClass O .aecSimoleName O " " + ident);

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1547 )

) clas9 Key extends Element { oublic Key(String id) ( super(id)? |

) class Valu extens Blemenc ( public Valu(String id) ( super(id); )

) public class CannicalMappmg I public static void main(String[3 args) { int size - 1000; // O bien elegir tamao a travs de la lnea de comandos: iffargs.iength > 0) size = new Integer(argsfD] ); Key[] keys = new Keytsize] : WeakHashMapcKey,Valu> map = new WeakKashMap<Key, Value> {) for(int i 0; i < size; -M-) { Key k = new Key(Integer.toString(i)); Valu v new Valu (Integer. toString

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1548 (i) ) ,* if ( * 3 == 0) keya[i] * k; // Guardar como referencias "reales" map.puttk, v);

) System.ac );

} ) /* lExecute to see output) *///-

La clase Key debe tener sendos mtodos hashCode( ) y i*quals( ), puesto que est siendo usada como clave en una estructura de datos hash. Ya hemos hablado anteriormente en el capitulo del mtodo hasht'odef ).

Cuando se ejecuta el programa, vemos que el depurador de memoria se sallar una de cada tres claves, porque en la matriz key* se ha incluido tambin una referencia normal a dichas claves, por lo que esos objetos no pueden ser depurados de la memoria. Contenedores Java 1.0/1.1

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1549 Lamentablemente, hay una gran cantidad de cdigo que se escribi utilizando los contenedores de Java 1.0/1.1, incluso hoy dia se sigue describiendo algo de cdigo nuevo empleando dichas clases. De modo que aunque nunca vayamos a utilizar 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 ellos. Y, como son anacrnicos, trataremos de evitar poner demasiado nfasis en alguno de los detalles relativos 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, asi que dicho contenedor se utilizaba con gran frecuencia. Sus defectos son demasiado numerosos como para describirlos aqu (vase la primera edicin de este libro, disponible en ingls para descarga gratuita en wwiv.MindView.net). Bsicamente, podemos considerar este contenedor como un tipo Array List con nombres de mtodos muy largos y complicados F.n la biblioteca revisada de contenedores Java, se adapt Vector para que pudiera funcionar como un contenedor de tipo a Collection y de tipo List. Esta solucin no es excesivamente 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 antiguo.

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 slo dos mtodos y utiliza nombres de mtodo ms largos; hoolcan hasMoreEIcments( ) devuelve true si esta enumeracin contiene ms elementos \ Objcct nextElement( ) devuelve el siguiente elemento de esta enumeracin si es que existe (en caso contrario, genera una excepcin).

Enumeracin es slo una interfaz, no una implcinentacin. e incluso las nuevas bibliotecas utilizan todava en ocasiones la interfaz Enumeraron, lo que no resulta muy afortunado, aunque tampoco es que

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1550 genere grandes problemas. En general, debemos usar Iterator siempre que podamos en nuestro propio cdigo, pero an asi debemos estar preparados para encontramos 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 utilizando el mtodo Collections.enumeratioii ). como se puede ver en el siguiente ejemplo: //: containers/Enumerations.java // Vector y Enumeration de Java 1.0/1.1. import java.til.*; import net.mindview.til.* public class Enumerations ( public static void main(String[] args) ( Vector*String> v * new Vector<String>(Countres.ames(10)); Enumeration<String> e * v.elements()j while(e.hasMoreElements{I) System, out. print te. nextElement O **, v); // Generar un objeto Enumeration a partir de otro Collection: e = Collections.enumeration(new ArrayList<String>(>);

l } /* Output: ALGERIA. ANGOLA, BENIN, BOTSWANA. BULGARIA, BURKINA FASO, BURUNDI. CAMEROON, CAFE VERDE, CENTRAL AFRICAN REPUBLIC,

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1551 *///:-

Para generar un objeto Enumeration. llamamos al mtodo e!ements( ). despus de lo cual podemos usarlo para realizar una iteracin en sentido directo.

La ltima linea crea un objeto ArrayList y utiliza cnumcration( ) para adaptar un objeto Enumeration a partir del aerador de ArrayList Iteralor As. si disponemos 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 explicado anteriormente al hablar de LinkcdList. Lo que resulta ms confuso acerca del contenedor Stack de Java 1.0 I I es que en lugar de utilizar un Vector empleando el mecanismo de composicin, Stack hereda de Vector Por tanto, tiene todas las caractersticas y comportamientos de Vector ms alguna funcionalidad propia de Stack. Resulta ditlcil saber si los

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1552 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 distribucin, de modo que este diseo incorrecto contina hacindose notar todava hoy en muchos programas (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 fcil de utilizar un elemento LinkcdList como pila, o bien la clase Stack creada en el Captulo 11. Almacenamiento de objetos: //: containers/Stacks.java // Ilustracin de la clase Stack, import java.util.*; import Gtatic net.mindview.util.Print. ; enum Month ( JANUARY, FEBRUARY, MARCH, APRIL, MAY. JUNE. JULY. AUGUST. SEPTEMBER, OCTOBER. NOVEMBER ) public class Stacks { public static void main(String[3 aras) { Stack<String> 3tack * new Stack<String>0; foriMonth m : Month.values() ) stack.push(m.toString(J); print ("stack " stack); // Tratar una pila como un Vector: stack.addElement("The last line"J; print < "element 5 *= " * stack.elementAt\ S )) ; print("popping elements:); while 11 stack empty()) printnbistack.pop()" ") ; // Utilizar un objeto LinkedList como una pila: LinkedList<String> lstack * new LinkedList<String>l) ; for(Month m : Month.values()) lstack.addFirstm.toString()); print("lstack * " f lstack); while(lstack.isEmpty0) princnb(lstack.removeFirst 0 -+ ' M);

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1553 // Uso de la clase Stack del Captulo 11, // Almacenamiento de objetos: net.mindview.til.St ack<String stack2 = new net.mindview.util.St ack<String>(); fortMonth m : Month.values()) stack2push(m.toString0); print("stack2 = " stack2); while(! stack2.empty()) printnbIstack2.popl) + " ");

) } /* 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 *///:-

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1554 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 la pila mediante pop( ). Para resaltar uno de los puntos que hemos comentado anteriormente, tambin se realizan operaciones de tipo. Vector sobre el objeto Stack Esto es posible porque, debido a la

herencia, un objeto Stack es un Vector Por tanto, todas las operaciones que puedan realizarse sobre un objeto Vector tambin podrn realizarse sobre un objeto Stack. como por ejemplo la operacin elementAt( ).

Como hemos mencionado anteriormente, debemos utilizar un contenedor t.inkedList cuando queramos un contenedor con comportamiento de pila: o bien, la clase net.mindview.utM.Stack creada a partir de la clase l.inkedl.ist BitSet

BitSet se utiliza si se quiere almacenar de manera eficiente una gran cantidad de informacin de tipo binario. Slo resulta eficiente desde el punto de vista del tamao; si lo que estamos buscando es eficiencia de acceso, este contenedor resulta ligeramente ms lento que emplear una matriz nativa.

Adems, el tamao mnimo de un contenedor BitSet es el de los valores a long 64 bits. Esto implica que si estamos almacenando algn conjunto de bits menor, como por ejemplo. 8 bits, con BitSet estaremos

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1555 desperdiciando buena parte del espacio; en este caso, seria simplemente mejor crear una clase, o una matriz, para almacenar los indicadores binanos. en caso de que el tamao sea un problema (esto slo ser un problema si estamos creando una gran cantidad de objetos que contengan listas de informacin binaria: y solo debera tomarse esta decisin despus de realizar un perfilado del programa o de realizar algn otro tipo de mtrica. Si tomamos la decisin basndonos simplemente en nuestra propia creencia de que algo es demasiado grande, terminaremos creando una complejidad innecesaria y desperdiciando una gran cantidad de tiempo)

Un contenedor normal se expande a medida que aadimos ms elementos \ BitSet tambin acta de esta misma manera. El siguiente ejemplo muestra cmo funciona BitSet: //: container?/Bits.jav a // Ilustracin de BitSet. iraport java.til.*; import static r.et .mindview.util. Print. * ; public class Bits ( public static void printBtSetlBitSet b) ( print(MbitS: M b); StringBuilder bbits = new StringBuilderf)/ fcr(int j = 0; j < b.sisei' ; bbits.append(b.get(j) ? M1H : "0")/ print(Mbit pattem: " + bbits);

) public static void mam(String] args) ( Random rand = new Randcm(47); // Tomar bit de menos peso de nextlnt(): byte bt -

new TreeSet<String>(data)) ; 17 Anlisis detallado de los contenedores 1556 byte)rand.nextlnt{); BitSet bb = new BitSet(); for(int i * 7; i >= 0; i--) if < ((1 i) & bt) != 0) bb.sst (i) ; else bb.clear(i); print(*byte valu; " + bt) ; prmtBitSet(bb); short st = (short) rand.nextlnt () , BitSet bs *= new BitSet O; for(int i - 15; i >= 0; i) ifHIl i) & st) t 0 ) bs.set (i) ; else

17 Anlisis detallado de los contenedores 1557 bs.clear(i); print("short valu: M + st) ; printBitSetibs);int it = rand.nextlnt(); BitSet bi * new BitSet()/ for(int i = 31; i >= 0; i--) ifl(II i) & it) 1= 0) bi.set (i) ,* else bi.clear i); print ( int valu: v -* it) ; prntBitSet(bi); // Probar conjunto de bits >= 64 bits: BitSet bl27 = new BitSetI) ; bl27.set(127) , print("set bit 127; " + b!27); BitSet b255 = new BitSet(65); b255.set(255); print ("set bit 255: " b255); BitSet bl023 = new BitSet(512); blQ23.set(10231; bl023,set(1024); print ("set bit1023: ) / Output: byte valu: -107 bits: (0. 2,4. 7} bit pattern: 101010010000000000000000000000000000000000000000000000000 0000000 short valu: 1302 bits: (1, 2, 4. 9, 10} bit pattern: 0110100010100000000000000000000000000000000000000000 00000000000 0 int valu; -2014573909 bit3:{0, 1, 3. 5,7, 9, 11, 23. 24, 25. 26, 31} 18, 19, 21, 22, "-* bl023);

bit pattern: 1101010101010000001101111110000100000000000000000000000000000000 set bit 127; (i27l set bit 255; (255) set bit 1023: (1023, 1024}

17 Anlisis detallado de los contenedores 1558 *///:

Utilizamos el generador de nmeros aleatorios para crear nmeros aleatorios byte. short e int. trans ormando cada uno en su correspondiente patrn de bits que se almacena en un contenedor BitSet listo no causa ningn problema porque BitSet tiene 64 bits como mnimo, asi que ninguno de estos valores hace que se tenga que incrementar el tamao. A continuacin, se crea contenedores BitSet de mayor tamao. Como podemos ver en el ejemplo, cada contenedor BitSet se expande segn es necesario.

Normalmente, resulta ms conveniente utilizar un contenedor EnumSet (vase el Capitulo 19. Tipos enumerados) en lugar de BitSet cuando disponemos de un conjunto lijo de indicadores binarios a los que podemos asignar nombre, porque EnumSet nos permite manipular los nombres en lugar de las posiciones numricas de cada bit. con lo que se reduce la posibilidad de cometer errores en el programa. EnumSet tambin nos impide aadir accidentalmente nuevas posiciones binarias. que es algo que podra causar algunos errores graves y difciles 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 operaciones especiales incluidas en BitSet (consulte la documentacin del JDK. relativa a BitSet y EnumSet) Resumen

La biblioteca de contenedores es. probablemente, la mas importante de cualquier lenguaje orientado a objetos. En la mayora 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.

17 Anlisis detallado de los contenedores 1559 Como hemos visto en el Capitulo 11. Almacenamiento de objetos, podemos hacer varins cosas enormemente 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 hash como para escribir nuestro propio mtodo hashC ode( ) (y debemos saber tambin cundo es necesario hacer esto), y debemos conocer lo suficiente acerca de las distintas implementaciones de contenedores como para poder decidir cul es la apropiada para nuestras necesidades. En este capitulo hemos cubierto estos conceptos y hemos proporcionado detalles tiles adicionales acerca de la biblioteca de contenedores 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 diseo de bibliotecas). En C+-K las clases de contenedores cubren todos los aspectos fundamentales empleando muchas clases diferentes. Evidentemente, esta solucin era mejor que la que habia disponible antes de que aparecieran las clases de contenedores 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 porua nica clase, container" que acta al mismo tiempo como secuencia lineal y como matriz asociativa La biblioteca de contenedores de Java trata de conseguir un cierto equilibrio. 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 C4-4 y a otras bibliotecas de contenedores similares El resultado puede parecer algo extrao en algunos aspectos, pero, a diferencia de algunas de las decisiones tomadas en las primeras bibliotecas Java, esos aspectos extraos no son simples accidentes, sino decisiones de diseo cuidadosamente adoptadas y basadas en una serie de compromisos relativos a la complejidad de la solucin adoptada.

PucUc encontrar la* ulumme* lo* ejercicio* nclcccinniulwi ai el dwimicntu electrnico The Thinkinx tu Ahu AnnuiuiedSoJutlon Gutde. disponible pura l.i venia en nmv.XfndVitrw'.netEntrada/salida

18

La creacin de un buen sistema de entrada/ sal ida (E/S) es una de las tareas ms difciles para el diseador de lenguajes, cosa que queda de manifiesto sin ms que ver la gran cantidad de tcnicas distintas que se han utilizado.

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, binaria, de caracteres. por lincas, por palabras, etc.).

Los diseadores de la biblioteca Java abordaron el problema creando una gran cantidad de clases. De hecho, hay tantas clases para el sistema de ES 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 I .U. 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 nio (que quiere decir nevv I/O, nueva E/S. las cuales fueron introducidas en el JDK 1.4 y ya son. por tanto, bastante antiguas) 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 biblioteca de E/S. an cuando nuestra primera reaccin sea decir: *No me des la lata con cuestiones histricas, limtate a ensearme como utilizar las clases*. El problema es que. sin la perspectiva histrica, podemos llegar rpidamente a sentimos confundidos por algunas de las clases y a ni tener claro cundo deberan usarse y cundo no.

En este capitulo 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 utilidad de la biblioteca que nos sirve de ayuda a la hora de tratar con aspectos relativos a los directorios de archivos.

La clase File tiene un nombre que se presta a confusin, podramos pensar que hace referencia a un archivo, pero en realidad no es asi. De hecho, FilePath (ruta de archivo) hubiera sido un nombre mucho mejor para esa clase. Puede representar 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 conjunto utilizando el mtodo list ). que devuelve una matriz de objetos String. Tiene bastante sentido devolver una matriz en lugar de una de las clases contenedoras ms flexibles. porque el nmero de elementos es fijo y. si queremos un listado de directorio distinto, basta con crear un objeto File diferente. F.sta seccin muestra un ejemplo de utilizacin de esta clase, incluyendo la interfaz asociada FilenameFllter.

Una utilidad para listados de directorio

Suponga que deseamos obtener un listado de directorio. F.I objeto File puede utilizarse de dos maneras distintas. Si invocamos list( ) sin ningn argumento, obtendremos la lista completa contenida en el objeto File. Sin embargo, si queremos una

lista restringida, por ejemplo, todos los archivos que tengan la extensin .java, entonces debemos utilizar un "filtro de directorio. que es una clase que especifica 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_INSFNSITTVE_ORDF.R: //i io/DirList.java // Mostrar un listado de directorio utilizando expresiones regulares. // (Args: "D.*\.java") import java.til.regex.; import j ava.io.; import java-til.; public class DirLst { public static void mam (String (] args) { File path = new Filef".M) ; Stringl) Iist; if(args.length == 01 iist * path. Iist O; eise list * path.Iistnew DirFilter(args10]1) i Arrays .sort (list, String. CASE_INSENS1TIVE_0RDEP.J . forString dirltem : list) Svstem.out.println(dirltem);

) class DirFilter implements FilenameFilter ( private Pattern pattern; public DirFilter[String regex) ( pattern = Pattern.compile(regex);

) } public boolear. accepttFile dir, String ame) | retura pattern.matcher(namej.matchesfj;

) / Output: DirectoryDemo.java DirList.java DirList2.java DirList3.java *///:-

La clase DirFilter implementa la interfaz FilenameFilter. Observe lo simple que es esta interfaz: public interface FilenameFilter ( boolean accept(File dir, String ame);

La nica razn de existir de DirFilter es proporcionar el mtodo aecept( ) al mtodo list(), de modo

que ste pueda retro- llamar a accept( ) con el fin de determinar qu nombres de archivos deben incluirse en la lista. Debido a esto, esta estructura se suele calificar como retrollamada. 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 objeto 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- tc FilenameFilter con el fin de elegir (incluso en tiempo de ejecucin) cmo debe comportarse el mtodo lst(). El propsito del patrn de diseo de Estrategia es proporcionar una dosis de flexibilidad en el comportamiento del cdigo.

18 Entrada/salida 1566 El mtodo accepM ) debe aceptar un objeto File que represente el directorio en el que se encuentre un archivo concreto y un objeto String que contenga el nombre de dicho archivo. Recuerde que el mtodo list( ) llama a ncceptt ) para cada unode los nombres de archivo del objeto directorio, para ver cules hay que incluir; esto se indica mediante el resultado de tipo houlean devuelto por accept( ).

accept( ) utiliza un objeto matcher de expresiones regulares con el fin de ver si la expresin regular reges se corresponde con el nombre del archivo. Utilizando accept( ). el mtodo list( ) devuelve una matriz. Clases internas annimas

Este ejemplo e.s ideal para reescribirlo empleando una clase interna annima (concepto descrito en el Capitullo. Clases internas). Como primera aproximacin se crea un mtodo filtert ) que devuelve una referencia a un objeto FilenameFilter: //: io/BirList2.java // Utilizacin de clases internas annimas. // {Args: "D.*\.java") import java.til.regex.*; import java.io.*; import java.ut il.* ; public class DirLlst2 ( public static FilenameFilter tilter(final String regex) { // Creacin de la clase interna: return new FilenameFilter( ) { private Pattem pattern = Pattem.compile (regex) ; public boolean accept(File dir, String ame ( return public class DirListS {

18 Entrada/salida 1567 Da ttem. matcher (ame) .matcheaO;

) i ) i t i Fin de la clase interna annima

public static void main(String[] arg9) ( File path = new File("."); Stringi] list; if(args.length =* 0) list * path. list O,* else list = path.listIfilter(args10] )); Arraya.sort(list, St ring.CA5E_INSEKSITTVE_0RDE K); for(String dirltem : list) System.out.orintlnidirltem);

) } / Output; DirectoryDemo.j ava Dir List.java DirList2.java DirList3.java *///:-

public class DirListS {

18 Entrada/salida 1568 Observe que el argumento de filter( ) debe ser de tipo final Esto es requerimiento de la clase interna 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 I)irl.ist2. Sin embargo, podemos llevar este enfoque un paso ms all y definir la clase interna annima como un argumento de list( ), en cuyo caso el ejemplo es todava ms sucinto: //: io/DirList3.java // Creacin de la clase interna annima "sobre el terreno*1. // (Args: HD.\.java") import, j ava. ut i 1. regex. * ; import j ava.io.* ; lmport java.til.*; public static void mainifinal String (] args! ( File path = new File StringU Iist; lf larga.iength == Q) iist * path.Iist(); elee Iist = path. Iist (new FilenamsFil ter1 1 { private Pattern pattern = Fattem.compile (args (0}); public boolean acceptiFile dir, String namei { return pattern. matcher (ame) .matches O ,*

) public class DirListS {

18 Entrada/salida 1569 )i; Arraya. sort I Iist, String. CASE INSENSITIVE_ORDEfi: , Cor(String dirItera : listi System.cut.printin\dirltem );

) } /* Output: DirectoryDemo.j ava DirList.java DirList2 .java DirList3.java *///:-

El argumento de main ) es ahora de tipo final, puesto que la clase interna annima utiliza args|0| directamente

Este ejemplo nos muestra cmo las clases internas annimas permiten la creacin de clases especficas de un solo uso para resolver ciertos 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 fcil de leer y entender dicho cdigo, as que hay que utilizar juiciosamente esta tcnica. public class DirListS {

18 Entrada/salida 1570 Ejercicio 1: (3) Modifique DirList.java (o un3 de sus variantes) para que el objeto FilenanitrF'ilter abr y lea cada

archivo (utilizando la utilidad net.mindvicw.utiLTextFiie) y acepte el archivo basndose en si alguno de los argumentos finales de la lnea de comandos existe en dicho archivo.

Ejercicio 2: (2) Cree una clase denominada SortedDirl .ist con un constructor que tome un objeto File y construya una

lista 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).

Ejercicio 3: (3)Modifique DirList.java (o una de sus variantes) para que calcule la public class DirListS {

18 Entrada/salida 1571 suma de los tamaos 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 rccomcndo todo el rbol de directorios. Resulta til disponer de una herramienta que produzca el conjunto de archivos para nosotros. La siguiente clase de utilidad produce una matriz de objetos File en el directorio local utilizando el mtodo 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 informacin). Los archivos se eligen basndose en la expresin regular que proporcionemos: //: net/mindview/util/Directcry.java // Generar una secuencia de objetos File que se correspondan // con una expresin regular bien en el directorio local, // o bien recorriendo un rbol de directorios. public class DirListS {

18 Entrada/salida 1572 package net.mindview.util; import java.til.regex.; import java.io.*; import java.til.* ;public final caas Directory ( public static FilelO local(File dir, final String regex) { return dir.ilBtPiies(new FilenameFilter '{ private Pattem pattem * Pattem. compile l regexl ; public boolean accept(File dir. String namei ( return pattern.matcher( new Fi 3 einame) .aetHame()).matchesi); Ii public static FiieH local(String path, final String regex) | f f Sobrecargado return local(new Fileioathi, regex) ; } // Una tupia de dos elementos para devolver una pareja de objetos: public static class Treelnfo implements Iterable<File> ( public ListcFile> files new ArrayList<File>(); public List<Ftle> airs * new ArrayList<File> ,* // El elemento iterable predeterminado es la lista de archivos: public Iterator*:File iterator!) ( return files . iterator I) ,*

} void addAil(Treelnfo other) ( files .addAll {other.files) ,* dirs.addAll(other.dirs);

public class DirListS {

18 Entrada/salida 1573 ) public String toString() ( return "dirs: " + PPrlnt .pformat (dir) ) "\n\nflles: " PFrlnt.pformat (files) i

> public static Treelnfo walktString start. String regex) { // Comenzar recursin return recurseDirsi new File tstart), regex

} public static Treelnfo walk(File start. String regex) { // Sobrecargado return recurseDirai start, regexi;

} public static Treelnfo walk{File startI { / / Todo return recurseDira i start, i public static Treelnfo walk{String public class DirListS {

18 Entrada/salida 1574 start) { return recurseDira (new Fi lei starts ".*"1/

static Treelnfo recurseDira(File startDlr, String regex}( Treelnfo result <= new Treelnfo 0; for(File item ; startDir.listFiles()> ( if(item.lsDirectory()) ( result.airs.add(itemI: result.addAll(recurseDira(item, regex)); ) else I f Archivo normal If litem.getName().matches(regex)) result.files.add litem) ;

return result; // Prueba simple de validacin :public scatlc void main(String[J args) ( if(args.length == 0) System.out.printlnlwalk( * " I ); eise for(String arg : args) I 1 ///:System.cut.printlntwalk(arg)I;

El mtodo localf ) utiliza una variante de File.lis(() denominada listFiles( ) que genera una matriz de objetos File. Podemos ver tambin que utiliza un objeto FilenameFilter. Si hace falta una lista en lugar de una matriz, podemos convertir nosotros mismos el resultado utilizando Arrays.asList( )

public class DirListS {

18 Entrada/salida 1575 El mtodo walk( ) convierte el nombre del directorio de inicio en un objeto File e invoca recurseDirM ). que realiza un recorrido recursivo del directorio, recopilando ms informacin con cada recursion. Para distinguir los archivos normales de los directorios, el valor de retomo es. de hecho, una tupia" de objetos: un contenedor List que almacena archivos normales y otro que almacena los directorios. Los archivos estn definidos aqu como public a propsito, porque el objeto Treelnfo es simplemente para recopilar los objetos: si estuviramos simplemente devolviendo una lista, no lo definiramos como prvate, as que el hecho de que estemos devolviendo un par de objetos no quiere decir que los tengamos que definir como prvate. Observe que Treelnfo implementa Iteral>le<File>, que genera los archivos, de modo que disponemos de una iteracin predeterminada a travs de la lista de archivos, mientras que para especificar directorios tenemos que escribir .dirs'\

El mtodo TreeInfotoStrng() utiliza una clase de impresin avanzada, para que la salida sea ms fcil de visualizar. Los mtodos predeterminados toStrin( ) de los contenedores imprimen todos los elementos de un contenedor en una misma lnea. Para colecciones de gran tamao, esto puede hacerse difcil de leer, as que podemos tratar de emplear un formato alternativo. Me aqu la herramienta que aade avances de linea y sangrados a cada elemento: //: net/raindview/util/PPrin t.java // Impresin avanzada de colecciones package net. mmview. til ; import java.til.*, public class PPrint { public static String pformat(Collection<?> c) ( lflc.sizeU == 0) return M[]*; StringBuilder result = new StringSuilder (" t" > ; for(Object elem : c) (

public class DirListS {

18 Entrada/salida 1576 f(C.B2() i* i) result.appe nd i "\n H) / result.append *elem);

) iffc.sixeU 1= 1) result.append(H\n"); result.append("1W1; return result. toString ()

) public static void pprint(Collection<?> c) ( System.out.orintln(pformatle));

} public static void pprint(Object1] c) { System.out.println(pformat(Arraya.asList(c)));

public class DirListS {

18 Entrada/salida 1577 ) } m-.-

El mtodo pformat( ) produce un objeto String formateado a partir de un objeto Collection. y el mtodo pprint( ) utiliza pformat( ) para llevar a cabo esa tarea. Observe que los casos especiales en los que no existe ningn elemento o slo exista uno se gestionan de manera distinta. Tambin hay una versin de pprint( ) para matrices.

La utilidad Director}' esta incluida en el paquete net.mindview.util, para que est fcilmente disponible He aqu un ejemplo de utilizacin //: io/DlrectoryDemo.java // Ejemplo de uso de las utilidades de directorio. mport java.io.*/ import net. raindview.til.*; import static net.rnindview.til.Print.; public class DirectoryDemc { public static void main(String[] args) ( // Todos los directorios: PPrint.pprint(Directory.walk(* . *M .dirs) / // Todos los archivos que comiencen con 'T' for(File file : Directory.local i, "T.*")) print{file)j print (" - -- -- -- - ") ; f t Todos los archivos Java public class DirListS {

18 Entrada/salida 1578 que comienzan con 'T': for(File file : Directory.walk M.". "T.*\\.java")) print{file); print { = ss3sss=s= = sa" ) r // Todos los archivos de clase que contengan "Z" o "z": for(File file : Directory.walk(H.M,". *[2z] .*\\.class")) print(file);

:s

==

} } / Output: (ejemplo) t.\xfilesj .\TestEOF.class .\TestEOF.java .\TranserTo.class .\TransferTo.j ava .\TestEOF.}ava .\TransferTo.J ava .\xfiles\7hawAlien.java .\ Fr ee ze Al ie n. cl as s .\ GZ IP co mp re ss .c la public class DirListS {

18 Entrada/salida 1579 ss .\ Zi pC om pr es 5. cl as s */// :-

Puede que necesite refrescar sus conocimientos sobre expresiones regulares en el Capitulo 13, Cadenas de caractetvs. para comprender los argumentos situados en segunda posicin en |ocal( ) > \valk( )

Podemos llevar esta idea un paso ms all y crear una herramienta que recorra directorios v procese los archivos contenidos en ellos de acuerdo con un objeto Strategy sc trata de otro ejemplo del patrn de diseo Estrategia): 11: net/mindview/util/ProcessF iles.java package net.mlndview.util; import j ava.i o.*; public class ProcessFiies { public interface Strategy ( void process(File file);

public class DirListS {

18 Entrada/salida 1580 ) private Strategy strategy; private String ext; public ProcessFiies(Strategy strategy, String ext) | this.strategy *- strategy; this.ext = ext;

) public void start(String[] args) ( try { if'args.length =* 0) processDi rectoryTree l new Fileln."1)/ el se foriString arg : args { File CileArg * new Fxlefarg)/ lf (fileArg. isDlrectory() ) proeessDi rectoryTree i ileArg} ; else l // Permitir que el usuario no incluya la extensin: It {! arg. endsWith (*" ext}) arg -*= u. u + ext ; strategy.procesa i i new File(arg} .getCanonical File(J);

) ) catchIOException e) ( public class DirListS {

18 Entrada/salida 1581 throw new RuntimeException(e);

] public void processDirectoryTree(File root) throws XOException { for(File file Directory.walk root .getAbsolutePath i) , + ext)) strategy.orocess( ile.getCanonicalFileMi;

} // Ejemplo de utilizacin: public static void main(StringH aros) ( new ProcessFiles(new ProcessFiles.Strategy() public void procesa(File file) ( System.out.println(file>; public class DirListS { (

18 Entrada/salida 1582 } ), "java")-startlargs);

) } / (Execute to see output) ///:-

La interfaz Strategy est anidada dentro de ProcessFiles. de modo que si queremos irnplementarlu debemos implementar ProcessFiles.Strategy. que proporciona ms informacin 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 directorio actual. Tambin podemos especificar un archivo concreto, con o sm la extensin (el programa aadir la extensin en caso necesario) o uno o ms directorios

public class DirListS {

18 Entrada/salida 1583 En main() podemos ver un ejemplo bsico de utilizacin de la herramienta; en el que se imprimen los nombres de todos los archivos fuente Java de acuerdo con la lnea de comandos que proporcionemos.

Ejercicio 4: (2) Utilice Directory.walk( ) para sumar los tamaos de todos los archivos de un rbol de directorios

cuyos nombres se correspondan con una expresin regular concreta.

Ejercicio 5: (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 public class DirListS {

18 Entrada/salida 1584 existente. Tambin podemos utilizar un objeto File para crear un nuevo directorio o una ruta completa de directorios, si es que no existe. Tambin podemos examinar las caractersticas de los archivos (tamao, fecha de la ltima modificacin, permisos de lectura/escritura), para ver si un objeto File representa a un archivo o a un directorio, y borrar un archivo, bl ejemplo siguiente muestra algunos de los otros mtodos disponibles en la clase File (vase la documentacin del JDK disponible en http://juva.sun.com para conocer el conjunto completo de mtodos) ://: io/MakeDiiecLoriea.jsva Kjempl-o de uso de la clase File para crear // directories y manipular archives.
11

// {Args. MakeDlrectoriesTest) import java.io.*; public cla3 a MakeDirectories ( private static void usage| System, err .print lilt "Usage:MakeDirectories path! ...\n" "Creates each path\n" +"Usage:MakeDirectories -d pathi Deletes each path\n" "Usage:MakeDirectories -r pathl path2\n" - "Renames from pathl to path2n); System, ex it (1) ; \nr +

) private static void fileData(File fl { System.out.printIn( Absolute path: " 4 f.getAbsolutePath() read: " + .canRead() + "\n Can write: "\n getName: " " f-canWriteiJ + f.getName 0 + f. get Parent i) + "\n Can

"\n get Parent: H public class DirListS {

18 Entrada/salida 1585 "Vn get Path: * * f. get Path () ^

"\n length; " * f.length0 + "\n lastModified: " f.lastMoaified( > ) j if C . isFilet)) System.out.printlnCIt*s a file"); else if(f.isDirectory()> System.out.println("It *s a directory")/

) public static void main(StringI] args) ( if(args.length < 1 ) usage(); if(args[0 ].equals("-r")) | if(args.length 3} usage(); File old = new File(args[1]), rname = new File (args [2] ) ,- old.reuameTo(rname); fileData(old); t ileData(rname\ ; return; // Salir de main

) int count 0 ; boolean del = false; if(args[0 ].equals(H-d")) ( count-H-; del = true,-

public class DirListS {

18 Entrada/salida 1586 ) count--; while(count < args.length) ( File f - new File(args[count]); if(f.exists()) { System.out .println (f " exists1'); if(del) ( System.out.println("deleting." f); f.delete 0 ;

) }else { // No existe i f l d e l ) ( f . t n k d i r s ( ) ; System.out .println "created * * f) ,public class DirListS {

18 Entrada/salida 1587 i }

fileDataIf);

} } / Output: (80% match) created MakeDirec toriesTest Absoiute path: d:\aaaTIJ4\code\io\MakeDirectoriesTe st Can read: true Can write: true getName: MakeDirectoriesTest getParent: nuil getPath: MakeDirectoriesTest length: 0 lastModif ied: 110169030 8831 It's a directory *///:-

public class DirListS {

18 Entrada/salida 1588 En flel)ata( ) podemos ver diversos mtodos de consulta de archivos utilizados para mostrar informacin acerca del archivo o de la ruta de directorios.

I primer mtodo utilizado por main() es renanieTo(). que permite renombrar (o desplazar) un archivo a una ruta de directorios nueva, representada por el argumento, que es otro objeto File. Esto funciona tambin con directorios de cualquier longitud.
I

Si experimenta con el programa anterior, ver que puede construir una ruta de directorios todo lo compleja que quiera, porque mkdirst ) se encarga de hacer el trabajo por nosotros.

Ejercicio 6: (5) Utilice ProcessFilcs 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. public class DirListS {

18 Entrada/salida 1589 Entrada y salida

Las bibliotecas de ES de los lenguajes de programacin utilizan a menudo la abstraccin del flujo de datas (stream). para representar cualquier origen o destino de datos como un objeto capaz de producir o recibir elementos de datos. F.l 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 di\ ididas en entrada y salida, como se puede ver en la jerarqua de clases al examinar la documentacin del JDK. A travs del mecanismo 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 misma forma, todas las clases derivadas de las clases OutputStream o W riter disponen de mtodos bsicos denominados 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 (lujo de datos utilizando una nica clase, sino que en su lugar apilaremos 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 biblioteca de E/S de Java sea tan confusa.

Resulta til clasificar las clases segn su funcionalidad. En Java LO, los diseadores 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 public class DirListS {

18 Entrada/salida 1590 Como 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. Tipos de InputStream

La tarca de InputStream consiste en representar clases que produzcan datos de entrada procedentes de diferentes fuentes. Fstos orgenes de datos pueden ser:

1.

Una matriz de bytes.

2.

Un objeto String.

3.

Un archivo.

public class DirListS {

18 Entrada/salida 1591
4.

Una canalizacin {pipe)" que funciona como una canalizacin fsica: se introducen las cosas por un extremo y esas cosas salen por el otro.

Una secuencia de otros flujos de datos, de modo que se los pueda recopilar en un nico flujo.
5.

6.

Otros orgenes de datos, como por ejemplo una conexin a Internet (este tema se cubre en Thinking in Enterprise Java. disponible en www.MindView.net)*

Cada uno de estos orgenes tiene una subclase asociada de InputStream. Adems. FilterlnputStrcam tambin es un tipo de InputStream. para proporcionar una clase base para las clases decoradoras que asocian atributos o interfaces fciles a los flujos de datos de entrada. Este tema se analiza ms adelante.

Tabla E/S.1. Tipos de InputStream

public class DirListS {

18 Entrada/salida 1592

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 Siring, aunque presumiblemente podemos crear uno usando la matriz de bytes), a un archivo O a una canalizacin. public class DirListS {

18 Entrada/salida 1593 Adems, el objeto FilterOutputStream proporciona una clase base para las clases 'decoradoras'* que asocicn atributos o interfaces tiles a ios flujos de datos de salida. Este tema se analiza ms adelante Tabla E/S.2. Tipos de OutputStream

Adicin de atributos e interfaces tiles

Los decoradores ya han sido introducidos en el Capitulo 15. Genricos. La biblioteca de E S de Java requiere muchas combinaciones diferentes de caractersticos, y sta es la justificacin de utilizar el patrn de diseo Decarador.* La razn de la existencia de las clases filtro de la biblioteca de E/S de Java es que la clase abstracta de filtro'* es la clase base para iodos los decoradores Un decorador debe tener l;i misma interfaz que el objeto al que decore, pero el decorador tambin puede ampliar la interfaz, que es algo public class DirListS {

18 Entrada/salida 1594 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 ajustar fcilmente los atributos), pero aumentan la complejidad del cdigo. 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 disponer de esc nico objeto de E S que deseamos.

Las clases que proporcionan la interfaz de decoracin para controlar un flujo de datos InputStreaiii o OutputStream concreto son FilterInputStream y FilterOutputStream. que no tienen nombres muy intuitivos FilterlnputStream No cM claro que esta haya sido una buena decisin tic dl*eiU>. especialmente %i la compararnos con In simplicidad de la* biblioteca* de t S en otro* lenguajes Pero es, fein ninguna duda, la justification de esa decisin.
1

FiiterOutputSiream derivan de las clases base de la biblioteca de 11 S InputStream y OurputStream. lo que es un requisito elave de los decoradores (para que puedan proporcionar la interfaz comn para todos los objetos que estn siendo decorados). Lectura de un flujo InputStream con FilterlnputStream public class DirListS {

18 Entrada/salida 1595 Las clases FilterlnputStream realizan dos tareas significativamente distintas. DatalnputStrcam permite leer diferentes tipos de datos primitivos, asi como objetos String (todos los mtodos empiezan con reacT, como por ejemplo read!lyte(). rcaclFioat( ). etc. Esta clase, junto con su compaera DataOutputStreain. permite desplazar datos primitivos de un lugar a otro a travs de un flujo de datos. Esos "lugares" estn determinados por las clases de la Tabla E/S, I.

Las clases FilterlnputStream restantes modifican la forma en que se comporta internamente un llujo InputStream: indican si ste dispone de huffer o no. si llevan la cuenta de las lineas que estn leyendo (lo que nos permite preguntar por los nmeros de linea o asignar nmeros de linca), y si se puede retroceder un nico carcter. Las dos ultimas clases parecen pensadas para permitir la escritura de un compilador tprobablemente se aadieron pata soportar el experimento de "construir 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 buffet, independientemente del dispositivo de E/S con el que nos estemos conectando, asi que habra tenido ms sentido que la biblioteca de E/S dispusiera de un caso especial (o simplemente una llamada de mtodo) para la entrada sin huffer en lugar de pan la entrada con huffer. Tabla E/S.3. Tipos de FilterlnputStream

public class DirListS {

18 Entrada/salida 1596

Escritura de un flujo OutputStream con FilterOutputStream

El complemento a DatalnputStrcam es DataOutputStream. que formatea cada uno de los tipos primitivos y objetos String en un flujo de datos de tal manera que cualquier objeto DatalnputStrcam. en cualquier mquina, pueda leerlos, lodos los mtodos comienzan con "write". como por ejemplo \vriteByte( ). nriteFIat( ). etc.

La intencin original de PnntStream era imprimir todos los tipos de datos primitivos y objetos String en una forma legible. Esto difiere de DafaOutputStream, cuyo objetivo es insertar los elementos de datos en un flujo de datos de tal manera que DatainputStream pueda reconstruirlos de manera portable. public class DirListS {

18 Entrada/salida 1597 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 print!n( ) es que este ltimo aade un avance de linea cuando ha acabado.

PrintStream puede ser problemtico porque activa todas las excepciones de lOException (hay que comprobar explcitamente el estado de error con eheckrror( ), que devuelve true si se ha producido un error). Asimismo. PrintStream no se internacionaliza adecuadamente y no gestiona los saltos de linea de manera independiente de la plataforma. Estos problemas estn resueltos en PrintWriter, que se describe ms adelante.

RuffcrcdOutputStrcam es un modificador que le dice al flujo de datos que utilice un huffer, para que no se realicen escrituras fsicas cada vez que escribamos en el tlujo de datos. Normalmente, siempre conviene utilizar este modificador a la hora de llevar a cabo una salida de datos. Tabla E/S.4. Tipos de FilterOutputStream.

public class DirListS {

18 Entrada/salida 1598

Lectores y escritores

Java 1.1 realiz significativas modificaciones en la biblioteca fundamental de flujos de E/S. Cuando examine las clases Reader (lector) y Writer (escritor), su primer pensamiento (al igual que me pas a mi) puede ser que esas clases intentaban sustituir a InputStream y a OutputStream. pero en realidad no es asi Aunque algunos aspectos de la biblioteca original 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 funcionalidad en la forma de mecanismos de E/S orientados a bytes. mientras que las clases Reader y Writer proporcionan mecanismos de E/S orientados a caracteres y compatibles con Unicode. Adems:

1.

Java l.l aadi nuevas clases a las jerarquas InputStream y OutputStream. asi que resulta obvio que la intencin no era sustituir esas jerarquas.

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 public class DirListS {
2.

18 Entrada/salida 1599 objeto InputStream en un objeto Reader. y OutputStream Writer convierte un objeto OutputStream en un objeto Writer

L a razn mas importante para la existencia de las jerarquas Reader y \\ riter es la intemalizacin. La antigua jerarqua de flujos de datos de E S slo soportaba (lujos de datos con bytes de 8 bits y no gestionaba adecuadamente los caracteres Unicode 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 nuevas bibliotecas estn disertadas 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 ES de Java disponen de clases Reader y Writer correspondientes con el ln de permitir la manipulacin nativa de datos Unicode. Sin embargo, existen algunas ocasiones en las que los flujos InputStreani y OutputStream orientados a bytes constituyen la solucin correcta; en particular, las bibliotecas java.utiL/ip estn orientadas a bytes. en lugar de a caracteres. Por tanto, el enfoque ms apropiado consiste en tratar de utilizar las clases Reader y \N riter 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 aqui una tabla que muestra la correspondencia entre los orgenes y los destinos de informacin (es decir, de dnde vienen y a dnde van fsicamente los datos) en las dos public class DirListS {

18 Entrada/salida 1600 jerarquas.

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 InputStrcam y OutputStream, los flujos de datos se adaptaban para cada necesidad particular utilizando subclases decoradoras de FilterlnputStream y FilterOutpufStream. Las jerarquas de clases de Reader y Writer continan utilizando esta idea, pero no exactamente.

public class DirListS {

18 Entrada/salida 1601 En la siguiente tabla, la correspondencia es algo menos precisa que en la tabla anterior. La diferencia se debe a la organizacin de las clases; aunque BufferedOutputStream es una subclase de FilterOutputStream. BufferedWriter no es una subclase de FilterWriter (la cual, aunque es abstracta no tiene subclases y parece, por tanto, que se ha incluido simplemente 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.

Existe una directriz bastante clara: cuando quiera utilizar readl.ine( l. no debe hacerlo con un flujo DatalnputStream (esto origina un mensaje de advertencia en tiempo de compilacin donde se informa de que esa tcnica est desaconsejada), sino que debe utilizar BufferedReader Por lo dems. DatalnputStream contina siendo uno de los miembros aconsejados de la biblioteca de E/S.

Pam facilitar la transicin a soluciones que empleen Print Writer. esta clase tiene constructores que admiten cualquier objeto OutputStream asi como objetos Writer. 1.a interfaz de formateo de PrintWriter es casi idntica a la de PrintStream

public class DirListS {

18 Entrada/salida 1602 En Java SE5, se han aadido constructores a PrintWriter para simplificar la creacin de archivos a la hora de escribir la salida, como veremos enseguida.

Un constructor PrintWriter tambin dispone de una opcin para realizar un vaciado de buffcr automtico, lo que tiene lugar despus de cada llamada a println( ) si se activa el correspondiente indicador en el constructor. Clases no modificadas

Algunas clases no sufrieron modificaciones entre las versiones Java 1.0 y Java 1,1: Clases Java 1.0 sin clases Java 1,1 correspondientes DataOutputStream File KandnniAccessFlIe SequenccInputSlream

public class DirListS {

18 Entrada/salida 1603 DataOutputStream. en concreto, se utiliza sin modificacin, por lo que para almacenar y extraer datos en un formato transportable, se utilizan las jerarquas InputStream y OutputStream. RandomAccessFile Random AccessFile se utiliza para archivos que contengan registros de tamao conocido, de modo que podamos desplazarnos de un registro a otro usando seek( ), y luego leer o modificar los registros. Los registros no tienen que tener el mismo tamao; simplemente tenemos que determinar el tamao que tienen y el lugar del archivo donde se encuentran

public class DirListS {

18 Enirada'salida 1604 .En principio, resolta bastante difcil de entender que RandoniAecessFile no forme pane de In jerarqua InpufMream o OutputStream Sin embargo, no tiene ninguna asociacin con dichas jerarquas, salvo el hecho de que implcmenta las interfaces Dutalnput v DataOutput (que tambin son implementadas por DataInputStream y DataOutputStream). Ni siquiera utiliza ninguna de las funcionalidades de las clases InputStream o OutputStream existentes; se traa de una clase completamente separada, que se ha escrito partiendo de cero, con sus propios mtodos (en su mayor parte nativos). La rayn puede ser que RandomAccessFIle tiene un comportamiento esencialmente distinto del de los otros tipos de i S. ya que nos podemos mover hacia adelante y hacia atrs dentro de un archivo. En cualquier caso, se traa de una clase aislada, descendiente directa de Objoct.

Esencialmente, un objeto RandomAccessFilc funciona como un flujo DatalnputStrcam que se hubiera conectado con un flujo DataOutputStream. junto con los mtodos de getFilePointer( ) para averiguar en qu lugar del archivo nos encontramos. seck( ) para desplazarse a un nuevo punto en el archivo y Iength( ) para determinar el tamao mximo del archivo Adems, los constructores requieren un segundo argumento (idntico a fopcn( ) en C) que indica si estamos *>implementc leyendo de manera aleatoria (r") o leyendo y escribiendo (nO No existe soporte para archivos de slo escritura, lo que podra sugerir que RandomAccessFile podra tambin haberle diseado como clase heredada de DaialnputStream

Los mtodos de bsqueda slo estn disponibles en RandomAccessFile, que solamente puede aplicarse a archivos BufTeredlnputStream permite marcar con mark ) una posicin (cuyo valor se almacena en una nica variable 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 RandomAccessFile, si es que no toda ella, ha sido sustituida en el JDK l 4 por los archivos /apeados en memoria nio. que describiremos ms adelante en el capitulo. Utilizacin tpica de los flujos de E/S

18 Enirada'salida 1605 Aunque podemos combinar las clases de flujos de E;S de muchas maneras distintas, lo ms probable es que en nuestros programas 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. F.n el cdigo de los programas reales, conviene 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 FilelnpiitRcader con un objeto String o File como nombre de archivo. Para aumentar la velocidad, conviene asociar con el archivo un hitffer. para lo cual se proporciona al constructor la referencia a un objeto BufferedReader Puesto que BufferedReader tambin proporciona el mtodo readLinet ), ste ser nuestro objeto final y la interfaz a travs de la cual efectuaremos las lecturas. Cuando readLne( ) devuelva nuil, habremos alcanzado el final del archivo. //: io/BufferedlnputFile.jav a import j ava. io. ,* public class BufferedlnputFile ( // Pasar excepciones a la consola: pubiic stacic String readlString filenamei throws IOException { // Leer la entrada lnea a lnea: BufferedKeader in = new BufferedReadert new FileReader(filename)); String s; StringBuilder sb * new StringBuilderO; whileHs = in.readLme ()) I* nuil) sb.appendls M\n"J; in.cise I) ,* retum sb.coStringO; public static void main(String[] args) throws IOException { System.out.Drint(reaci (hBufferedlnputFile.java"));

18 Enirada'salida 1606 ) } /* (Ejecutar para ver la salida) ///;-

El objeto StringBuilder sb se utiliza para acumular el contenido completo del archivo (incluyendo los avances de linea que haya que aadir, ya que re*dLine() los elimina). Finalmente, se invoca close( ) para cerrar el archivo.42

Ejercicio 7: (2) Abra un archivo de texto para poder leer el contenido de lnea en linea. Lea cada linea en lurma de una

cadena de caracteres y site dicho objeto Strng dentro de un contenedor LlnkedList. Imprima todas las lneas de l.inkedList en orden inverso.

Ejercicio 8: (l)ModifiqueelEjercicio 7 comandos el nombre del

para proporcionar como argumento de la linea de

se define de esta forni n para la clase* de h S. Sin embargo, coran liemos comentado anteriormente, la funcionalidad f1nali/i'( ) no ha llegado a funcionar de la forma en que los diseadores de Java haban previsto originalmente (en otras palabras, no va a llegar a funcionar nunca i. por lo que li tecnica segura consiste en invocar CIOM?( ) explcitamente para los archivos.
42

18 Enirada'salida 1607 archivo que haya que leer

Ejercicio 9: (I) Modifique el Ejercicio 8 para pasar a maysculas todas las lneas del contenedor l.inkedl.ist y enve

los resultados a System.out.

Ejercicio 10: (2)ModifiqueelEjercicio 8 comandos que especifi

para admitir argumentos adicionales en la linea de

quen palabras que haya que encontrar en el archivo. Imprima todas las lineas que contengan alguna de las palabras. Ejercicio 11: (2)En elejemploinnerclasses/GreenhouseController.java.GreenhnuseCnntroller contiene unconjun

18 Enirada'salida 1608 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 de factora para construir los sucesos; consulte Thinking in Pattems (witfi Java) en mnvMindView.net. Entrada desde memoria

Aqu, el objeto Strinj* resultante de BuffercdliiputFiIe.rcad( } se utiliza para crear un objeto StringRcador \ continua- cin. se utiliza read ) para leer de carcter en carcter y enviar los datos a la consola: //: o/MemoryInput.j ava import j ava.c. ; public class MemoryInput ( public static void mam(Strlng t) args) throws IOException { StringReader in = new StrinaReaderl BufferedlnputFile.read("Memorylnp ut.javaI); int C; whilellc =* in.readOJ 1 = -1) System.out.print((char)c) ; ) ) / {Ejecutar para ver la salidai ///:-

Observe que read( ) devuelve el siguiente carcter como un valor int y debe, por tanto, proyectarse el resultado sobre un valor char para imprimirlo adecuadamente. Entrada de memoria formateada

Para leer datos "formateados, utilizamos un flujo DatalnputStream. que es una clase de E S orientada a bytes (en lugar de a caracteres). Por tanto, debemos utilizar todas las clases InputStream en lugar de clases Reader. Por supuesto, podemos leer cualquier cosa (por ejemplo un archivo) de byte en byte

18 Enirada'salida 1609 utilizando clases InputStrcam. pero lo que aqu se utiliza es una cadena de caracteres: //: io/FcrmattedMemorylnput.java lmport j ava.io.; public class FormattedMemorylnput ( public static void mam (String [J arge) throws IOException ( try ( DatalnputScream in a new DatalnputStream( new ByteArraylnputStream( BufferedlnputFile.read( FormattedMemorylnput. java**) .getBytes())); while(truel System.out.printI(char)in.readByte()); } catch(EOFException e> { System.err.printIn"End of stream"1 ;

) i } / {Bxecute to see output) ///:-

A un flujo ByteArraylnputStream hay que proporcionarle una matriz de bytes. Para generarla. String dispone de un mtodo getBvtes(). El flujo ByteArraylnputStream resultante es un objeto InputStream apropiado para entregrselo a un flujo DatalnputStream

Si leemos los caracteres de un flujo DatalnputStream de bytc en byte usando readByte( ), todo valor de tipo byte es un resultado legtimo, por lo que el valor de retomo no puede utilizarse para detectar el final de la entrada. En lugar de ello, puede emplearse el mtodo avaable( ) para determinar cuntos caracteres ms hay disponibles. He aqu un ejemplo que muestra cmo leer un archivo de bytc en byte: //: io/TestEQF.java // Comprobacin del final del archivo mientras se lee de byte en byte. import j ava.io.*; public class TestEOF (

18 Enirada'salida 1610 public static void maintStringl] arge) throws IOException l DatalnputStream in = new DatalnputStream! new BufferedlnputStream( new FileLnputStreamt"TestEOF.java*))) while(m.available() t* 01 System.out.print i (char)in.readSyte());

} ) / (Ejecutar para ver la salida! *///:-

Observe que a\ailable( ) funciona de manera distinta dependiendo del tipo de medio del que estemos leyendo: literalmente. ese mtodo nos da "el nmero de bytes que pueden leerse sin que se proditzca un bloqueo Con un archivo, esto significa todo el archivo, pero con otro flujo de datos distinto podramos obtener alguna otra cosa, asi 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 FlIeVVriter escribe datos en un archivo. Prcticamente en todas las ocasiones nos convendr aadir un buffer a la salida, envolviendo el objeto en otro objeh> BufferedWriter (trate de eliminar este

18 Enirada'salida 1611 objeto envoltorio para ver el impacto sobre el rendimiento: la utilizacin de buffets ayuda a incrementar enormemente el rendimiento de las operaciones de E/S). En este ejemplo, se utiliza PrintW riter como decorador para que se encargue de las tareas de formateo. El archivo de datos creado de esta forma se puede leer como un archivo de texto normal. //: io/BaoicFileOutput. ;java import java.o.*; public class BasicFileOutput { static Strirsg file = "BasicFileOutput .out" ; public static void mainStringH args) throws IOException ( SufferedReader in = new BufferedReader( new StringReader( Buf feredlnputFile. read ("BasicFileOutput .java") )) ; PrintWriter out = new PrintWriter new BufferedWriter(new FileWriter(file))); int lineCount = 1; String a whileUs * in.readLine () J l* nuil \ out .println (lineCount-f4 * MH 4 s) / out.cise{ ) 1 // Mostrar el archivo almacenado: System.out .println (Buf feredlnputFile. read (file)) ,*

) } /* (Ejecutar para ver las salida) *///:-

A medida que se escriben lneas en el archivo, se aaden los nmeros de lnea. Observe que no se utiliza LineNumberReader, porque es una clase muy simple y no resulta necesaria. Como podemos ver en este ejemplo, resulta trivial llevar la cuenta de nuestros propios nmeros de lnea.

Una ve/, que se han terminado los datos del tlujo de entrada. readLinc< ) devuelve nuilEl muestra una llamada

ejemplo

18 Enirada'salida 1612 explcita a close( ) para out, porque si no invocamos a elose( ) para todos los archivos de salida, podramos encontramos 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 SE5 ha aadido un constructor a PrintWriter para que no tengamos que encargamos derealizar a manotodas las

tareas de decoracin cada vez que queramos crear un archivo texto y escribir en l. El ejemplo siguiente muestra el archivo BasicFilcOutput.java reescrito con esta nueva tcnica: //: io/FileOutputShortcut.java import j ava.io.*; public class FileOutputShortcut { static String file = "FileOutputShortcut.out"; public static void main(String[] args) throws IOException ( Buf feredReader in = new BufferedReader( new StringReader( BufferedlnputFile.read("FileOutputShortcut.java"))); // He aqu la tcnica ms simple: PrintWriter out = new PrintWriter(file) : int lineCount = 1; String s; whileUs = m.readLine{)) 1 nuil ) out.println(lineCount+ + " " + s) ; out.cise() // Mostrar el archivo almacenado: System.out.println(BufferedlnputFile.read(file));

) } / (Ejecutar para ver la salida) ///:-

18 Enirada'salida 1613 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 requiriendo una gran cantidad de texto redundante. Sin embargo, la utilidad TexiFilc que se usa en este libro, que definiremos mas adelante en este capitulo, permite simplificar estas tareas comunes.

Ejercicio 12: (3) Modifique el Ljercicio 8 para abrir tambin un archivo de texto de modo que podamos escribir texto

en l. Escriba en el archivo las lineas del contenedor LinkedList. junto con sus correspondientes nmeros de linea (no trate de utilizar las clases LineNumber"),

Ejercicio 13: (3) Modifique BasieFHcOutput.java para que utilice LineNumberReader con el fin de controlar el

nmero de lineas. Observe que resulta mucho ms sencillo llevar la cuenta mediante programa.

18 Enirada'salida 1614 Ejercicio 14: (2) Comenzando con BasicFileOutput.java, escriba un programa que compare la velocidad de escritura

en un archivo al utilizar mecanismos de E/S con buffer y sin bu/fer. Almacenamiento y recuperacin de datos

Un objeto Print\\ riter formatea los datos para que resulten legibles. Sin embargo, para sacar los datos de manera que puedan ser recuperados por otro flujo de datos, se utiliza DataOutputSlream para escribir los datos y DatalnputStream para recuperarlos. Por supuesto, estos flujos de datos pueden ser cualquier cosa, pero en el siguiente ejemplo se utiliza un archivo. usndose buffets tanto para lectura como para escritura. DataOutputStream y DatalnputStream estn orientados a bytes y requieren por tanto, flujos de datos InputStream y OutputStream: //: io/StoringAndRecoveringData ,j ava import java.io.*; public class StoringAndRecoveringDaca ( public static void main(StringU args) throws IOException ( DataOutputStream out * new DataOutputStream( new BufferedOutputStreamI new FleOutputStream"Data.txt"))); out. wnteDcuble (3 . 14159) ; out.writeUTF(43 That was pi"); out.writeDouble (1.41413); out.writeUTF("Square root of 2"); out.cise(); DatalnputStream in = new DatalnputStream( new BufferedlnputStream( new FilelnputStream (Data. txt )) ) ; System.out.printIntin.readDouble O ) ; // Slo readUTF() permite recuperar la cadena // de caracteres Java-UTF apropiadamente: System.out.println(in.readUTF(J1; System.out.println(in.readDouble()); System.out.println{in.readTF(>); i ) / * Output:
43Hablaremos

de XML ms adelante en este capitulo.

18 Enirada'salida 1615 3.14159 That was pi 1.41413 Square root of 2 ///:

S utilizamos DataOutputStream para escribir los datos. Java garantiza que podemos recuperar perfectamente los datos con DatalnputStream. independientemente de las plataformas que se empleen para leer y escribir los datos. Esto resulta enormemente til, como puede atestiguar cualquiera que haya dedicado algo de tiempo a resolver problemas relacionados con el tratamiento especifico de los datos en cada plataforma. Dichos problemas desaparecen si disponemos de Java en ambas plataformas.^

Cuando estamos usando DataOutputStream. la nica forma fiable de escribir una cadena de caracteres para que pueda recuperase mediante un tlujo DatalnputStream consiste en utilizar codificacin UTF-8. la cual se consigue en este ejemplo mediante wntel!TF() y readlTF( ). UTF-8 es un formato multibyte. y la longitud de codificacin varia de acuerdo con el conjunto de caracteres que se est empleando. Si estamos trabajando con ASCO o caracteres que sean ASCII en su mayor parte (los cuales slo ocupan siete bits). Unicode representa un tremendo desperdicio de espacio yo 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 rres byies. Adems, la longimd de la cadena de caracteres se almacena en los dos primeros bytes de la cadena UTF-8. Sin embargo. \vriteUTF( ) > readUTF( ) utilizan una vanante de UTF-8 especial para Java (que est completamente descrita en la documentacin del JDK correspondiente 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.

18 Enirada'salida 1616 Con \\rirelITF( ) y readl TF( ). podemos mezclar cadenas de caracteres con otros tipos de datos empleando un tlujo de datos DataOutputStream. en la seguridad de que las cadenas de caracteres sern apropiadamente almacenadas como datos Unicode y podrn recuperarse fcilmente mediante DatalnputStream

El mtodo \\ rte)ouble( ) almacena el nmero double en el flujo de datos, y el mtodo complementario readl)>uble( ) permite 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 datos, ya que seria igualmente posible el valor dnuble almacenado como una simple secuencia de bytes, o como un valor char. etc. Por tanto, tenemos que tener un formato fijo para los datos en el archivo o. alternativamente, deberemos almacenar en el archivo informacin adicional que ser necesario analizar para detenmnar dnde estn ubicados los datos. Observe que otras tcnicas, como la de sealizacin de los objetos o XML (describiremos ambas ms adelante en el capitulo), pueden ser ms sencillas a la hora de almacenar y recuperar estructuras de datos ms complejas.

Ejercicio 15: (4) Consulte DataOutputStream \ DatalnputStream en la documentacin del JDK. Comenzando con

StormgAndRecoveringData.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

18 Enirada'salida 1617 Utilizar RandomAccessFile es como emplear sendos flujos DatalnputStream y DataOutputStream compilados (porque implementa !a.s mismas interfaces Datalnput y DaraOutput). Adems, podemos utilizar seek( ) para desplazamos por el archivo y cambiar los valores.

Para poder usar RandomAccessFile. debemos conocer la disposicin del archivo para poder manipularlo adecuadamente. RandomAccessFile tiene mtodos especficos para leei y escribir primitivas y cadenas de caracteres UTF-8 Fie aqu un ejemplo: //: io/UsingRandamAccessFile.java import j ava. io. *; public class UsingRandomAccessFile { static String file = MMrtest.dat"j static void display) throws IOException { RandomAccessFile rf = new RandomAccessFile<file, "r"); for(int i 0,* i < "1j i**) System.out.println( "Valu H * i M: " rf. readDouble) J ; System.out.println(rf.readUTFi)i; rf.cise()f ) public static void mainlStringH args) throws IOException { RandomAccessFile rf new RandomAccessFile(file, "rw"l; for(lnt i <= 0; i < 7; -M-) rf.writeDouble(i*1.414); rf.writeUTF("The end cf the file"); rf.cise ( ) diaplayO ; rf - new RandomAccessFile(file, "rW'J; rf. seek(5*8) rf.writeDouble(47,0001) : rf .cise{ ) ; displayd

) ) /* output: Valu 0: 0.0 Valu 1: 1.414 Valu 2: 2.020 Valu 3: 4.242 Valu 4: 5.656 Valu 5: 7.069999999999999 Valu 6: 8.484 The end of the file Valu 0: 0.0 Valu

18 Enirada'salida 1618 1: 1.414 Valu 2: 2.829 Valu 3: 4.242 Valu 4: S.656 Valu 5: 47.0001 Valu 6: 8.484 The end of the file *///1-

El mtodo display( ) abre un archivo y muestra siete elementos contenidos en l en forma de valores dotiblc. En main( ). se crea el archivo y luego se abre y modifica. Puesto que, un valor double siempre tiene ocho bytes de longitud, para desplazarlos con seek() al nmero situado en la posicin cinco basta con multiplicar 5*8 con el fin de obtener el valor de bsqueda.

C omo hemos indicado anteriormente. RandomAccessFile est aislado, de hecho, del resto de la jerarqua de E/S, salvo por la circunstancia de que implementa las interfaces Datalnput > DataOutput. lista clase no soporta el mecanismo de decoracin. asi que no se la puede combinar con ninguno de los aspectos de las subclases InputStream > OutputStream Tenemos que asumir, por tanto, que RandomAccessFile dispondr de los mecanismos de buffer apropiados, ya que no tenemos manera de especificar que se emplee.

La nica opcin de la que disponemos se encuentra en el segundo argumento del constructor, podemos abrir un archivo RandomAccessFile para lectura ("r) o lectura y esentura (nv)

Tambin merece la pena considerar la utilizacin de archivos mapeados en memoria nio en lugar de RandomAccessFile

18 Enirada'salida 1619 Ejercicio 16: (2) Consulte RandomAccessFile en la documentacin del JDK. Tomando como punto de partida

l'singRandomAccessFUe.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 solo hemos mencionado brevemente las clases PipedOutputStream. PipedReader y PipedNN riter. 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 anali/jir el tema de la concurrencia, ya que los flujos de datos canalizados se emplean para la comunicacin entre tareas. Este lema se tratar junto con un ejemplo en el Captulo 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 volverlo a escribir. Uno de los problemas con la biblioteca de E/S de Java es que nos obliga a escribir bastante cdigo para poder realizar estas operaciones comunes: no existen funciones bsicas de utilidad que se encarguen de realizar el trabajo por nosotros. Todava peor, los decoradores hacen que resulte relativamente difcil acordarse de qu hay que hacer para abrir archivos. Por tanto, resulta bastante conveniente aadir clases de utilidad a nuestra biblioteca que se encarguen de realizar estas tarcas por nosotros. Java SE5 ha aadido un constructor muy til a Prini Writer 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 V otra vez y conviene tratar de eliminar el cdigo redundante asociado con dichas tareas.

18 Enirada'salida 1620 He aqu la clase TextFile 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 TextFile que almacene las lineas del archivo en un contenedor Array List (con lo que tendremos toda la funcionalidad de Array List a la hora de manipular el contenido del archivo): //: net/mindview/util/TextFile.java // 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.til; impon j ava. io. import java.til.*; public classTextPile extends ArrayList<Strang> { // Leer un archivo como una tnica cadena de caracteres: public static String read(String fileName) ( StringBuilder sb = new StringBuilder(); try ( BufferedReader in= new BufferedReader(new FileReader new File(fileName}.getAbsoluteFileiM); try { String s; while((s - in.readLine0) i= nuil) { sb.append{s); sb.append fn\n *); l } finally { in.cise(l; l } catch(IOException e) { throw new RuntimeExcepcin(e);

) return sb.toString ()r

} // Escribir un archivo en una llamada a mtodo: public static void write(String fileName, String text) { try (

18 Enirada'salida 1621 PrintWriter out <= new PrintWriter new File(fileName).getAbsoluteFile()); try f out.print(text); } finally { out.cise(); i ) catch(IOException e) { throw new RuntimeException(e);

) // Leer un archivo, dividir segn cualquier expresin regular: public TextFile(String fileName, String splitter) ( super(Arrays.asList(read(fileName/.splitisplitt er)));

i 18 Entrada/salida 1622 / / E l mtodo splitO con expresiones regulares suele dejar una// cadena de caracteres vaca en la primera posicin: if (get (0) .equalsf")) remove(O) / // Leer normalmente lnea a lnea*, public TextFile(String fileName) { this(fileName. "\n"J; i public void write(String fileName) { try { PrintWriter out = new PrintWriter( new File<fileName).getAbsoluteFil eO)j try { for(String item : this) out.println(item); } finally ( out.cise i);

} ) catch(IOException e) ( throw new RuntimeException(e);

} // Prueba simple: public static void main(Stringt] args) (

i 18 Entrada/salida 1623 String file - read("TextFile. java" ; write("test.txt", file); TextFile text = new TextFile"test.txt" ) ; text. write t "test2, txtn ) ; // Descomponer en una lista ordenada de palabras distintas: TreeSet<String> words = new TreeSet<String>( new TextFileI"TextFile.javaM, "\\W+"))/ // Mostrar las palabras en maysculas: System.out.println (words.headSet{a"));

) } / Output: [0, ArrayList, Arrays, Break, BufferedReader, BufferedWriter, Clean, Display, File, FileReader, FileWriter, IOException, Normally, Output, PrintWriter, Read. Regular, RuntimeException, Simple, Static, String, StringBuilder. System. TextFile, Tools, TreeSet. W, Write]

*///:-

read( ) aade cada linea a un objeto StringBuilder. seguida de un avance de linea, ya que los caracteres de avance de linea se eliminan durante la lectura. A continuacin, devuelve un objeto String que contiene el archivo completo, w ritc( ) abre el archivo y escribe en l el texto contenido en String

i 18 Entrada/salida 1624 Observe que todos los elementos de cdigo en los que se abre un archivo protegen la llamada a close( ) dentro de una clusula finally para garantizar que el archivo se cierre correctamente.

El constructor utiliza el mtodo read( ) para transformar el archivo en un objeto String. y luego emplea String.split( ) para dividir el resultado en lneas, segn estn dispuestos los avances de linea (si utiliza esta clase muy a menudo, trate de rescribir este constructor para mejorar el rendimiento). Como no existe ningn mtodo para combinar las lneas es necesario utilizar el mtodo write( ) no esttico para escribir las lineas de forma 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 embargo, en los programas reales puede que sea necesario crear otra versin que pase las excepciones IOException al llamante.

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 permite ahorrar una gran cantidad de tiempo de programacin, como veremos posteriormente en algunos de los ejemplos de este capitulo.

i 18 Entrada/salida 1625 Otra forma de resolver el problema de leer archivos de texto consiste en utilizar la clase java.utiLScanner irtroductda en Java SE5. Sin embargo, esa clase slo sirve para leer archivos, no para escribirlos, y dicha herramienta (que no se encuentra en java.io) est diseada principalmente para crear analizadores de lenguajes de programacin o pequeos lenguajes**.

Ejercicio 17: (4) Utilizando TextFile y un contenedor Map<Character,lnteger>. cree un programa que cuente el

nmero de apariciones de los diferentes caracteres dentro de un archivo (en otras palabras, si la letra V aparece 12 veces en el archivo, el valor Integer asociado con el valor C haracler que contenga 'a* en el mapa ser *12*).

Ejercicio 18: (l) Modifique Text File, java para que pase las excepciones IOException al llamante. Lectura de archivos binarios

lista utilidad es similar a Text FUe. ja va. en el sentido de que permite simplificar el proceso de lectura de archivos binarios: / /: net/mindview/util/BinaryFile. java // Utilidad para leer archivos en forma binaria, pacfcage net. roindview. til; import java.io.;

i 18 Entrada/salida 1626 public class BinaryFile ( public static byte[J read(File bFile) throws IOException) BufferedlnputStrearo. bf = new BufferedlnputStrearo( new FilelnputStream(bFile)); try { bytef] data new byte[bf.availabie()1; bf.readldata); return data; ) finally ( bf.cise(J;

} public static byteU read(String bPtle- thrcws IOException ( 1 return readmew 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.

i 18 Entrada/salida 1627 Se utiliza el mtodo availahle( ) para obtener el tamao apropiado de la matriz y esta versin concreta del mtodo read( ) sobrecargado se encarga de rellenar la matriz.

Ejercicio 19: (2)UtilizandoBinaryFile programa que cuente el nmero

un

contenedor

Map<B>te.lnteger>.

cree

un

de apariciones de los diferentes bytes dentro de un archivo.

Ejercicio 20: (4)UtilizandoDireetory.walk() y BinaryFile. verifique que todos los archivos .class en un rbol de

directorios comienzan con los caracteres hexadecimalcs *CAFEBABE\ E/S estndar

i 18 Entrada/salida 1628 El trmino E/S estndar hace referencia al concepto Unix de un nico flujo de informacin que es utilizado por un programa (esta idea se reproduce en cierta manera en Windows y muchos otros sistemas operativos). Toda la entradu de un pro- grama puede provenir de la entrada estndar, toda su salida puede ir a la salida estndar y todos sus mensajes de error pueden enviarse a la salida de error estndar. El valor de la E/S estndar es que los programas se pueden encadenar fcilmente entre si. y la salida de un programa puede ser la entrada estndar de otro programa. Se trata de una herramienta de gran potencia. Lectura de la entrada estndar

Siguiendo el modelo de la E/S estndar. Ja\ a dispone de System.in. System.out y System.err \ lo largo de este libro hemos visto cmo escribir en la salida estndar utilizando System.out. que est ya preenvuelta en un objeto PrintStream System.err es tambin un objeto PrintStream. pero System.in es un objeto InputStream simple sin ningn tipo de envoltorio Esto significa que aunque podemos utilizar System.out y System.err de manera directa, es necesario envolver System.in antes de leer el mismo.

Normalmente, leeremos la entrada de lnea en linea empleando readl.inet ) Para hacer esto, envuelva System.in en un objeto Buffered Reader. lo que requiere que convierta System.in en un objeto Reader mediante InputStream Reader He aqu un ejemplo que se limita a devolver como un eco cada lnea que escribamos: //: io/Echo.java // Crac leer de la enerada estndar. // (RunByHand} impart j ava.io.; public clase Echo ( public static void roain(String[1 args) throws IOException { BufferedReader stdin = new BufferedReader{ new InputStreamReader(System.in)); String s; whileUs = stdin.readLine() ) = nuil k

i 18 Entrada/salida 1629 3.1engthOJ* 01 System.out.println(s); // Una lnea vaca o Ctrl-Z hace que se termine el Drograma

) ///*-

La razn de la especificacin de excepciones es que readl .inet ) puede generar una excepcin IOException Observe que System.in debera normalmente emplearse con un huffer. al igual que la mayora de los flujos de datos.

Ejercicio 21: (l) 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. Red i rija los contenidos de un archivo hacia este programa (el proceso de redireccmn variar dependiendo de su sistema

i 18 Entrada/salida 1630 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 utilizando dicho constructor: //: io/ChangeSystemOut.java // Transformacin de System.out en un objeto PrintWriter. import java.io.; public class ChangeSystemOut { public static void mam (String U args) { PrintWriter out = new PrintWriter(System.out, true); out.println"Helio, world");

) ) / Output: Helio, world

//AJES importante utilizar la versin de dos argumentos del constructor de PrintWriter y asignar al segundo argumento el valor true, para permitir el vaciado automtico de huffer. en caso contrario, podramos no llegar a ver la salida. Redireccionamiento de la E/S estndar

i 18 Entrada/salida 1631 La clase System de Java permite redingir los flujos de E S estndar de entrada, de salida y de error utilizando simples llamadas a los mtodos estticos: set ln(l nputStream) setOut(PrintStrea m) setrr( PrintStrea m)

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. 44 El redireccionamlento de la entrada resulta muy til para un programa de lnea de comandos en el que queramos probar repetidamente una secuencia concreta de entrada de datos de usuario. He aqu un ejemplo simple que muestra el uso de estos mtodos: //: io/Redirecting.java // Ilustra la redireccin de la E/S estndar, import java.io.*; public class Redirectmg { public static void main (String [] args) throws IOException { PrintStream consol = System.out; BufferedlnputStream in * new BufferedlnputStream{ new FilelnputStream("Redirecting.java" ) ) PrintStream out new PrintStream( new BufferedOutputStreami new FileOutputStream("test.out"))); System.setn(in) System.setOut(out); System.setErr(out); BufferedReader br = new BufferedReader< new InputStreamReader(System.in)); String s; whilels = br.readLine()) != null 44 En el Capitulo 22, Interfaces grficas itit usuario, >e muestra una solucin todava mfls comodu para este problema: un programa GUI con un rea de texto dc&plozablc

i 18 Entrada/salida 1632 System.out.println(s); out. el ose O; // Remember this! ) System.setOut(consol);

) ///:-

Este programa asocia la entrada estndar con un archivo y redirige la salida estndar y la salida de error estndar a otro archivo. Observe que almacena al principio del programa una referencia al objeto Sysfem.out original y que restaura la salida del sistema hacia dicho objeto al final del programa.

F.l redirecdonamiento de E'S manipula flujos de bytes. no flujos de caracteres; es por eso que se utilizan 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 salida de tales programas. La biblioteca Java proporciona clases para realizar teles operaciones.

i 18 Entrada/salida 1633 Una larca comn consiste en ejecutar un programa y enviar la salida resultante a la consola. En esta seccin vamos a pie- sentar una utilidad que permite simplificar dicha tarea.

Con esta utilidad pueden producirse dos tipos de errores: los errores normales 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. Informaremos de dichos errores mediante una excepcin diferente: // .* net/mindview/util/OSExecuteExcept ion. java package net.mindview.util; public class OSExecuteException extends RuntimeException ( public OSExecuteException(String why) ( super(why); )

I ///:-

Para ejecutar un programa, hay que pasar a OSExecute.command( ) una cadena de caracteres command, que es el mismo comando que escribiramos para ejecutar el programa en la consola. Hste comando se pasa al constructor java.lang. ProcessBuilder (que requiere que se le suministre en forma de una secuencia de objetos String), despus de lo cual se inicia un objeto ProcessBuilder: //: net/mindview/util/OSExecute.java // 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 {

i 18 Entrada/salida 1634 Process process = new ProcessBuilder(command.split(" ")).startt); Buf feredReader results * new BufferedReader( new InputStreamReader (process. get InputStream0 ) ) ; String s; while((s = results.readLine())!= null) System.out.println(s); BufferedReader errors = new BufferedReader( new InputStreamReader(process.getErrorStream())); // Informar de los errores y devolver un valor distinto de // cero al proceso llamante si existen problemas: while((s errors.readLme()) != nuil) ( System.err.println (s); err = true;

) ) catch(Exception e) { // Correccin para Windows 2000, que genera una // excepcin para la linea de comandos predeterminada: ifllcommand.startsWith("CMD /C")) command("CMD /C " -r command), else throw new RuntimeException(e);

] if(err) throw new OSExecuteException("Errors executing - command);

i 18 Entrada/salida 1635 )

) ///:-

Para capturar el flujo de salida estndar del programa a medida que ste se ejecuta, hay que invocar getltpulStream( ). La razn es que un objeto InputStream es algo de lo cual podemos leer.

Los resultados del programa llegan linca a linea, asi que los leemos mediante readl-ine{ ). Aqu nos limitamos a imprimir las lineas, pero tambin podramos capturarlas y devolverlas desde command( ).

Los errores de programa se envan al tlujo estndar de error y se capturan invocando getErrorStream( ). Si existen errores. se imprimen y se genera una excepcin OSExecuteException, de modo que el programa llamante pueda gestionar el problema.

Me aqui un ejemplo que muestra cmo utilizar OSExecute:

i 18 Entrada/salida 1636 //: io/OSExecuteDemo.j ava // Ilustra el redireccionamiento de la E/S estndar. impert net.raindview.util.; public class OSExecuceDemo { public static void main(String[] args) ( OSExecute.command("javap GSExecuteDemo* ) ; ) | / * OutpuC: Compiled from "OSExecuceDemo.java public class OSExecuteDemo extends java.lang.Object( public OSExecuceDemo(); ublic static void roain(java.lana.String[1);

} V//:-

Este ejemplo utiliza el dcscoropador javap (incluido en el JDK) para descompilar el programa.

Ejercicio 22: (5)ModifiqueOSF.xecute.javapara que, en lugar de imprimir el flujo estndar de salida, devuelva los

i 18 Entrada/salida 1637 resultados de la ejecucin del programa como una lista de cadenas de caracteres. Ilustre 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.* en el JDK I 4 tiene como principal objetivo aumentar la velocidad. De hecho, los antiguos paquetes de ES se han reimplementado utilizando nio para poder aprovechar este incremento de la velocidad, asi que nos podremos beneficiar de esa mayor velocidad incluso aunque no escribamos codigo con nio. El incremento de velocidad se hace patente tanto en la E'S de archivos, que es la que vamos a explorar aqui, como en la E S de red. de la que se trata en Thinklng in Enter)rseJa\a.

La mayor velocidad se obtiene utilizando estructuras que estn ms prximas a la forma en que se realiza la E S en el sistema operativo: canales y bujfers. Podramos utilizar el simil de una mina de carbn: el canal seria 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 inleractuamos directamente con el canal sino que lo hacemos con el buffer. enviando el buffer al canal. El canal extrae datos del huffer o pone datos en el huffer.

El nico tipo de buffer que se comunica directamente con el canal es BvteBiiffer. un buffer que almacena bytes sin ningn tipo de formato. Si examinamos la documentacin del JDK para java.nio.BytcBuffcr. podremos ver que se trata de una clase muy bsica: creamos uno de estos buffers diciendolc cunto espacio de almacenamiento hay que asignar y existen mtodos para msertar v extraer datos, bien como bytes sin formato o como tipos de datos primitivos. Pero no existe manera de insertar o de extraer un objeto, ni siquiera de tipo String Es una clase de nivel bastante bajo, precisamente porque esto hace que se pueda implcmentar de una forma ms eficiente la mayora de los sistemas operativos.

i 18 Entrada/salida 1638 Tres de las clases del antiguo" esquema de E/S han sido modificadas para poder generar un objeto FileChannel: FilcInputStreani. FileOutpiitStreani y, tanto en lectura como en escritura. RandomAccessFile Observe que se trata de los llujos de datos para manipulacin de bytes. en consonancia con la naturaleza de bajo nivel de nio. Las clases Reader y Writer en modo carcter no generan canales, aunque la clase java.nio.channeIs.Channels dispone de mtodos de utilidad para generar objetos Reader y Wrlter a partir de canales.

He aqui un ejemplo simple donde se prueban los tres tipos de flujo de datos con el fin de generar canales de escritura, de lectura/escritura y de lectura: //: io/GetChannel.java // Obtencin de canales a partir de flujos de datos import java.nio.*; import java.nio.channel3; xmport j ava.io.; public clasB GetChannel ( private static final int BSIZE = 1024; public static void main(String13 args) throws Exceptlon { U Escribir un archivo: FileChannel fe = new FileOutputStream("data.txt").getChannel() . fc.write(ByteBuffer.wrapl"Some text ".getBytesI)I); fe.cise 0; // Aadir al final del archivo: fe * new RandomAccessFile(HMdata.txtM. Mrw*).getChannel( ) fc.positionfc.size()); // Desplazarse al final fe.write(ByteBuffer.wrap<"Some more".getBytes( ) ) ) ; fe.cise (); // Leer el archivo: fe = new FilelnputStream("data.txt").getChannel0; ByteBuffer buff = ByteBuffer.allocate(B5IZE); fe.read(buff); buff .flipO ; while(buff.hasRemaining()) System.out.print((char)buf f.get ()) ;

i 18 Entrada/salida 1639 ) } /* Output: Some text Some more *///?

Para cualquiera de las clases de flujos de datos mostradas aqu. getChannelf) generar un objeto FileChannel. Los canales 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 posteriormente).

Una forma de insertar bytes en un objeto ByteBuffer consiste en introducirlos directamente utilizando uno de los mtodos put. con el fin de insertar uno o ms bytes. o v alores 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 vvrap( ). Cuando hacemos esto, no se copia la matriz subyacente, sino que se utiliza 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 objete RandomAccessFile. Observe que debemos desplazar el objeto FileC hannel por el archivo; en el ejemplo, se le desplaza basta al final para poder aadir nueva informacin mediante escrituras adicionales.

Para acceso de slo lectura, es necesario asignar explcitamente un objeto ByteBuffer utilizando el

i 18 Entrada/salida 1640 miodo esttico alloca- te( ). 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 IK utilizado aqui 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 alloeateDircct( ) en lugar de allocaio(), con el fin de generar un huffer directo" que pueda estar acoplado de forma an ms estrecha con el sistema operativo. Sin embargo, el gasto adicional de procesamiento de dicho tipo tic asignacin es mayor, y la implementacin varia de un sistema operativo o otro, asi que, de nuevo, ser necesario experimentar con cada aplicacin para determinar si un huffer directo permite obtener una ventaja de velocidad.

Despus de invocar read ) para decirle al objeto FileChannel que almacene bytes en el objeto ByteBuffer, es necesario invocar flip( ) en el huffer con el fin de que ste se prepare para la extraccin de los bytes que contiene (como puede ver. el mecanismo parece un poco rudimentario, 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 huffer para operaciones read() adicionales, tendramos tambin que invocar clcar( ) para preparar el huffer para cada read( ) Podemos ilustrar esto mediante un sencillo programa de copia de archivos: ff: io/ChannelCopy.j ava ff Copia en un archivo utilizando canales y buffers // (Args: ChannelCcpy.java test.txt) import java.nio,; import j ava. nio.channels.; import } ava. ia. ; public class ChannelCopy { private static final int BSIZE = 1024; public static void main(String[] args) throws Exception ( if(args.length 1= 2) { System.out.println("arguments: sourcefile destfile"); System.exit(1) ; ) FileChannel in = new FilelnputStream(args[0]).getChannelO, out

i 18 Entrada/salida 1641 = new FileOutputStream(argsfl]).getChannel(); ByteBuffer buffer = ByteBuffer.allocate(BSIZE); while(in.read(buffer 1 -1) { buffer. flip (} ,* // Preparacin para la escritura out.write(buffer; buffer.clearO;// PreDaracin para la lectura

>

} ///:-

Como puede ver. se abre un objeto FileChannel para lectura y otro para escritura. Se asigna un objeto ByteBulTer, y cuando FLleChanuel.read() devuelve 1 (una reminiscencia, sin lugar a duda, de Unix y C), querr decir que hemos alcanzado el final del flujo de datos de entrada. Despus de cada read(), que inserta datos en el buffer. flip( ) prepara el buffer para poder extraer la informacin con una Humada a write( ). Despus de la ejecucin write(). la informacin continuar estando en el buffer, y clear() permitir reinicializar todos los punteros internos para que el buffer quede listo para aceptar datos durante otras llamadas a read().

i 18 Entrada/salida 1642 Sin embargo, el programa anterior no es la forma ideal de gestionar este tipo de operacin. Dos mtodos especiales transferTo( ) y transferFrom( ) permiten conectar directamente un canal con otro: //: io/TransferTo.java // Utilizacin de transferTo(i entre canales // (Aros: TransferTo.java TransferTo.txt} import j ava.nio.channe1 s. ; import java.io.*; public clase TransferTo ( public static void main(String13 args) throws Exr*ption ( if(args.length 2) System.exit l) / {

System.out.println("argumenta: sourcefile destfile");

i FileChannel in = new FilelnputStream (args [0] ) .getChannel' ) , out = new FileOutputStream(args11]).getChannel( ); in. transferTo (.0, in.sizeO, out); // O bien: // out.transferFrom(in, 0, in.sizeO);

} ///:-

i 18 Entrada/salida 1643 No vamos a tener que hacer csic upo de cosas muy a menudo en nuestras tareas de programacin, pero resulta conveniente conocerlas. Conversin de datos

Si volvemos a examinar GetChannel.java, observaremos que para imprimir la informacin del archivo, estamos extrayendo los datos de byte en byte y proyectando cada byte sobre un valor char. Esto parece un tanto primitivo: si examinamos la clase java.nlo.CharBuffer. veremos que dispone de un mtodo toString() que dice "Quiero que me devuelvas una cadena de caracteres que contenga los caracteres de este buffer". Puesto que un objeto ByteBuffer puede manipularse como un huffer de tipo CharBuffer mediante el mtodo asCharBufler( ). por qu no usar dicho mtodo? Como puede ver analizando la primern linea de la salida del siguiente ejemplo, dicha solucin no funciona: //; io/BufferToText.java // Conversin de texto para objetos ByteBuffer import java.nio.*; import java.nio.channels.*; import java.nio.charset.; import j ava.10.; public class BufferToText ( private static final int BSIZE 1024; public static void main(StringU args) throws Exception ( FileChannel fc = new FileOutputStreamCdata2.txt") .getChannel (); fc.write 1 ByteBuffer wrap ("Some text'* .getBytes() > ) ; fc.close(); fc = new FilelnputStreami"data2.txt").getChannel(); ByteBuffer buff = ByteBuffer.allocate(BSIZEJ; fc.read(buff;; buff.flip{); U No funciona: System.out.println(buff.asCharBuffer{)); // Decodificar usando el conjunto de caracteres // predeterminado de este sistema: buff.rewind( ) String encoding = System.getProperty("file.encoding**);

i 18 Entrada/salida 1644 Sys tem. out. print In "Decoded using -+ encoding -f n : " + Charset.forName(encoding).decode(buff)); // Q bien, podemos codificar con algo que permita imprimir: fc * new FileOutputStreamwdata2.txt").getChannel(); fc.write(ByteBuffer.wrap( -Some text' .getBytes ( "TF-16BE") )) ; fc.close( ) i // Ahora intentamos leer de nuevo: fc = new FilelnputStream("data2.txt").getChannelO; buff.clear(); fc.read(buff1j buff.flip( ) ; System.out.println(buff.asCharBuffer()); // Utilizar un objeto CharBuffer a travs del cual escribir: fc = new FileOutputStream(Mdata2.txt").getChannel(); buff - ByteBuffer .allocate (24) fc.write(buff); fc.close(); // Leer y visualizar: fc = new FilelnputStream("data2.txt").getChann elO; buff.clear ); fc.read(buff); buff.flip(); System.out.printIn(buff.asCharBuffer()); // M3 que suficiente buf f. asCharBuf fer () .put("Some text**) ;

} } / Output:

i 18 Entrada/salida 1645 ???? Decoded using Cpl252: Some text Some text Some text *///:-

El buffer contiene bytes -*>in formato, y para transformarlos en caracteres debemos codificarlos a medida que los introducimos (para que tengan significado cuando se los extraiga) o decodiflcarlos a medida que salen del buffer. Esto se puede conseguir utilizando la clase javu.no.charset.Charset, que proporciona herramientas para la codificacin en muchos tipos distintos de conjuntos de caracteres: //: io/AvailableCharSets.java // Muestra los conjuntos de caracteres y sus alias import java.nio.charset; import java.utll.*; import static net.mindview.til.Prlnt.*; public class AvailableCharSets ( pubiic static void mainlString] args) ( SortedMap<String,Charset> charSets = Charset.availableCharsets{\/ Iterator<String> it * charSets.keySet().iteratorO; while(it.hasNextO) ( String csName * it.nextOr printnb{csName; Iterator aliases charSets .ge t (csName) .aliases (1 .iteratorO ; if (aliases.hasNext()) printnb fH: M); while(aliases.hasNext()) { printnb (aliases.next () ) ,* if(aliases.hasNext()) printnb(", " ) ;

prlnt ()

i 18 Entrada/salida 1646 )

) } /* Output: BigS: csBigS Big5-HKSCS: big5-hkscs. blg5hk, big5hkscs:Unicode3.0. big5hkscs, Big5_HKSCS EUC-JP: eucjis, x-eucjp, csEUCPkdFmtjapanese, euc;jp. Extended_UNIX_Code_Packed_Format_for_Japa nese. x-euc- j p, euc_jp EUC-KR: ksc5601, 5601, ksc5601_19S7, ksc_5601f ksc5601- 1987, euc_kr, ks_c_5601-1987, euckr, csECKR GB10O3Q: gbl8Q30-2000 GB2312: gb2312-198Q, ab2312, EUC_CN, gb2312-80, euccn, euccn. x-EUC-CN GBK: windows-936. CP936

Por tanto, volviendo a Buffer loTcxl.ja va. si rebobinamos el buffet ton rewind() (para retroceder al principio de los datos) y a continuacin utilizamos el conjunto de caracteres predeterminado de esa plataforma para decodificar los datos con deco- de( ). el objeto de buffer resultante CharBuffer podr imprimirse sin problemas en la consola. Para averiguar el conjunto de caracteres predeterminado, use System.getProperty(,,file.encoding,,)f que genera la cadena de caracteres que da nombre al conjunto de caracteres. Pasando este nombre a Charset.forName( ) se genera el objeto Charset (conjunto de caracteres) que puede utilizarse para decodificar la cadena.

Otra alternativa consiste en codificar (con encode( )) utilizando un conjunto de caracteres que permita obtener algo que pueda imprimirse en el momento de leer el archivo, como podemos ver en la tercera pane del archivo HuferToText.java. Aqui, se utiliza UTF-I6BE para escribir el texto en el archivo, y

i 18 Entrada/salida 1647 en el momento de leerlo, todo lo que hay que hacer es convertirlo a un objeto CharBuffcr. 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 (analizaremos este tema con ms detalle posteriormente). Observe que asignamos 24 bytes para el objeto ByteBuffer Puesto que cada 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 toString( ). como puede verse en la salida.

Ejercicio 23: (6)Creeypruebeunmtodode utilidad para imprimir el contenido de un objeto CharBuffer hasta 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 valores primitivos a partir de los bytes que contiene. Este ejemplo ilustra la insercin y la extraccin de varios valores empleando dichos mtodos: //: io/GetData.java

i 18 Entrada/salida 1648 // Obtencin de diferentes representaciones // a partir de un objeto ByteBuffer import java.nio.*; import static net.raindview.util.Print. *; public class GetData ( private static final int BSIZE * 1024; public static void main(StringJ argsl { ByteBuffer bb = ByteBuffer.allocate(BSIZEJ; // El proceso de asignacin pone a cero // automticamente el objeto ByteBuffer: int i = 0; while(i+4 < bb.limitO) lf(bb.get() != 0) print("nonzero" ) ; print ("i = " + i) i bb.rewind(); // Almacenar y leer una matriz char: bb.asCharBuffer().put("Howdy 1M)/ char c; while({c = bb.getChart)) t 0) printnblc * " print I) ; bb.rewind()? // Almacenar y leer un valor short: bb.asShortBuffer().put((short i 471142); print(bb.getShort I ) ) ; bb.rewind 11; // Almacenar y leer un valor int: bb.asIntBuffer().put(99471142); print (bb.getInt() ); bb.rewind(); // Almacenar y leer un valor long: bb.asLongBuffer().put(95471142); print(bb.getLong )); bb.rewind(); // Almacenar y leer un valor float: bb.asFloatBufferO.put(99471142); print(bb.getFloat()); bb.rewind(); / / Almacenar y leer un valor double: bb.asDoubleBuffer(J .put(99471142); print bb.getDouble \ )) ,* bb.rewind( )

i 18 Entrada/salida 1649 ) ) / 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. asi sucede. Se comprueban los 1.024 valores (hasta e! limite de buffer obtenido con limit( )). y veremos que todos ellos son cero.

La forma 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 primitivos. F.l nico de estos casos que resulta un poco ms extrao es el mtodo put( ) para el objeto ShortBuffer. que requiere una proyeccin 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 buffers utilizados como vistas permiten examinar un objeto ByteBuffer subyacente a travs de la ventana que proporciona 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 cmodamente tipos primitivos en un buffer de tipo ByteBuffer Una vista permite tambin leer tipos primitivos a partir de un objeto ByteBuffer. bien de uno en uno (tal como lo permite ByteBuffer) o por lotes (almacenando en matrices). He aqui un ejemplo en el que se manipulan objetos int en un objeto ByteBuffer a travs de una vista Int Buffer:

i 18 Entrada/salida 1650 //: io/IntBufferDemo.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[J arga) { ByteBuffer bb = ByteBuffer.allocate(BSIZE) ; IntBuffer ib = bb.asIntBuffer(); // Almacenar una matriz de valores int: ib.put(new int[]{ 11, 42, 47, 99, 143, 311, 1016 )}; // Lectura y escritura en posiciones absolutas: System.out.println(ib.get(3)) ? ib.put(3. 1811); // Establecimiento de un nuevo lmite antes rebobinar el buffer. ib.flip() ,* while{ib.hasRemaining()) ( int i * ib. get () ; System.out.println(i);

i 99 11 42 47

) /* Output:

1811 143 811 1016 ///:-

i 18 Entrada/salida 1651 Se utiliza primero el mtodo sobrecargado put( ) para almacenar una matriz de valores int Las siguientes llamadas a los mtodos get() y put( ) acceden directamente a una posicin int dentro del objeto ByteBuffer subyacente. Observe que estos accesos mediante la posicin absoluta estn tambin disponibles para los tipos primitivos si manipulamos directamente el objeto ByteBuffer.

Una vez rellenado el objeto ByteBuffer con objetos int o algn otro tipo primitivo a travs de un huffcr de vista, podemos escribir el objeto ByteBuffer directamente en un canal. Tambin podemos, con igual facilidad, 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. float. long y double generando diferentes buffets de vista para el mismo objeto ByteBuffer. //: io/ViewBuffers.java import j ava.nio.; import static net.mindview.til.Print.*; public class ViewBuffers ( public static void mainlString] argsj ( ByteBuffer bb = ByteBuffer.wrap( new byte[] ( 0, 0, 0, 0. 0, 0, 0, a' bb.rewind(); printnb ("Byte Buffer ); while(bb.hasRemain ing )) printnb (bb .posit ion () " -> " + bb.getO ", print () CharBuffer cb = ((ByteBuffer)bb.rewind()).asCharBuffer (); printnb("Char Buffer "); while (cb. hasRemaining ()) printnb (cb .posit ion () print(); PloatBuffer fb = ((ByteBuffer)bb.rewind()).asFloatBuffe r(); printnb("Float Buffer while(fb.hasRemaining()) printnb(fb.positiond 4 -> " + fb.get) + print(); IntBuffer ib = ") ; +cb.getO 4 ", M); )j;

i 18 Entrada/salida 1652 ((ByteBuffer)bb.rewindO 1.asIntBufferO; printnb<"Int Buffer M); while(ib.hasRemaining()) printnb (ib.position() *-> * ib.getO ", ) ; print0; LongBuffer Ib = ((ByteBuffer)bb.rewind()).asLongBuffer {); printnb("Long Buffer "); while(Ib.hasRemaining()) printnb(Ib.position(>+ " -> " + Ib.getO * ", "); print(); ShortBuffer sb = ((ByteBuffer)bb.rewind()).asShortBuffe r(); printnb("Short Buffer M); while(sb.hasRemaining()) printnb(sb.position() " -> " sb.getO ", "); print(); DoubleBuffer db = ((ByteBuffer)bb.rewind()> .asDoubleBuffer(); printnb(HDouble Buffer "); whlle i.db.hasRemalning ()) prntnb (db.positionO-* " - > * * db.getO t- ", *);

) ) /* Otput: ByteBuffer 0 -> 0, 1 > 0, 2 -> 0, 3 -> 0. 4 -> 0, 5 97, Char Buffer 0 -> . 1 -> , 2 -> Float Buffer 0 -> 0.0, 1 -> 1.36E-43, Int Buffer 0 -> 0. 1 -> 97. Long Buffer 0 -> 97, Short Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 97, Doubie Buffer 0 -> 4.8E-322, - 0, 6 -> 0, 7 ->

, 3 -> a,

i 18 Entrada/salida 1653 *///:-

El objeto BytcBuffcr se genera 'envolviendo una matriz de ocho bytesque visualiza a travs de buffers

continuacin

se

de vista apropiados para todos los diferentes tipos primitivos. Podemos ver en el siguiente diagrama las distintas formas en

que los datos aparecen cuando se los lee desde los diferentes tipos de buffers:

Estos valores se corresponderan con la salida del programa.

i 18 Entrada/salida 1654 Ejercicio 24: (1)Modifique IntBuffcrDcmo.java para utilizar valores douhle. Terminaciones

Las diferentes mquinas pueden utilizar diferentes esquemas de ordenacin de los bytes a lu hora de almacenar los datos. Las mquinas con terminacin alta" [hig endian) colocan el byte ms significativo en la direccin de memoria ms baja, mientras que las mquinas con terminacin baja ittle endiari) colocan el byte ms significativo en la direccin de memoria ms alta.

A la hora de almacenar una magnitud con un tamao superior a un byte. como por ejemplo int. float. etc., puede ser necesario tener en cuenta la ordenacin de los bytes. Un objeto ByteBufTer almacena los datos en formato de terminacin alta y los datos enviados a travs de una red siempre utilizan terminacin alta. Podemos cambiar el tipo de terminacin de un objeto ByteBufTer utilizando ordcr( ) y pasando a dicho mtodo el argumento ByteOrder.BlG_ENDIAN o By t eO rd c r. LITTLEE NDIA N.

Considere un objeto ByteBufTer que contenga los siguientes dos bytes

b l

b 2

Si leemos los datos como un valor short (ByteBuffer.asShortBuffert )). obtendremos el nmero 97

i 18 Entrada/salida 1655 (OOQOQOOO 01100001), pero si cambiamos a terminacin baja, obtendremos el nmero 24832 <01100001 00000000)

le aqu un ejemplo que muestra cmo se modifica la ordenacin de los bytes en los caracteres dependiendo de la terminacin elegida: //: io/Endians.j ava // Diferencias de terminacin y almacenamiento de datos, import java.nio.*/ import java.til.*; import static net.mindview.til.Print.*; public class Endians ( public static void main(Stringl] args) ( ByteBuffer bb = ByteBuffer.wrap(new byte[12])/ bb.asCharBufferO.put("abedefM); print (Arrays.toString(bb.arrayo )) ; bb.rewi nd{), bb.order{ ByteOrder.BIG_ENDIAN>; bb.asCharBuffer(J.put("abedef"); print(Arrays.toString (bb. arrayO) ) ,* bb.rewind(); bb. order (ByteOrder. L,IT7LE_ENDIAN I ; bb.asCharBuffer().putiabedefH ) ; print (Arrays. toStrmg (bb. array 01);

} ) /* Output: [0, 97, 0, 98,0, 99, [0, 97, 0, 98,0, 99, 197, 0. 98, 0,99, 0, 0, 100, 0, 100, 100, 0, 0, 101, 0, 101, 101, 0, 0, 102] 0, 102] 102, Q]

i 18 Entrada/salida 1656 ///:-

Al objeto ByteBuffer se le asigna suficiente espacio para almacenar todos los bytes de una matriz de caracteres, de modo que podemos invocar el mtodo urray( ) para visualizar los bytes subyacentes. El mtodo array() es opcional y slo se puede invocar sobre un bu/fer que est respaldado por una matriz; en caso contrario, se generar una excepcin l'nsuppnrtedOperationExccption.

Al visualizar los bytes subyacentes, podemos ver que la ordenacin predeterminada coincide con la impuesta por el sistema de terminacin alta, mientras que el sistema de terminacin baja invierte los bytes. Manipulacin de datos con buffers

El diagrama de la pgina siguiente ilustra las relaciones entre las elases nio. para poder entender mejor cmo se transfieren y se convierten los datos. Por ejemplo, si queremos escribir una matriz de tipo byte en un archivo, tenemos que envolver la matriz byte utilizando el mtodo ByteBuffer.wrap( ). abrir un canal en el flujo FileOutputStream usando el mtodo getChnnnel( ) y luego escribir los datos en el canal FileChannel a partir de objeto ByteBuffer.

Observ e que B>teBuffer es la nica forma de transferir datos hacia y desde los canales y que nosotros slo podemos crear un buffer autonomo con un tipo de dalos primitivo, 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 primitivo en un objeto ByteBuffer Sin embargo, puesto que podemos transferir datos

i 18 Entrada/salida 1657 primitivos hacia y desde un objeto B>teBuffer a travs de un buffer de vista, esta restriccin realmente no es tal. Detalles acerca de los buffers

18 Entrada/salida 1658

Un objeto Buffer est compuesto por datos y por cuatro ndices que permiten acceder a estos datos y manipularlos eficientemente: marca, posicin, limite y capad Jad. Existen mtodos para asignar valores a estos ndices, para reinicial izarlos y para consultar su valor (vase la tabla de las Paginas 626-627).Utilidades Canales

getChannel()

map(FileChannel MapMode. position, size) MappedByteBuffer Aparece en el espacio de direcciones del proceso array()/got(byte[]) byte[] wrap(byteO) asCharBuffer() charQ array()/get(char[] )wrap(charU) array()/get(doubleQ) asDoubleBuffer() doublef] DoubleBuffer wrap(double{J) array()/get(floatfl) asFloatBuffer() FloatButfer wrap(fioatQ) array()/get(mtQ) aslntBuffer() intfl IntBuffer wrap(int) array()/get(longQ) asLongBuffer() long[] (: LongBuffer wrap(long[]) array()/get(shortQ) asShortBuffer() short [] ShortBuffor wrap(short(]) floatQ

18 Entrada/salida 1659

A codificado encode(CharBuffer) A

i----Codificacin/decodificacin utilizando ByteBuffor -

un flujo de bytes decode(ByteBuffer) f De un flujo de bytes codificado

Los mtodos que insertan y extraen datos del buffer actualizan estos ndices para reflejar los cambios.

Este ejemplo utiliza un algoritmo muy simple (intercambio de los caracteres adyacentes) para cifrar y descifrar los caracteres contenidos en un objeto CharBuffer: //: io/UsingBuffers.java import java.nio.*;

18 Entrada/salida 1660

import static net.mindview.til.Print. ; public class UsingBuffers ( private static void symmetricScramble(CharBuffer buffer) { while(buffer.hasRemaining()) ( buffer.mark(); char el = buf fer .get () ; char c2 buffer.getO; buffer.reset(); buf fer. put (c2) .put (d) r j

) public static void main(Stringf] args) { charU data = "UsiagBufers".toCharArray(); ByteBuffer bb = ByteBuffer.aliocatefdata.length 2); CharBuffer cb = bb.asCharBuffer(}; cb.put idata); print(cb.rewind()); symmetricScramble(cb); print(cb.rewind()) j

symm#t ri cScr^mhT f (rb) ; print(cb.rewind1)1/

>

18 Entrada/salida 1661

) / Qutput: UsingBuffers sUniBgfuefsr UsingBuffers

///:-

Aunque podramos generar un buffer de tipo C harBuffer directamente invocando wrap( ) con una matriz de tipo char, asignamos en su lugar un objeto B\ teBuffer subyacente, generndose el objeto CharBuffer como una vista del ByteBuffer F.ste 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 symmetrcScramble( )

18 Entrada/salida 1662

La posicin apunta al primer elemento del buffet-, y la capacidad y el limite apuntan al ltimo elemento.

En symmctricScramblc ), el bucle while realiza una serie de iteraciones hasta que posicin es equivalente a limite. La posicin del buffir varia cada ve/, que se invoca una funciona get( ) o put( ) relativa. Tambin se pueden invocar mtodos get() y pui( ) absolutos, que incluyen un argumento de ndice que especifica In ubicacin en la que la operacin get( ) o put( ) tienen lugar. Estos mtodos no modifican el valor de la posic in del buffet'. | mar |

Cuando el control entra en el bucle while. el valor de mem a se fija utilizando una llamada a marki ), El estado del buffet es entonces: EE] ng pos DED

Las dos llamadas a get( ) relativas guardan el valor de los dos primeros caracteres en las

18 Entrada/salida 1663

variables el y c2. Despus de estas dos llamadas, el buffet- tendr el siguiente aspecto | mar | Para realizar el intercambio, necesitamos escribir c2 en posicin = 0 y el en posicin = l Podemos emplear el mtodo absoluto de insercin para conseguir esto, o asignar a posicin el valor de man a, que es precisamente lo que hace el mtodo resel( ): | mar |

li m I^PI r | pos I

Los dos mtodos pul( ) escriben c2 y luego el | nw |

|psi GD

18 Entrada/salida 1664

I^PI

Durante la siguiente iteracin del bucle, se asigna a mana el valor actual de posicin: | mar | [J]

| cap |

El proceso contina hasta que se ha recorrido todo el buffer. Al final del bucle while. posicin apuntar a] final del buffer. Si imprimimos el buffer. slo se imprimirn los caracteres entre posic in y imite. Por tanto, si queremos mostrar el contenido completo del buffer. deberemos fijar la posicin al principio del buffer utilizando re\vind( ). He aqui el estado del buffer despus de la llamada a rovind ) (el valor de nunca pasa a ser indefinido): pos Cuando se invoca de nuevo la funcin symmetricScramble ). el buffer C'harBuffer pasa por el mismo proceso y se restaura a su estado original Archivos mapeados en memoria

l os archivos mapeados en memoria permiten crear y modificar archivos que sean demasiado grandes como para cargarlos en memoria Con un archivo mapeado en memoria, podemos actuar como si todo el archivo se encontrara en la memoria y podemos acceder a l tratndolo simplemente como si fuera una matnz de muy gran tamao. Esta tcnica simplifica enormemente el cdigo que es necesario escribir para poder modificar el archivo. He aqui un ejemplo simple //: io/LargeMappedFiles.java // Creacin de un archivo de muy gran tamao // utilizando el mapeado de memoria. // {RunByHand) import j ava.nio.*; import java .nio.charinels . ; import java.io.*; import static net.mindview.util.Print.*; public class LargeMappedFiles { static int length * OxSFFFFFF; // 128 MB public static void main{String[1 args) throws Exception { MappedByteBufter out new RandomAccessFile("test.dat, MrwM).getChannelt)

.map(FileChannel.MapMode.READ_WRITE, 0, length); forint i = 0; i < length; i++) out.put(Ibytej 'x' J; print ("Finished writing"! ,* for(int i = length/2; i < length/2 + 6; ++) printnb((char>out.aetli I);

} ///:-

Para efectuar tanto lecturas como escrituras, comenzamos con un objeto RandomAccessFile. obtenemos un canal para dicho archivo y luego invocamos map( ) para generar un buffer MappedByteBuffcr. que es un tipo particular de buffer directo. Observe que es necesario especificar 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.

MappedByteBufTer hereda de ByteBuffer. asi que dispone de todos los mtodos de dicha clase Aqu slo mostramos los usos mas simples de put( ) y get( ), pero tambin podemos emplear mtodos como asCharBuffer( ). etc.

El archivo creado en el programa anterior tiene 128 MB de longitud, lo cual es un tamao probablemente mayor de lo que el sistema operativo permitir residir en memoria en cualquier momento determinado. El archivo parece estar completamente accesible, aunque en realidad slo se cargan en memoria partes del mismo, intercambindose por otras panes a medida que es necesario. De esta forma, 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. Rendimiento

Aunque el rendimiento del 'antiguo mecanismo de flujos de datos de E/S se lia mejorado al implementarlo con nlo, el acceso a archivos mapeados tiende a ser muchsimo ms rpido. Este programa hace una comparativa simple de rendimiento: //i io/MappedIO.java import java.nio.*; import java.mo.channels. * ; import java.io.*; public class MappedIO { private static int numOflnts 4 000Q00; private static int numOfbuffInts = 200000; private abstract static class Tester { private String name; public Tester(String name) { this.name = name; } public void runTestO { System.out.print(name + ": "); try ( long start = System.nanoTime(); test () ; double duration <= System.nanoTime () - start; System.out .format ("%.2f\n", duration/1. 0e9) ; } catch(lOException e) { throw new Runt ime Except ion (e) ;

) public abstract void testU throws IOException;

} private static Tester[] tests = ( new Tester("Stream Write") ( public void test(> throws IOException { DataOutputStream dos = new DataOutputStreaml new BufferedOutputStream( new FileOutputStream(new File("temp.tmp")))); for{int i = 0; i < numOflnts; i+-0 dos.writelnt(i) ; dos.closel);

} ), new Tester("Mapped Write") { public void testO throws IOException { FileChannel fc = new RandomAccessFile("temp.tmp", "rw"> .getChannel() IntBuffer lb fc.mapf

FileChannel .MapMode.READ_WRITE, 0, fc.sizeO) .asIntBuffer(); for(int i = 0; i < numOflnts; i++) ib.put{i); fc.close();

) ). new Tester("Stream Read") { public void teBtt) throws IOException ( DatalnputStream dis = new DataInputStream( new BufferedlnputStream! new FileInputStream("temp.tmp" ))); for (int i = 0; i < numOflnts; i+4) dis.readlnt O; dio.close()j

>

). new Tester I"Mapped Read} { public void test!) throws IOException { FileChannel fc * new FilelnputStream( new File("temp.trap")I.getChannelO;

IntBuffer ib = fc.map( FileChannel .MapMode. READ_ONLY, 0. fc.sizeO) .asIntBuffer(); whi 1 e ( ib. hasRemaming () ) ib.get 0; fc.close{)f

} h new Tester("Stream Read/Write") ( public void test!) throws IOException { RandomAccessFile ra new RandomAccessFilei new File " temp, tmp") , "rw"! ,* raf.writelnt(1); forint i * 0; i < numOfUbuffInts; i++) { raf.seek(raf.length() - 4); raf.writelnt(raf.readlnt());

} raf.close();

) }. new Tester("Mapped Read/Write") { public void testO throws IOException {

FileChannel fc * new RandomAccessFile( new File("temp.tmp"), "rw") .getChannel 0; IntBuffer ib fc.mapi FileChannel.MapMode.READ_WRITE, 0, fc.sizeO) .asIntBufferO; ib.put(0J; forlint i = 1; i < numOfUbuff Ints ib.put(ib.getti - I) ) ; fc.close 0;

J; public static void main(String[J args> ( for(Tester test : tests) test.runTest{);

) / Output: (901 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 *///:-

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. C ada una de estas subclases realiza un tipo de prueba, por lo que los mtodos test( ) tambin nos proporcionan un prototipo para realizar las distintas actividades de E/S.

Aunque podra parecer que una escritura mapeada debera utilizar un flujo FileOutputStream. todas las operaciones de salida en el mecanismo de mapeo de archivos deben utilizar un objeto RandomA ccessFHe, al igual que se hace con las operaciones de lectura escritura en el programa anterior

Observe que los mtodos te$t( ) incluyen el tiempo necesario para metalizar los distintos objetos de ES. de modo que aunque la configuracin de los archivos mapeados puede requerir un gasto de procesamiento considerable, la ganancia global de velocidad, por comparacin con la E/S basada en flujos de datos resulta significativa.

Ejercicio 25: (6)Experimentecambiandolas instrucciones ByteBuffer.allocate() de los ejemplos de este

captulo por

BvteBiiffer.allocafcDirect(). Demuestre las diferencias de rendimiento que existen, pero observe tambin si el tiempo de arranque de los programas se modifica de manera perceptible.

Ejercicio 26: (3)Modifique strlngs/JGrep.java para utilizar archivos mapeados en memoria al estilo nio de Java. Bloqueo de archivos

F1 bloqueo de archivos permite sincronizar el acceso a un archivo utilizado 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 programacin Java y la otra puede ser alguna hebra nativa del sistema operativo. Los bloqueos de archivo son visibles para otros procesos del sistema operativo, porque el mecanismo de bloqueo de archivos de Java se mapea directamente sobre la ftineionalidad de bloqueo nativa del sistema operativo.

He aqu un ejemplo simple de bloqueo de archivos. //: lo/FileLock ir.g. java import java.nio.channels.; import java.util.concurrent.*. import java.io,*;

public elass FileLocking { public static void main(StringU args) throws Exceptiar* ( FrleOutputStream fos= new Flle0utput5tream(''flle.txt,,) ; FileLock fl fos.getChanneH) .tryLockO ; iftfl 1= nuil) ( System.out.println("Locked File"); Tiroenit.MILLISECONDS.sleep(100)/ f1.release(),* System.out.printin{"Released Lock"); i i fos.cise(i

) / Gutput: Locked File Released Lock ///:-

Podemos obtener un bloqueo (FileLock) sobre el archivo completo invocando tryLock( ) o lock() sobre un objeto FileChannel. (SocketChannel. DatagramC hannel y ServerSocketChannel no necesitan bloqueos, ya que son inherentemente 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 lia establecido el mismo bloqueo y ste no es de tipo compartido), simplemente se limita a volver del mtodo terminando asi la llamada. lock( ) se bloquea hasta que adquiere el bloqueo indicado, o hasta que se interrumpe la hebra que ha invocado lock( ),

hasta que se cierra el canal para el cual se ha invocado el mtodo loek( ). Un bloqueo se libera utilizando FileLock. release( ).
0

Tambin es posible bloquear una pane del archivo con: tryLockUona posicin, long tamao, boolean compartido)

o lock(long posicin, long tamao, boolean compartido)

que bloquea la regin (tamao - posicin). 1 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 posicin + tamao y el archivo se incrementa ms all de posicin + tamao, entonces la seccin situada despus de posicin + tamao no estar bloqueada. Los mtodos de bloqueo que no utilizan argumentos bloquean el archivo completo, incluso si ste aumenta de tamao.

El soporte para los bloqueos exclusivos o compartidos debe ser proporcionado 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 bloqueo exclusivo. El tipo de bloqueo (compartido o exclusivo) puede consultarse utilizando FiloLock.*Sliari*d( ) Bloqueo de partes de un archivo mapeado

Como hemos mencionado anteriormente, el mecanismo de mapeo de archivos se utiliza principalmente para archivos de muy gran tamao. Puede que necesitemos bloquear partes de dicho archivo de gran tamao, de modo que se permita a otros procesos modificar partes del archivo no bloqueadas. Esto es lo que sucede, por ejemplo, con una base de datos, de lal manera que sta pueda ser utilizada por muchos usuarios a la vez.

He aqu un ejemplo con dos hebras de programacin, cada una de las cuales bloquea una parte distinta de un archivo: //: io/LockingMappedFiles.java /! Bloqueo de partee de un archivo mapeado. // (RunByHand) import ]ava.nio.#; import java.nio.channeis.; import java.ia.*; public class LockmqMappedFiles ( static final int LENGTH 0x8FFFFFF; // 128 MB static FileChannel fc,* public static void mam (String [] args) throws Exception ( fc * new RandomAccessFilelMtest.dat", "rw").getChannel(); MappedByteBuf fer out = f c.map(F i1eChanne1.MapMode.READ_WRITE, 0, LENGTH1; fordnt i * 0; i < LENGTH; out.put v fbyte] 'x') . new LockAndModify lout. O, 0 + LENGTH/3) / new LockAndModify(out, LENGTH/2, LENGTH/2 LENGTH/4);

prvate static class LockAndModify extends Thread ( prvate ByteBuffer buff/ prvate int start, end; LockAndModify(ByteBuffer mbb, int start, int end i { this.start = start; this.end - end; mbb.limit(end) ; mbb.position(start); buff * mbb.sliceO ; start(); i public void run() { try { // Bloqueo exclusivo sin solapamiento: FileLock fl = fc-lock(start, end, false);

System.out.printIn("Locked: " start *" to M+ end; // Realizar modificacin: while (buff .positior* O < buff. limitO - 1) buff.put((byte)(buff.getO + 1)); fl. release {> ,* System.out .printin "Released: M+8tart+" to end); } catchIIOException ej { throw new Rur.timeExceptian(e) ;

} ///:-

La clase de hebra LockAndModify configura la regin del buffer y crea con slice( ) un fragmento para modificarlo. En ritn( ). se establece el bloqueo sobre el canal del archivo (no se puede establecer un bloqueo sobre el hnffcr, slo sobre el canal). La llamada a lock( ) es muy similar a establecer un bloqueo de un objeto en una hebra de programacin: despus de la llamada dispondremos de una seccin critica con acceso exclusivo a dicha parte del archivo.5

Los bloqueos se liberan automticamente cuando termina la ejecucin 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 objeto HleLock como se muestra en el ejemplo. Compresin

La biblioteca de E/S de Java contiene clases que permiten manejar flujos de lectura y escritura en formato comprimido. Estas clases se envuelven en otras clases de E/S para proporcionar la funcionalidad de compresin.

Estas clases no derivan de las clases Reader y YVriter. sino que forman parte de las jerarquas InputStream y OutputSfrcam Esto se debe a que la biblioteca de comprensin trabaja con bytes, no con caracteres. Sin embargo, a veces podemos vemos forzados a mezclar los dos tipos de flujos (recuerde que puede utilizar InputStrcamReadcr y OutputStreamWriter para proporcionar un mecanismo sencillo de conversin entre un tipo y otro).

Aunque existen muchos algoritmos de compresin, Zip y GZIP son posiblemente los ms comnmente utilizados. De este modo, podemos manipular fcilmente los datos comprimidos con muchas de las herramientas disponibles para la lectura y escritura de estos formatos de archivo. ' Puede encontrar mas detalle* acerca de la* hebra* de programacin en el Capitulo 21, Concurrencia. Compresin simple con GZIP

La interfaz GZIP es simple y resulta, por tanto, apropiada cuando disponemos de un nico llujo de datos que queramos comprimir (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 // (Args: GZIPcompress.java) irnport java.til.zip.; import j ava. io.; public class GZIPcampress ( pubiic static void main( String [] args) throws IOException { if(args.length == 0) { System.out.println( "Usage: NnGZIPcompress file\n" "\tUses GZIP compresson. to compresa ' + Hthe file to test.gz")? System.exit ti);

} BufferedReader in = new BufferedReader( new FileReader(args[01 ));

BufferedOutputStream out = new BufferedOutputStreamI new GZIPGutputStream/ new PileOutputStreamltest.gz"))); System.out.println("Writing filen); int C; whileHc * in.readO) ~ -1) out. write (c) ; in.cise O out.cise(); System.out.println("Reading flie"); BufferedReader in2 * new BufferedReaderI new InputStreamReader (new GZIPInputStream( new FileInputStream("test.gz")))); String s; while(<s = in2.readLineO) t* nuil) j System.out.println(s) *

| /* (Execute to see output) ///:-

La utilizacin de las clases de compresin resulta sencilla: basta con envolver el tlujo de salida en un objeto GZIPutput- Stream o ZipOutpufStream. y el flujo de datos de entrada en un objeto GZIPInputStream o ZiplnputStream Todo lo dems son lecturas y esenturas de E/S normales. Bste es un ejemplo de mezcla de los flujos de datos orientados a caracteres con los flujos de datos orientados a bytes; in utiliza las clases Readcr. mientras que el constructor de GZIP- OutputStream slo puede aceptar un objeto OutputStream. no un objeto VVritcr Cuando se abre el archivo, el objeto GZIPInputStream se convierte en un objeto Reader. Almacenamiento de mltiples archivos con Zip La biblioteca que sopona el formato Zip es ms amplia Con este formato, podemos almacenar fcilmente mltiples archivos y existe incluso una clase separada para facilitar el proceso de lectura de un archivo Zip. La biblioteca utiliza el formato Zip estndar, asi que funciona de forma transparente con todas las herramientas Zip que podemos descargar actualmente a travs de Internet. El siguiente ejemplo tiene la misma forma que el ejemplo anterior, pero permite tratar tantos argumentos de la linea 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 clases Checksum: Adler32 (que es la ms rpida) y CRC32 (que es ms lenta, pero ligeramente ms precisa).//; io/ZipCompress.java // utiliza compresin Zip para comprimir cualquier // nmero de archivos que se indique en la linea de comandos.

// (Args: ZipCompress.java) import java.util.zip.*; import java.io.*; import java.util.* i import static net.mindview.util.Print. ; public class ZipCompress ( public static void main(String[J args) throws lOException { FileOutputStream f = new FileOutputStream(test.zip") ; CheckedOutputStream csum = new CheckedOutputStream(f. new Adler32()); ZipOutputStream zos = new ZipOutputStream(csum); BufferedOutputStream cut * new BufferedOutputStream(zos); zos.setComment("A test of Java Zipping"); // Sin embargo, no hay un mtodo getComment) correspondiente, for(String arg : args) { print("Writing file " + arg); BufferedReader in = new BufferedReader(new FileReader(arg)); zos.putNextEntry (new ZipEr.try larg)) ; int c; while((c * an.read()) 1 -11 out.write(c); in.close(); out.flush);

) out.close(); // jLa suma de comprobacin slo es vlida // despus de cerrar el archivo! print (*Checksum: " csum.getChecksumO .getVaiue ()) ; // Ahora extraer los archivos: print tReading file"): FilelnputStream fi = new FiielnputStream("test.zip"); CheckedlnputStream csumi * new CheckedInputStream(fi. new Adler320) ZiplnputStream m2 new ZiplnputStream(csumi); BufferedlnputStream bis = new BufferedInputStream(in2i; ZipEntry ze; whlelze in2.getNextEntry()) 1= nuil) ( print I"Reading

file " * ze) ; int x; while Ux = bis.read(J) 1* -1) System.out.write x);

) if(args.length == 11 print(Checksum: " csumi.getChecksum().getVaiue()); bis.close); // Forma alternativa de abrir y leer archivos Zip: ZipFile zf new ZipFileCtest.zip**); Enumeration e = zf.entriesl); while(e.hasMoreEIements()) ( ZipEntry ze2 * (ZipEntry)e.nextEIementO ;printC'File: H + ze2) // ... y extraer ios datos como antes

) /* iIargs.length 1) */

) } /* fExecute to see output) *///t-

Para cada archivo que haya 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, tamaos comprimido y sin comprimir, fecha, suma de comprobacin CRC, campo adicional de datos, comentario, mtodo de compresin e indicador de si se trata de un directorio. Sin embargo, an cuando el formato Zip dispone de una forma para establecer una contrasea, esta caracterstica no est soportada en la biblioteca Zip de Java. V aunque C'heckedlnputStream y CheckedOutputStream soportan las sumas de comprobacion Adler32 y CRC32. la clase ZipEntry slo proporciona una interfaz para CRC. Esta es una restriccin del formato Zip subyacente, pero se trata de una restriccin que puede impedimos utilizar la clase \dler32 que es ms rpida.

Para extraer los archivos. ZipInputStream dispone de un mtodo getNextEntry( ) que devuelve la siguiente entrada ZipEntry. si es que existe alguna. Como alternativa ms sucinta, podemos leer el archivo utilizando un objeto ZipFile. que dispone de un mtodo entrics( ) para devolver un objeto de tipo Enumeration con las entradas del archivo Zip

Para leer la suma de comprobacin, debemos conseguir acceder de alguna forma al objeto Checksum asociado. En el ejemplo. retenemos una referencia a los objetos CheckedOutputStream y CheekedlnputStream. pero tambin podramos habernos limitado a conservar una referencia a) objeto Checksum

Un mtodo bastante absurdo dentro de la biblioteca Zip es sctComment(). Como se muestra en ZipCompress.java, podemos fijar un comentario a la hora de escribir un archivo, pero no existe forma de recuperar el comentario en el objeto ZipInputStream Parece que los comentarios slo se soportan de manera completa accediendo entrada por entrada mediante ZipEntry

Por supuesto, no tenemos por qu limitarnos a emplear archivos a la hora de utilizar las bibliotecas GZIP o Zip; podemos comprimir cualquier cosa, incluyendo datos que vayan a enviarse a travs de una conexin de red. Archivos Java (JAR)

El formato Zip tambin se utiliza en el formato de archivo JAR (Java ARchive), que es una forma de recopilar un gmpo de archivos en un nico archivo comprimido, igual que Zip. Sin embargo, como todos los dems componentes de Java, los archivos JAR son archivos interplataforma. as que no hay necesidad de preocuparse acerca de los problemas de portabili- dad. Pueden incluirse tambin archivos de audio y de imagen, adems de los archivos de clases.

Los archivos JAR resultan particularmente tiles a la hora de trabajar con Internet. Antes de que aparecieran los archivos JAR. el explorador web tenia que realizar solicitudes repetidas a un servidor web para poder descargar todos los archivos que conformaban un applet. Adems, estos archivos no estaban comprimidos, al combinar todos los archivos de un applet concreto en un nico archivo JAR. solo es necesaria una solicitud al servidor v la transferencia se realiza ms rpidamente, gracias a la compresin. Adems, la entrada de un archivo JAR puede estar firmada digitalmente par;i aumentar la seguridad.

Un archivo JAR esta compuesto por un nico archivo que contiene una coleccin de archivos comprimidos en formato 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 informacin acerca de los manifiestos JAR en la documentacin del JDK.

La utilidad jar incluida en el JDK de Sun comprime automticamente los archivos que elijamos. Esta utilidad se invoca mediante la linea de comandos: jar [opciones] destino [manifiesto] archivo(s)de entrada

Las opciones son simplemente un conjunto de letras (no hace taita ningn guin ni ningn otro smbolo indicador). Los usuarios de Unix Linux se percatarn de la similitud que existe con las opciones de tar. Las opciones disponibles son:

Si se incluye un subdircctorio dentro de los archivos que hay que insertar en el archivo JAR. dicho subdirectorio se aade automticamente incluyendo todos sus subdirectorios. etc. La informacin de ruta tambin se preserva.

He aqu algunas forma tpicas de invocar jar. El siguiente comando crea un archivo JAR denominado mvJarFile.jar que contiene todos los archivos de clases del directorio actual, junto con un archivo de manifiesto generado automticamente: jar cf myJarFile.jar *.class

Fl siguiente comando es como el del ejemplo anterior, pero aade un archivo de manifiesto creado por el usuario que se denomina myManifestFile.mf: jar cmf myJarFile.jar myManifescFile.mf vclass

Este otro comando genera una tabla de contenidos de los archivos en myJarFile.jar jar tf myJarFileoar

lin el siguiente comando se aade la opcin de salida verbosa, para proporcionar informacin mas detallada acerca de los archivos myJarFile.jar: jar tvf myJarFile.jar

Suponiendo que audo. classes c image sean subdirectorios. el siguiente comando combina todos los subdirectorios dentro del archivo m> App.jar. Tambin se incluye la opcin verbosa para obtener informacin adicional sobre un proceso mientras que est trabajando el programa jar jar cvf myApp.jar audio classes image

Si se crea un archivo JAR utilizando la opcin 0 (cero), dicho archivo puede incluirse en la variable CLASSPATH: CLASSPATH=*libl.j ar;1ib2.j ar;M

Con esto. Java podr explorar libI.jar v Iib2.jar en busca de archivos de clase.

La herramienta jar no es de propsito tan general como una utilidad Zip. Por ejemplo, no podemos aadir archivos a un archivo JAR ni actualizar los archivos existentes; los archivos JAR slo pueden crearse partiendo de cero. Asimismo, tampoco se pueden desplazar archivos a un archivo JAR. borrando los originales a medida que se los desplaza. Sin embargo, un archivo JAR creado en una plataforma podr ser ledo transparentemente por la herramienta jar en cualquier otra plataforma (evitndose asi uno de los problemas que en ocasiones afecta a las utilidades Zip).

Como veremos en el Captulo 22, interfaces grficas Je usuario, los archivos JAR se utilizan para empaquetar componentes JavaBeans. 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 programa termina. Aunque esto tiene bastante sentido a primera vista, existen situaciones en las que seria enormemente til que un programa pudiera existir y almacenar su informacin incluso cuando el programa no se estuviera ejecutando. Si esto fuera asi. la siguiente ve/ que iniciramos el programa, el objeto ya se encontrara all y contendra la misma informacin que tuviera la vez anterior que se ejecut el programa. Por supuesto, podemos conseguir un efecto similar escribiendo la informacin en un archivo o en una base de dalos, pero si traamos 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 detalles por nosotros.

El mecanismo de serializacin de objetos de Java nos permite tomar cualquier objeto que implemente la interfaz Seriali/able y transformarlo en una secuencia de bytes que pueda restaurarse posteriormente de modo completo, para regenerar el objeto original Esto es asi incluso si estamos trabajando a travs de una red, lo que significa que el mecanismo de serializacin trata de compensar automticamente las diferencias existentes en los distintos sistemas operativos. En otras palabras, podemos crear un objeto en una mquina Windows, sealizarlo y enviarlo 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 detalle.

En s misma, la serializacin de objetos resulta interesante porque nos permite implementar lo que se denomina persisten-

i ta

ligera. El concepto de persistencia quiere decir que el tiempo de vida de un objeto no est determinado por si un programa se est ejecutando; el objeto contina existiendo entre sucesivas invocaciones del programa. Tomando un objeto serializable. escribindolo en disco para posteriormente restaurar dicho objeto cuando se vuelva a invocar el programa, podemos obtener el efecto de persistencia. I.a razn por la que a esa persistencia se le denomina ligera'* es que no podemos limitamos sunplemente a definir un objeto utilizando algn tipo de palabra clave 4 persistent" y dejar que el sistema se ocupe de los detalles (aunque quiz pueda hacerse esto en el futuro). En lugar de ello, podemos serializar y des-seriali- zar explcitamente los objetos en nuestro programa Si necesitamos un mecanismo de persistencia mas serio, considere la utilizacin de alguna herramienta como (http://hibernate.sourceforge.net). Para obtener ms detalles, consulte Thinking in Enterprise Java, que se puede descargar en la direccin vvh*w. MindView.net

La serializacin de objetos se aadi al lenguaje para soportar dos caractersticas principales. El mecanismo KVil (Remte Method Invoca! ion > invocacin remota de mtodos) de Java permite que los objetos que residen en otras mquinas se comporten como si estuvieran en nuestra propia mquina ( uando 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 RMI se analiza en Thinking n Enterprise Java.

La sealizacin de objetos tambin es necesaria para el sistema de componentes JavaHeans, descrito en el Captulo 22. Interfaces grficas de usuario. Cuando se utiliza un componente Uean, su informacin de estado suele configurarse, general mente. en tiempo de diseo. Esta informacin de estado debe almacenarse, para poder recuperarse posteriormente cuando se inicie el programa, el mecanismo de serializacin de objetos se encarga de esta tarea.

La serializacin 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 serializacin al lenguaje, se modificaron muchas clases de la biblioteca estndar para hacerlas serializables. incluyendo todos los envoltorios de los tipos primitivos. todas las clases contenedoras y muchas otras. Incluso los objetos Class pueden serial izarse.

Para serializar un objeto, creamos algn tipo de objeto OutputStream y luego lo envolvemos dentro de un objeto ObjectOutputStrcam Con esto, lo nico que necesitamos es invocar wrteObject( ). y el objeto se serializar y se enviar al flujo de salida OutputStream (la serializacin de objetos est orientada a bytes. por lo que utiliza las jerarquas InputStream y OutputStream). Para invertir el proceso, envolvemos un objeto InputStream dentro de un objeto ObjectInputStream 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 correctamente.

Un aspecto particularmente inteligente 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 esas 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 que mantener nuestro propio esquema de sealizacin de objetos, mantener el cdigo para poder seguir iodos nuestros vnculos seria enormemente difcil. Sin embargo, el mecanismo de sealizacin de objetos de Java parece poder realizar esta tarea de muera muy precisa, utilizando sin ninguna duda algn algoritmo optimizado que recorre la red de objetos. El siguiente ejemplo permiie probar el mecanismo de sealizacin utilizando una cadena de objetos vinculados, cada uno de los cuales tiene un enlace al siguiente segmento de la cadena, asi como una matriz de referencias a objetos de una clase distinta,

Data: //: io/Worm.java // Ilustra el mecanismo de sealizacin de objetos, import j ava.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 toStringO ( return Integer.toString(n) / }

) public class Worm implements Serializable { private static Random rand = new Random(47)/ private Dataf] d * { new Daca(rand.next Int(10)), new Data(rand.nextInt110 ) I, new Data(rand.nextInt(10 ))

}; private Worm next; private char c; // Valor de i ** nmero de segmentos public Worm I int i, char x) ( print("Worm constructor: " * i); C = XJ if { i > 0J next = new Wormii, (char)(x 1));

} public WormO ( 1 print <"Default constructor");

public String toString\) [ StringBuilder result = new StringBuilder{"c"); result.append(c)i result, append (''(") ; for (Data dat : d) result.append t dat); resul t, append (") ") ,* if(next != null) result.append(next 1; return result.toString0;

) public static void main(String[J args) throws ClassNotFoundException, lOException { Worm w new Worm (6, ' a *) ; print("w = " + w); ObjectOutputStream out = new GbjectOutputStream( new PileOutputStream("worm.out**)) ; out.writeObject("Worm storage\n"); out.writeObject(w); out.close(); // Tambin vaca la salida ObjectInputStream in = new ObjectlnputStreara( new FilelnputStreami "worm.out") ) ; String s = (String)in.readObject(); WGrm w2 = (Worm) in. readObj ect () ; printls "w2 " + w2)j ByteArrayOutputStream bouc = new ByteArrayOutputStreamO ; ObjectOutputStream out2 = new ObjectOutputStream(boutI ,* out2.writeObject ( "Worra storage\n") f out2.writeObject(w); out2.flushO ; ObjectInputStream in2 = new

ObjectInputStream( new ByteArrayInputStream(bout.toByteArray())) ; s = (String;in2.readObject() Worm w3 = iWorm1in2.readObject(I; printls t "w3 = " w3) ;

} } /* Outputi Worm constructor: 6 Worm constructor: 5 Worm constructor: 4 Worm constructor: 3 Worm constructor: 2 worm constructor: 1 w * :a/853f:b{119>:c(802):d(788):e{199):f(881) Worm storage w2 * :a(853) :b (119) :C 1802) :d(7.88) :e(199) :f (8B1J Worm storage w3 = :a(853) :bCHS) :c(B02) :d(788) :e (199) : (861)

///:-

Para que las cosas se; interesantes, la matriz de objetos Data contenida en la cadena Worm se inicializa con nmeros aleatorios (de esta forma, eliminamos las sospechas de que el compilador est conservando algn tipo de meta-informacin). C ada segmento de Worm se etiqueta con un valor char que se genera automticamente como parte del proceso de generacin recursiva de la lista enlazada de objetos Worm. Cuando se crea un Worm. se le dice al constructor la longitud que queremos que tenga. Para construir la referencia next. invoca al constructor de Worm con una longitud inferior en una unidad, etc. La referencia next final se deja con el valor nuil, lo que indica el final de la cadena Worm.

til objetivo de esto es construir una estructura razonablemente compleja que no pueda serializarse fcilmente. Sin embargo, el acto de sealizar es bastante simple Una vez que se crea el objeto ObjectOutputStream a partir de algn oiro flujo de datos, el mtodo writeOb|ect( ) permite sealizar el objeto. Observe que tambin se ha incluido una llamada a writeObject( ) para un objeto String Se pueden tambin escribir todos los tipos de datos primitivos utilizando 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 mas vanado, escribe y lee una matriz ByteArray. Podemos leer y escribir un objeto, utilizando el mecanismo de sealizacin, en cualquier flujo DatulnputSlrcam o DataOutputStrcam, incluyendo (como puede verse en Thinking in Enterprise Javo) una red.

Podemos ver. examinando la salida, que el objeto des-serial izado contiene todos los enlaces que estaban en el objeto original.

Observe que no se invoca ningn constructor, m siquiera el constructor predetenninado. en el proceso de des-serializacin de un objeto de tipo Serializable El objeto completo se restaura recuperando los datos desde el flujo de entrada Inpuf- Stream.

Ejercicio 27: < l) Cree una clase Serializable que contenga una referencia a un objeto de una segunda clase Serializable

Cree una instancia de esa clase, serialicela en disco, restarela a continuacin y verifique que el proceso ha funcionado correctamente. Localizacin de la clase

Podramos preguntarnos que es lo que hace falta para poder recuperar un objeto a partir de su estado sealizado. Por ejemplo. suponga que sealizamos un objeto y lo enviamos como un archivo o lo mandamos a travs de una red hacia otra mquina. Podra un programa en la otra mquina reconstruir el objeto utilizando nicamente los contenidos del archivo?

La mejor forma de responder a esta cuestin es (como siempre) realizando un experimento. F1 siguiente archivo est contenido en el subdirectono de este capitulo: //: io/Alien.java // Una clase serializable.

import j ava. io. * ;

public class Alien implements Serializable {} ///:- El archivo que crea y sealiza un objeto Alien est incluido en el mismo directorio: //; io/FreezeAlien.java // Crear un archivo de salida serializable. import java.io.*; public class FreezeAlien ( public static void main(StringU args) throws Excepton ^ ObjectOutput out new ObjectOutputStreamI new FileOutputStream("X.file")); Alien quellek * new Alien<1r out.writeObject(quellek);

) } m-.-

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 informar de su existencia a travs de la consola.

Una vez compilado y ejecutado, el programa genera un archivo denominado X.file eu el directorio io. El siguiente cdigo

est incluido en un subdirectorio denominado afiles: //: io/xfiles/ThawAlien.java // Tratar de recuperar un archivo serializado sin la // clase del objeto que est almacenado en dicho archivo. // {RunByHand} import java.io.*; public class ThawAlien { public static void main(String[] args) thrcws Exception { ObjectlnputStream ir. = new ObjectlnputStream( new FileInputStream(new File["X.file"))); Object mystery = in.readobject(); System.out.println(mystery.getClass());

) } t* Output: class Alien *///:-

La simple operacin consistente en abrir el archivo y leer el objeto mystery requiere disponer del objeto Class correspondiente a Alien: la mquina JVM no puede localizar Alien.class (a menos que se encuentre en la ruta de clases, lo que no debera ser el caso en este ejemplo). Por ello, obtenemos una excepcin ClassNotFoundException La mquina JVM debe ser capaz de encontrar el archivo .class asociado. Control en la serializacin

Como podemos ver. el mecanismo predeterminado de serializacin es bastante sencillo de usar. Pero qu sucede si tenemos necesidades especiales? Quiz, haya problemas especiales de seguridad y no queramos serializar ciertas panes del obje- 10, o quiz no tiene sentido que uno de los subobjetos sea sealizado si de todos modos hay que crear de nuevo esc subob- jeto cuando recuperemos el objeto completo.

Podemos controlar el proceso de serializacin implememando la interfaz Externalizable en lugar de la mterfaz Serializable. La interfaz Externalizable ampla la interfaz Serializable y aade dos mtodos. >vriteExternal() y readExternal( ). que se invocan automticamente para el objeto durante la serializacin v la des-serializacin, para poder realizar esas operaciones especiales que necesitamos.

El siguiente ejemplo muestra implementaciones simples de los mtodos de la interfaz Externalizable. Observe que Blip 1 y BIip2 son casi idnticos salvo por una sutil diferencia (trate de descubrirla examinando el cdigo): //: io/Blpa.java // so simple de Externalizable, junto can un problema. import j ava.io.*; import static net.mindview.util.Print.*; class Biipl implements Externalizable ( public 31ipl() {

print i"Blipl Constructor");

) public void writeExtemal (ObjectOutput out) throws IOException { print t uBlipl.writeExternal',);

) public void readExternaltObjectInput in) throws IOException, ClassNotFoundException [ print("Blipl.readExternalH);

) clas9 Blip2 implements Externalizable { Blip2() { ) print("Blip2 Constructor")j

public void writeExternal(ObjectOutput out) throws IOException ( print ("Blip2.writeExternal*) ;

) public void readExternal(Objectinput in) throws IOException, ClassNotFoundException { print("Blip2.readExternal);

] public class Blips { public static void main(StringU args) throws IOException. ClassNotFoundExceptlon ( print ('Constructing objects: ") ,* Blipl bl = new Blipl(Ir BIip2 b2 * new Blip2(); ObjectOutputStream o = new ObjectOutputStreaml new FileOutputStream ('Blips. out )); print("Saving objects: M ) ; o.writeObject(bl); o.writeObject ib2); o.close(); // Ahora obtenerlos de nuevo: ObjectlnputStream in = new

ObjectInputStream( new FilelnputStream(Blips.out"I); print("Recovering bl:" / ; bl = (Blipl) in. readObject O ; // ;Ha fallado1 Genera una excepcin: //i print < "Recovering b2:'M; //i b2 = \Blip2)in.readObject(J; l ) / Output: Constructing objects: Blipl Constructor Blip2 Constructor Saving objects: Blipl.writeEx ternal Blip2.writeEx ternal Recovering bl: Blipl Constructor Blipl.readExt eraai ///:-

La razn de que el objeto Blip2 no se recupere es que. al tratar de hacerlo, se genera una excepcin. Puede ver la diferencia entre BlipI y Blip2? El constructor de Blipl es pblico, mientras que el constructor de Blipl no lo es. y eso es lo que provoca la excepcin al intentar efectuar la recuperacin. Pruebe a definir como pblico el constructor de Blipl y elimine los comentarios //! para ver los resultados correctos.

Cuando se recupera bl. se invoca el constructor predeterminado de Blipl. Esto difiere del proceso normal 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 Externaiizable, tienen lugar todas las tareas normales predeterminadas de construccin (incluyendo las inicializaciones en los puntos donde se definen los campos), despus de lo cual se invoca readE\ternal( ). Es necesario tener esto en cuenta (en particular el hecho de que tienen lugar todas las tarcas predeterminadas de construccin), para poder obtener el comportamiento correcto de los objetos Externaiizable

lie aqu un ejemplo que muestra qu es lo que hay que hacer para almacenar y recuperar un objeto Externaiizable: //: io/Blip3.java // P.econstrucccin de un objeto externaiizable. import java.io.*; import static net.mindview.util.Print.*; public class Blip3 implements Externaiizable { private int i; private String s; // Sin inicializacin public Blip3() ( print("Blip3 Constructor"); i // s, i no inicializados

public Blip3(String x, int a) ( print I "Blip3(String x, int a)"); = x;


i

= a;

// s & i inicializados slo en el constructor no predeterminado.

) public String toString() ( return s j ) public void

writeExternal(ObjectOutput throws lOException { // Hay que hacer esto: out. writeObj ect (s) ,- out. wntelnt (i) ;

outi

print(Blip3.writeExternal");

) public void readExtemal (ObjectInput in) throws lOException, ClassNotFoundException ( print (" Bl ip3 . readExtemalM) ; // Hay que hacer esto: s = (String)in.readobject O:
i

= in.readlnt(};

) public static void main(Stringf] args) throws IOException, ClassNotFoundException { print("Constructing objects:") ; Biip3 b3 = new Blip3("A String ", 47); print(b3>; ObjectOutputStream o = new ObjectOutputStream* new FileGutputStream< *'Blip3 .out") ) ; print("Saving object:"); o.writeObject(b3); o.cise(); // Ahora extraer los datos:

ObjectInputStrearc in = new ObjectInputStream( new FileInputStream("Blip3.out* ) ) ; print "Reccrvering b3 : " ) ; b3 = (Blip3)in.readObject( )j print(b3)?

) ) /* Output: Constructing objects: Blip3Strlnq x. int a) A string 47 Saving object: Blip3.writeExter nal Recovering b3: B1ip3 Constructor 31ip3.reaaExtern al A String 47 *///:-

Los campos s e I slo se incializan en el segundo constructor, pero no en el constructor predeterminado, listo quiere decir que si no inicializamos s c I en readExternal( ). s ser nuil e i ser cero (ya que el espacio de almacenamiento del objeto se pone a cero en el primer paso de la creacin del objeto). Si desactivamos mediante comentarios las dos lineas de cdigo a continuacin de las frases: Hay que hacer esto: y ejecutamos el programa, podremos ver que al recuperar el objeto s es nuil e i es cero.

Si estamos heredando de un objeto Exrernalizablc. lo que haremos normalmente ser invocar las versiones de la clase base de writeExternal( ) y readExternal( ). para almacenar y recuperar apropiadamente los componentes de la clase base.

Para hacer que las cosas funcionen correctamente, no slo hay que escribir los datos importantes de los datos del objeto durante el mtodo writeExtcrnal ) (no hay ningn comportamiento predeterminado que escriba ninguno de los objetos miembro de un objeto Extcrnali/ablc). sino que tambin hay que recuperar esos datos en el mtodo readF.xternal( ). Esto puede resultar confuso a primera vista, porque la realizacin de las tareas de construccin predeterminadas para un objeto Kxternalizahle podran hacer parecer que se est produciendo automticamente algn tipo de operacin de almacenamiento y recuperacin, pero en realidad no es asi.

Ejercicio 28: (2) F.n Rlips.ja\ a. copie el archivo y renmbrelo como BlipCheck.java Renombre tambin la clase Blip2

como BlipCheck (hacindola pblica y eliminando el mbito pblico de la clase Blips en el proceso). Elimine las marcas de comentario //! del archivo y ejecute el programa, incluyendo las lincas problemticas. A continuacin, desactive con un comentario el constructor predeterminado de BlipCheck. Ejecute el programa y explique por qu funciona. Observe que. despus de la compilacin, es necesario ejecutar el programa con java 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 cuando las dos lincas forman parte del programa. La palabra clave transient

Cuando estamos controlando la serial i/acin, puede que exista un subobjeto concreto que no queramos que sea automticamente guardado y restaurado por el mecanismo de sealizacin de Java Esto suele suceder cuando dicho subobjeto representa informacin confidencial que no queramos sealizar, como por ejemplo una contrasea. Incluso si esa informacin es de tipo prvate dentro del objeto, una vez que ha sido sealizada resulta posible que alguien acceda a ella leyendo un archivo o interceptando una transmisin de red.

Una forma de evitar que las panes confidenciales del objeto sean sealizadas consiste, como hemos visto previamente, en implemeniar la clase Externaiizable. En ese caso, no hay nada que se sealice automticamente y podemos serial i/ar explcitamente slo aquellas panes que sean necesarias dentro de writeExternaM )

Sin embargo, si estamos trabajando con un objeto de tipo Serializable. toda la tarea de sealizacin tiene lugar automticamente. Para controlar esto, podemos desactivar la sealizacin campo a campo utilizando la palabra clave transient, que

loque 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 inicio de sesin concreto. Suponga que. una \ez verificados los datos de inicio de sesin, queremos almacenar los datos, pero sin la contrasea. La forma ms fcil de hacer esto es implementando Serializable y marcando el campo password como transient. He aqu un ejemplo: //: io/Logon.java // Ilustra la palabra clave "transient". import java.util.concurrent.*; import java.io.*; import java.til.*; import static net.mindview.til.Print.* . public claas Logon implements Serializable ( prvate Date date - new Date O; private String usemame; orivate transient String password/ public Logon(String ame. String pwd) ( usemame * ame; password = pwd;

) public String toString) { retura "logon info: \n usemame: M * usemame + H\n date: " + date + "\n password: " password;

) public static void mam (String [1 args) throws Exception { Logon a = new Logon ( "Hulk" r "myLittlePony"I; print("logon a = " -f

aj / ObjectOutputStream o = new ObjectOutputStream( new FileOutputStream("Logon.out")); o.writeObject(a); o.cise()/ TimeOnit.SECONOS.sleepil); // Retardo // Ahora recuperar los datos: ObjectInputStream in = new 0b3ectInputStreami new FileInputStreamI"Logon.out")); print ("Recovering object at " + new DateO); a = (Logon)in.readObject(); print ("logon a ** " + a) ;

) / Output; (Sample) logon a = logon info: usemame: Hulk date: Sat Nov 19 15:03:26 MST 2005 password: myLittlePon

18 Entrada/salida 1708 yRecovering object at Sat Nov 19 15:03:28 MST 2005 iogon a lnfo: useraame: Hulk date: Sat Nov 19 15:03:26 MST 2005 password: aull *///:45

logcm

Podemos ver que los campos date y username son normales (no de tipo transient). por lo que se los serializa automticamente. Sin embargo, el campo password es de tipo transient. as que no se almacena en disco; asimismo, el mecanismo de serializacin no hace nada por intentar recuperarlo. Cuando se recupera el objeto, el campo password contiene el valor nuil. Observe que. mientras toStrng( ) est construyendo un objeto String utilizando el operador sobrecargado +\ las referencias nuil se convienen automticamente en la cadena nuil".

Tambin puede ver que el campo date se almacena en disco V se recupera desde all, no siendo generado de nuevo.

Puesto que los objetos Externalizable no almacenan ninguno de sus campos de manera predeterminada, la palabra clave transient es para ser usada nicamente por los objetos Seriali/able. Una alternativa a Externalizable

I.a seccin "Interfaces c informacin de tipos* al final tlcl Captulo 14. n/'ormnrin de tipin, muestra cmo c* posible acceder a mtodo, privado desde fuera de tu clase.
45

18 Entrada/salida 1709 Si no desea implcmentar la interfaz Externalizable. existe otra tcnica alternativa. Puede implementar la interfaz Seriali/able y aadir (observe que decimos aadir* y no sustituir" o implementar") sendos mtodos denominados writeObjectt ) y readObject( ) que se invocarn automticamente cuando el objeto se sealice o des-serialice. respectivamente. En otras palabras, si proporcionamos estos otros mtodos se usarn esos mtodos en lugar del mecanismo predeterminado de sealizacin.

Los mtodos deben tener estas signaturas exactas: prvate void writeObject{ObjectOutputStream stream) throws TOException; prvate void readObject(ObjectInputStream stream} thrcws lOException, ClassNotFoundException

Desde un punto de vista de diseo, las cosas pueden ser bastante complicadas si recunimos a esta solucin. En primer lugar, podemos pensar que como estos mtodos no forman pane de una clase base ni de la interfaz Seriali/able. deberan ser definidos en sus propias interfaces. Pero observe que esos mtodos estn definidos como prvate, 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 writcObJect( ) y readObjeet( ) de los objetos ObjectOutputStream y ObjcctInputStream los que se encargan de invocar a los mtodos writeObjectt ) y read()bject( ) de nuestro objeto (observe cmo estoy contenindome para no entrar en una larga discusin acerca de lo inapropiado que resulta utilizar aqu los mismos nombres de mtodos; por decirlo en pocas palabras: resulta enormemente confuso). Puede estar preguntndose cmo es posible que los objetos ObjectOutputStream y Object InputStream tengan acceso privado a mtodos de nuestra clase. Lo nica respuesta en la que podemos pensar es que esto forma pane de la magia de la sealizacin.

18 Entrada/salida 1710 Cualquier cosa que definamos en una interfaz es automticamente de tipo public, por lo que si writeObject() y readObject( ) deben ser privados, eso quiere decir que no pueden formar pane de una interfaz. Puesto que queremos ajustamos a las signaturas exactamente, el efecto es el mismo que s estuviramos implementando una interfaz

Cabe imaginar que, cuando invocamos ObjectOutputStream.writeObject( ), el objeto de tipo Seriali/able que pasamos a ese mtodo es interrogado (utilizando, sin duda, el mecanismo de reflexin) para ver si implemcnta su propio mtodo writeObject( ). En caso afirmativo, se omite el proceso normal de sealizacin y se invoca el mtodo writeObject( ) personalizado La misma situacin se produce para el mtodo readObject( ).

Existe otra consideracin adicional que debemos tener en cuenta. Dentro de nuestro mtodo writeObject(). podemos decidir llevar a cabo la accin wrlteObject( ) predeterminada invocando defaultW riteObjectt ) De la misma forma, dentro de readObject() podemos invocar defaultReadObject( ). He aqu un ejemplo simple en el que se ilustra cmo puede controlarse el almacenamiento y la recuperacin de un objeto Serializable: //: io/SerialCtl.java // Control de la sealizacin aadiendo nuestros propios // mtodos writeObject0 y readObject(). import java.io.*; public class SerialCtl implements Serializable | prvate String a; private transient String b; public SerialCtl(String aa. String bb) ( a "Not Transient: * * aa; b = "Transient: 6M * bb; j public String toString() ( return a 4- '\n" - b; ) private void writeObject(ObjectOutputStream stream) throws tOException { i stream-defaultWriteObject); stream.writeObject|b);

18 Entrada/salida 1711 private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException ( stream.defaultReadObject(); b = (String)stream.readObject0;

) public static void main(Stringf) args) throws IOException, ClassNotFoundException { SerialCtl sc = new SerialCtl ("Test1", ,,Test2") ; System.out.println"Eefore:\n" se); ByteArrayOutputStream buf* new ByteArrayOutputStream!); ObjectOutputStream o = new ObjectOutputStream(buf); o.writeObject(se); // Ahora recuperar los datos: ObjectInputStream m = new ObjectlnputStream( new ByteArraylnputStream(buf.toByteArray()) ); SerialCtl sc2 = (SerialCtliin.readObject(); I System.out.printIn("After:\n" sc2) ;

) / Output: Before: Not Transient: Testl Transient: Test2 After: Not Transient: Testl Transient: Test2 '///:-

18 Entrada/salida 1712 En este ejemplo, uno de los campos String es de tipo normal y el otro est definido como transient, para demostrar que el campo que no es de tipo transient es guardado por el mtodo defaultWriteOI)ject( ) mientras que el campo transient se guarda y restaura explcitamente. Los campos se imcializan dentro del constructor en lugar de en el punto de definicin, para demostrar que no estn siendo metalizados por ningn mecanismo de tipo automtico durante la dcs-serializacin. Si utilizamos un mecanismo predeterminado para escribir las partes no transitorias (no marcadas como transient) del objeto. debemos invocar defaultVVrteOI>ject( ) como primera operacin en writeObjeet(). y defaultReadOI>jeet( ) como primera operacin en read<)bject( ). Se trata de sendas llamadas a mtodos que resultan un tanto extraas. Podra parecer, por ejemplo, que estamos invocando deault\VriteObject( ) para un objeto ObjectOutputStream sin pasarle ningn argumento. 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.El almacenamiento y recuperacin de los objetos transient utiliza un cdigo ms familiar. Y. sin embargo, examine atentamente lo que sucede en maln( ). se crea un objeto SerlalCtl y luego se sealiza en un flujo ObjecfOutputStream (observe en este caso que se utiliza un buffer en lugar de un archivo; para el objeto ObjectOutputStrcam no hay ninguna diferencia). La sealizacin tiene lugar en la lnea o.wriceCbjeccIsc);

El mtodo \\rite()bject( ) debe examinar se pan ver si dispone de su propio mtodo writeObjecM ) (no comprobando la interfaz, ya que no existe ninguna, m el tipo de la clase, sino buscando realmente el mtodo mediante el mecanismo de reflexin I. Si el objeto dispone de ese mtodo, lo utilizar. Para readObject( ) se utiliza una tcnica similar Quiz sta fuera la nica forma prciica con la que se poda resolv er el problema, pero hay que reconocer que rcsulla un tanto extraa Versionado

Es posible que queramos modificar la versin 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 probable es que no tengamos que recurrir a l ms que en casos especiales: el mecanismo requiere un anlisis ms en profundidad que no vamos a realizar aqu. Los documentos del JDK descargables en la direccin httj> ava.xttn.com analizan este tema de forma bastante exhaustiva.

18 Entrada/salida 1713 Tambin podr observar en la documentacin del JDK muchos comentarios que comienzan con la advertencia: los objetos sealizados de una determinada clase no sern compatibles con las futuras versiones de Swing y que el soporte actual de serializacin resulta apropiado para el almacenamiento a corto plazo o para la invocacin RXO ent/v aplicaciones...

Esto se debe a que el mecanismo de versionado es demasiado sencillo como para funcionar de manera fiable en (odas las situaciones, especialmente con JavaBeans. Los diseadores 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 sealizacin para almacenar parte del estado del programa. 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 .sealizamos dos objetos que tienen una referencia a un tercer objeto? Cuando se restauren esos dos objetos a partir de su estado sealizado, obtenemos una nica instancia de un tercer objeto? Qu sucede si sealizamos los dos objetos en archivos separados y los des-seal izamos en diferentes partes del programa?

He aqu un ejemplo que ilustra el problema; //: io/MyWorld*j ava import java.o.*; import java.til.;

18 Entrada/salida 1714 import static net.mindview.til.Print.* ; class House implemencs Serializable (] class Animal implements Serializable ( private Strir.g ame; private House preferredHouse; Animal(String nm, House h) { ame = nm; preferredHouse * h;

) public String toStringM { retum ame + " \ * + super. roString ) + "J , " + preferredHouse M\n*; }public class MyWorld { public static void main(String 13 args* throws IOException, ClassNotFoundException { House house - new House(}; List<Animal> animals = new ArrayLiat<Animal>(); animals.add(new Animal("Bosco the dog", house)); animals.add(new Animal("Ralph the hamster", housej); animals.add(new Animal("Molly the cat", house)); print (animals: ** animals); ByteArrayOutputStream bufl * new ByteArrayOutputStream( ); ObjectOutputStream ol = new ObjectOutputStream(buf1); ol.writeObjectlanimals); writeObject(animals); // Escribir un segundo conjunto // Escribir en un flujo de datos
1.

18 Entrada/salida 1715 diferente: ByteArrayOutputStream buf2 => new ByteArrayOutputStream(); ObjectOutputStream o2 = new ObjectOutputStream(buf2 } ;
2.

writeObject(animals);

// Ahora recuperar los datos: ObjectlnputStream inl = new ObjectInputStream new ByteArrayInputStream(buf1.toByteAr ray())); ObjectlnputStream in2 = new ObjectlnputStream: new ByteArrayInputStream(buf2.toByteAr ray ))); List animalsl = (List)inl.readObjectt), animals2 = (List)inl.readObject(), animals3 * I List)in2.readObjectI); print("animalsl: " + animalsl); print("animals2: " anlmals2); print I "animals! : " * animals3);

) /* Output: (Sample) animals: [Bosco the dog[Animal&addbfIj, Housed2e816 , Ralph the hamster[Anima 1*9304bl3, House42e816 , Molly the cat[Animal190dll), House42e816 1 animalsl: [Bosco the dog[Animalsde6f34J , House*156ee8e . Ralph the hamster [Animal*47b480] . Housediseeese . Molly the cat[Animalsl9b49e6l . House3156ee8e ] animals2: [Bosco the dog [Animal>de6f 34J , House&156ee8e . Ralph the hamster[Animal*47b480], House156ee8e , Molly the cat [Animal'*19b49e6l # Housel56ee8e J animals3: (Bosco the dog [Animal&l0d4481 , House3e0elc6 # Ralph the hamster [Animalft6calc] , House*e0elc6 , Molly the

18 Entrada/salida 1716 cat[Animal$lbf216aJ, House$e0elc6 1 V//:-

Un aspecto interesante del ejemplo es que resulta posible utilizar el mecanismo de serializacin de objetos con una matriz de tipo by le, como forma de obtener una copia profunda" de cualquier objeto de tipo Serializable (una copia profunda quiere decir que estamos duplicando la red completa de objetos, en lugar de slo los objetos bsicos y sus referencias. La copia de objetos se cubre en detalle en los suplementos en linea del libro. Los objetos de tipo Animal contienen campos de tipo House. En niain( ). se crea una lista de estos objetos Animal y se la sealiza dos veces en sendos flujos de datos. Cuando se des-serializan e imprimen esos flujos de datos, podemos ver un ejemplo de la salida que se obtendra (en cada ejecucin las posiciones de memoria correspondientes a los objetos sern diferentes)

18 Entrada/salida 1717 .Por supuesto, lo que cabria esperar es que los objetos dcs-senalizados tuvieran direcciones diferentes de las de sus originales Pero observe que en anmalsl y animals2 aparecen las mismas direcciones, incluyendo las referencias al objeto House que ambos comparten. Por otro lado, cuando se recupera aninials3. el sistema no tiene forma de saber que los objetos de este otro flujo de datos son alias de los objetos del primer iluto de datos, asi que construye una red de objetos completamente distinta

Mientras estamos sealizando todo en un nico (lujo de datos, recuperaremos la misma red de objetos que hayamos escrito. 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 responsabilidad: los objetos se escribirn en el estado en que se encuentren (y con cualesquiera conexiones que tengan con otros objetos) en el momento de sealizarlos.

Lo ms seguro, si queremos guardar el estado de un sistema, es hacer la sealizacin en forma de operacin 'atmica'. Si sealizamos algunos objetos, realizamos otras tareas y luego sealizamos ms objetos, ctc., no estaremos guardando el estado del sistema de una forma segura. En lugar de ello, incluya todos los objetos que forman pane del estado de su sistema en un nico contenedor y escriba simplemente dicho contenedor como pane de una nica operacin. Entonces podr restaurarlo tambin con una nica llamada a mtodo.

El siguiente ejemplo es un sistema imaginario de diseo asistido por computadora (CAO, class Circle extends Shape (

18 Entrada/salida 1718 computtr-aidetJ desigv) que ilustra la tcnica descrita Adems, el ejemplo plantea la cuestin de los campos estticos: si examina la documentacin del JDK. podr ver que Class es Serializable. asi que debe ser sencillo almacenar los campos de tipo static sealizando simplemente el objeto Class. En cualquier caso, parece una solucin llena de sentido comn. //: io/StoreCADState.java // Almacenamiento del estado de un supuesto sistema CAD. import j ava.io.*; import j ava.til.*; abstract class Shape implements Serializable ( public static final int RED = 1, BLIJE = 2, GREEN = 3; private int xPos, yPos, dimensin; private static Random rand = new Random(47); private static int counter 0; public abstract void setColor(inc newColor)/ public abstract int getColor () public Shapeint xVal, int yVal, int dim) ( xPos * xVal; yPos = yVal; dimensin * dim;

} public String toStrmgO ( retum getClassi) -r "color" - getCalor() xPos[ - xPos f n] yPost" + yPos dim" + dimensin + *]\n',l*

class Circle extends Shape (

18 Entrada/salida 1719 ) public static Shape randomFactory<) ( int xVal * rand.nextInt100)j int yVal = rand. next Int (100) ? Int dim * rand.nextInt (100); switch{ c ounter -h+ % 3) ( defa ulti case0: retum case1: retum new new CirclefxVal, SquarelxVal, yVal, yVal, dim); dim);

case2: return new LinefxVal, yVal, dim),*

) jprivate static int color = RED; public Circle(int xVal, int yVal, int dim) ( super(xVal, yVal, dim);

class Circle extends Shape (

18 Entrada/salida 1720 ) public void setColor(int newCoiorl { color = newColor; ) public int aetColorU { return color; )

} class Square extends Shape { private static int color; public Square(int xVal. int yVal, int dim) ( superixVal, yVal, dim); color RED; i public void setColorint newColor) ( color = newColor; } public int getColorf) { return color; |

) class Line extends Shape { private static int color =- RED; public static void serializeStaticState(ObjectOutputStream os) throws IOException { os.writelnt(colorI ; ) 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 newColor; } public int getColor() { return color; ]

class Circle extends Shape (

18 Entrada/salida 1721 } public class StoreCADState ( public static void main(String[] args) throws Exception ( List<Class<? extends Shape>> shapeTypes = new ArrayList<Class<? extends Shape>>(); // Aadir referencias a los objetos class: shapeTypes.add(Circle.class); shapeTypes.add(Square.class); shapeTypes.add(Line.class); List<Shape> shapes = new ArrayList<Snape>{); // Construir algunas forma geomtricas: for (int i = 0; i < 10; i-~t-) shapes.add(Shape.randomFactory()); // Configurar todos los colores estticos como GREEN: for (int i = 0; i < 10; i++) ((Shape)shapes.get(i)).setCoior(Shape.GREEN); / ) Guardar el vector de estado: ObjectOutputStream out * new ObjectOutputStream( new FileOutputStream(MCADState.out*)); out.writeObject(shapeTypes); Line.serializeStaticState(out); out.writeObject(shapes); // Mostrar las formas geomtricas: System.out.print ln(shapes);

} ) /* Output :[class Crrclecolor [3] xPos[58] y?os[55] dim[93] , class Squarecolor[3]xPos[61]yPos[61] dim[29] , class Linecolor[3] xPos(68) yPosfO] dim[22] , class Circlecolor[3] xPos[7] yPos[88] dim[28] class Circle extends Shape (

18 Entrada/salida 1722 , class Squarecolor [3]xPos [51]y?os[89] , class , class , class 3 Circlecolor(3]x?os[20jyPos[58] Squarecolor[3]xPos[40]yPos[ll] Circlecolor[3]xPos[75]yPoslO] dim[9] dim[16] dim[22] dira[42]

, class Linecolor[3] xPos[78) yPcs[98] dim[61]

t class Linecolor[3] xPos [4] y?os(83j dim[6]

V//5-

La clase Shape implcmenia Serializahle. por lo que cualquier cosa que herede de Shape ser tambin automticamente de tipo Seriatl/able Cada objeto Shape conuene datos y cada clase derivada de Shape contiene un campo static que determina el color de todos esos tipos de objetos Shape (si insertramos un campo esttico en la clase base slo tendramos un campo, ya que los campos estticos no se duplican en las clases derivadas). Los mtodos de la clase base pueden ser sustituidos para configurar el color de los diferentes tipos (los mtodos estticos no se acoplan dinmicamente, asi que son mtodos normales). El mtodo randoruFactory( ) crea un objeto Shape diferente cada vez que se lo invoca, utilizando valores aleatorios como datos para el objeto Shape.

Orele y Square son extensiones sencillas de Shape; la nica diferencia es que Circle micializa color en el punto de definicin. mientras que Square lo inicializa en el class Circle extends Shape (

18 Entrada/salida 1723 constructor. Dejaremos el anlisis de Line para ms adelante.

En main(). se utiliza un contenedor ArrayList para almacenar los objetos Class y el otro para almacenar las formas geomtricas

La recuperacin de los objetos es bastante sencilla: //: io/RecoverCADState.java // Restauracin del estado del supuesto sistema CAD. // {RunFirst: StoreCADSta te) import j ava.o.*; import java.til. ; publie class RecoverCADState { ^SuppressWamlngs ("unchecked") public static void main(String[) args) throws Exception ( ObjectlnputStreara in = new ObjectlnputStream new FilelupuLStream( "CADState .out") ) ,* // Leer en el mismo orden en que fueron escritos: List<Class<? extends Shape shapeTypes = (List<Class<? extends Shape>>) in.readObject () ,* class Circle extends Shape (

18 Entrada/salida 1724 Line.deserializeStaticState{in) List<Shape> shapes = (LisL < Shape> ) in.readObject0; System.out.prntln i shapes >;

) ) I * OUtpUt: [class Circlecolor [1] xPos[58] yPos[55] dim[93l . class Squarecolor[0] x? os|61] ypos [61] dim(291 , class Linecolor[3] xPos[68] yPosfO] ditn[22] , class Circlecolor[1] xPos [7] yPos(88] dimt2Bl . class Squarecolor[0] xPos[51] yPos[89l dim[9] , class Linecolor[3] xPos[78] yPos[9B] dim[Sl] , class Circlecolor[1]xPos[201y?os[58] dim[16l dim[22] dim[42] . class Squarecolor [0]xPos[40]yPos[ll] , class Circlecolor[1]xPos[75]yPos[10]

, class Linecolor[3] xPos[4] yPos[83] dim[6]

class Circle extends Shape (

18 Entrada/salida 1725 *///;-

Puede ver que los valores de xPos. > Pos > dim .son almacenados y recuperados satisfactoriamente, pero existe algn problema con la recuperacin de la informacin de tipo static Todos los valores son 3 al entrar, pero al salir son distintos Los objetos Circle tienen un valor de I (REI), que es la definicin) y los objetos Square tienen un valor de 0 (recuerde que se inicializahan en el constructor). Es como si los datos de tipo static no se hubieran sealizado en absoluto! En efecto, as es: an cuando la clase Class es Seriali/able. no hace lo que cabria esperar. Por tanto, si queremos sealizar valores de tipo static. debemos hacerlo nosotros mismos.

Para esto es para lo que se utilizan los mtodos serializeStaticState( ) y deserlalizeStaticState( ) de tipo static en Une. Como podemos ver. se los invoca explcitamente como pane del proceso de almacenamiento y de recuperacin (observe que es necesario mantener el orden de escritura y lectura en el archivo de sealizacin). Por tanto, para hacer que estos programas funcionen correctamente es necesario:

Aadir sendos mtodos seriallzeStaricState( ) y deserali/eStaticState( ) a las clases.


1.

class Circle extends Shape (

18 Entrada/salida 1726
2.

l.

Eliminar el contenedor ArrayList shape l'ypes y todo el codigo relacionado con

3.

Aadir llamadas a los nuevos mtodos estticos de sealizacin y desserializacin en las clases que representan 3 las distintas formas geomtricas.

Otra cuestin en la que hay que pensar es la de la seguridad, ya que el mecanismo de serializacin tambin guarda los datos de tipo prvate Si tenemos problemas de segundad, dichos campos deben marcarse como transient. Pero entonces, ser necesario disear alguna forma segura de almacenar dicha informacin, para que cuando efectuemos una restauracin, podamos reinicializar dichas variables de tipo prvate

Ejercicio 30: (1) Corrija el programa CADState.java como se ha descrito en los prrafos anteriores. XML

class Circle extends Shape (

18 Entrada/salida 1727 Una imponante limitacin de la sealizacin de objetos es que es una solucin vlida slo para Java: slo los programas Java pueden des-serial i zar sus objetos. L'na solucin ms interoperable consiste en convenir los datos a formato XML, lo que permite que sean consumidos por una amplia variedad de plataformas y de lenguajes.

Debido a su popularidad, existe un nmero enormemente grande y confuso de opciones para programar con XML. incluyendo las bibliotecas javax.xml.* distribuidas con el JDK. Aqui, he decidido utilizar la biblioteca XOM de cdigo abierto de EUiOttc Rusty Harold (puede descargar los archivos y la documentacin en vnrw.xom.nu) porque parece ser la forma ms simple y directa de generar y modificar cdigo XML utilizando 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 apellido, los cuales queremos serializar mediante cdigo XML. La siguiente clase Person tiene un mtodo getXN1L( ) que utiliza XOM para convertir los datos Person en un objeto Element XML y un constructor que toma un objeto Element y extrae los datos Person apropiados (observe que los ejemplos XML estn en su propio subdirectorio); // xml/Person.j ava // Utilizar la biblioteca XOM para escribir y leer XML // (Requires: nu.xom.Noae; You must install // the XOM library from http://www.xom.nu ) import nu.xom.*; import java.io.*; import java.til; public class Person { private String first, last; class Circle extends Shape (

18 Entrada/salida 1728 public Person(String first, String last) ( this.first first; this.last * last;

> // 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(f irst); Element lastName = new Element("last"); lastName.appendChild(last); person.appendChild(firstName); person.appendChiId (lastName); return person;

) // Constructor para restaurar un ob^eto Person // a partir de un objeto Element XML: public Person(Element person) { first person.getFirstChildEIement("first"> .getValueO; last person.aetFirstChildElement("last").getVa lueO;

class Circle extends Shape (

18 Entrada/salida 1729 } public String toStringO ( return first + " " last; ) // Hacer que sea legible: public static void format(OutputStream os. Document doc) throws Exception ( Serializer serializer= new Serializer(os."150-8859-1"); serializer.setlndent(4); serializer.setMaxLength(60) ; serializer.write(doc); serializer.flush0 ,*

} public static void main(StringL] args) throws Exception ( List<Person> people = Arrays.asListI new Person("Dr. Bunsen". "Honeydew"). new Person("Gonzo", "The Great"), new Person("Phillip J.", "Pry")); System.out.println(people); Element root = new Element("people"); for(Person p : people) root.appendChild(p.getXML i)); Document doc = new Document (root) ; format(System.out, doc); format(new BufferedOutputStream(new FileOutputStream( "People.xml")), doc);

) ) /* Output: class Circle extends Shape (

18 Entrada/salida 1730 (Dr. Bunsen Honeydew, Gonzo The Great, Phillip J. Fry] <?xml version="l.0" encodinguISO-B859-lu?> <people> <person> <first>Dr. Bunsen</first> clast>Honeydew</last> </person> <person> <first>Gonzo</first> last>The Great</last> </person> <person> <first>Phillip J.</first> <last>Fry</last> </per8on> </people>

///:Los mtodos XOM son bastante auto-explicativos y puede encontrarlos en la documentacin de XOM .XOM tambin contiene una clasc Serializer que. como podemos ver. se utiliza en el mtodo format( ) para transformar el cdigo XML a un formato ms legible. Con invocar simplemente toXML ) todo el sistema funciona, asi que Serializer es una herramienta bastante til.

class Circle extends Shape (

18 Entrada/salida 1731 Des-serial izar los objetos Person a partir de un archivo XML tambin resulta sencillo: //: xml/People.java // (Requires: nu.xom.Node; You muse inscail // the XOM library from http://www.xom.nu ) // {P.unFirst: Person) import nu.xom.; import java.til.*; public cla9s People extends ArrayList<Person> { public People(String fileName) throws Exception { Document doc = r.ew Builder) .bulId(fileName) ; Elements elements = doc.getRootElement{).g etChildElements (); forfint i = 0; i < elements .size () ; i*--*-) add(new Person{elements.get(i) ));

) public Btatic void main(String[] args) throws Exception { People p r.ew People IHPeople .xml") ; i System.out.println(p);

} / Output: [Dr. Bunsen Honeydew, Gomo The Great, Phillip J. Fry) *///t-

class Circle extends Shape (

18 Entrada/salida 1732 El constructor People abre y lee un archivo usando el mtodo Builder.huild( ) de XOM, y el mtodo getChildElements() genera una lista Elements (no es un objeto List estndar de Java, sino un objeto que slo tiene un mtodo size< ) y un mtodo get( ). Harold no quera obligar a los programadores a utilizar Java SE5. pero seguia queriendo disponer de un contenedor que fuera seguro en lo que respecta a tipos). Cada objeto Element de esta lista representa un objeto Person. por lo que se lo entrega al segunde) constructor de Person Observe que esto requiere que conozcamos por adelantado la estructura exacta del archivo XML. pero sta suele ser la norma en este tipo de problemas. Si la estructura no se ajusta a lo que esperamos. XOM generar una excepcin. Tambin podramos escribir cdigo ms complejo que analizar el documento XML en lugar de hacer suposiciones acerca del mismo, para aquellos casos en los que tengamos una informacin menos concreta acerca de la estructura XML entrante.

Para que estos ejemplos puedan compilarse, es necesario incluir los archivos JAR de la distribucin XOM dentro de nuestra Rita de clases.

Esto slo es una breve introduccin a la programacin XML con Java y a la biblioteca XOM; para obtener ms informacin, consulte ww'wxom.nu.

Ejercicio 31: (2) Aada una informacin People.java.

de

direccin

postal

Person.java y la

Ejercicio 32: (4) Utilizando un contenedor net.mindvIew.util.TextFHe. escriba un class Circle extends Shape (

Map<String.Integer>

utilidad

18 Entrada/salida 1733 programa que cuente el numero de apariciones de las distintas palabras en un archivo (utilice \\W+M

como segundo argumento para el constructor TextFile). Almacene los resultados como un archivo XML. Preferencias

La API Preferences est mucho ms prxima a lo que son los mecanismos de persistencia que a los de sealizacin de objetos, porque se encarga de almacenar y recuperar automticamente informacin. Sin embargo, su uso est restringido a conjuntos 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 SK (no es un tamao pequeo, pero tampoco nos permite construir ninguna aplicacin seria Como su propio nombre sugiere, la API Preferences est diseada para almacenar y extraer preferencias de usuario y opciones de configuracin de los programas.

Las preferencias son conjuntos de clave-valor (como los contenedores Map) que se almacenan en una jerarqua de nodos. Aunque la jerarquia de nodos puede utilizarse para crear estructuras complicadas, lo normal es crear un nico nodo con el mismo nombre que nuestra clase y almacenar all la informacin. He aqu un ejemplo simple: //: io/PreferencesDemo.java import java.util.prefs-; class Circle extends Shape (

18 Entrada/salida 1734 import static net.mindvew.util.Print.*; public class PreferencesDemo { public static void main{String[] args) throws Exception { Preferences prefs = Preferences . userNodeForPackageiPrefe rencesDemo.class); prefs.put("Location", "Oz"); prefs.puti"Footwear", "Ruby Slippers"),* prefs.putInt{"Companions *, 4},* prefs.putBooleanl"Are there witches?", true); int usageCount * prefs .getInt ("UsageCount1', 0) ; us age Counts; prefs.putInt{"Usage Count", usageCount); for(String key : prefs.keys 0) print(key " + prefs.get(key, null)); // Siempre hay que proporcionar un valor predeterminado: print("How many companions does Dorothy have? " pref s. get Int I "Companions", 0)),*

) ) /* Output: (Sample) Location: Oz Footwear: Ruby Slippers Companions: 4 class Circle extends Shape (

18 Entrada/salida 1735 Are there witches?: true UsageCount: 53 How many companions does Dorothy have? 4 ///:-

Aqu, se utiliza userNodeForPackagc(). pero tambin podramos elegir sy$temNodcForPackage( ); la eleccin es hasta cierto punto 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 static, se utiliza PrcfcrcnccsDcmo.class pon utilizar el nodo, pero dentro de un mtodo no esttico, probablemente utilizaramos getClass( ) No es necesario utilizar la clase actual como identifcador del nodo, aunque esa es la prctica habitual

Una vez creado el nodo, estar disponible para cargar o leer los datos. Este ejemplo carga el nodo con vanos tipos de elementos y luego obtiene las claves con kevs( ) Las claves se devuelven como un objeto String||. lo que puede resultar un tanto sorprendente si estamos acostumbrados a utilizar el mtodo keys( ) de la biblioteca de colecciones. Observe el segundo argumento de get( ): se trata del valor predeterminado que se genera si no existe ninguna entrada para dicho valor de clave. Mientras estamos realizando una iteracin a travs de un conjunto de claves, siempre sabemos si existe una entrada, asi que resulta seguro utilizar nuil como valor predeterminado, pero lo normal es que estemos extrayendo una clave nominada. como en: prefs.getint("Companions", 0)1;

class Circle extends Shape (

18 Entrada/salida 1736 En el caso normal, lo que conviene es proporcionar un valor predeterminado razonable. De hecho, podemos ver una estructura bastante tpica en las lineas: int usageCount = prefs.getInt("UsageCount", 0); usageCount *- ; prefs.putInt("UsageCount", usageCount);

De esta forma, la primera vez que ejecutemos el programa. I sageCount tendr el valor cero, pero en las subsiguientes invocaciones sera distinto de cero.

Al ejecutar PrefereneesDenio.juva, podemos ver que. en efecto, t sageC'ount se incrementa cada vez que se ejecuta el programa. pero dnde se almacenan los datos? No aparece ningn archivo 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 variaran dependiendo del sistema operativo. En Windows, se utiliza el Registro (puesto que ste es ya de por si una jerarqua de nodos con parejas clave-valor). Pero lo importante es que la informacin se almacena de alguna manera mgica y transparente, de forma que no tenemos que preocupamos de cmo funciona el mecanismo en un sistema o en otro

Habra mucho ms que comentar acerca de la API Preferences. por lo que puede consultar la documentacin del JDK., que resulta bastante comprensible para obtener ms detalles. class Circle extends Shape (

18 Entrada/salida 1737 Ejercicio 33: (2) Escriba un programa que muestre el valor 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 F S de Java satisface los requisitos bsicos. Podemos efectuar lecturas y escrituras a travs de la consola, un archivo, un bloque de memoria o incluso a travs de Internet. Mediante el mecanismo de herencia podemos crear nuevos tipos de objetos de entrada y de salida, fc incluso podemos aadir un mecanismo simple de amplia- bilidad a los tipos de objetos que un flujo de datos puede aceptar, redeftniendo el mtodo loString( ) que se invoca automticamente cada vez que pasamos un objeto a un mtodo que est esperando un argumento de tipo String (la limitada conversin de tipos automtica" de Java).

Existen diversas cuestiones que la documentacin y el diseo de la biblioteca de flujos de E S dejan sin resolver. Por ejemplo, hubiera resultad) muy conveniente que pudiramos especificar que se generara una excepcin cada vez que tratramos de sobreescribir un archivo a la hora de abrirlo para llevar a cabo una salida de datos, algunos sistemas de programacin permiten especificar que queremos abrir un archivo de salida, pero slo si ste no existia anteriormente. En Java, parece que debemos utilizar un objeto File para determinar si existe un archivo, porque si lo abrimos como FileutputStream o class Circle extends Shape (

18 Entrada/salida 1738 FileWriter. siempre ser sobreescrito.

La biblioteca de flujos de E/S tiene sus ventajas y sus inconvenientes; se encarga de realizar una parte de la tarea y es una biblioteca portable. Pero si no estamos familiarizados con el patrn de diseo Decorador, el diseo de la biblioteca no resulta intuitivo, por lo que existe una cierta cuna de aprendizaje y tambin requiere ms esfuerzo a la hora de explicar el funcionamiento de la biblioteca a los que estn aprendiendo el lenguaje. Asimismo, se trata de una biblioteca incompleta; por ejemplo, no tendramos por que tener necesidad de escribir utilidades como TcxtFlle (la nueva utilidad PrlntWrtter de Java SE5 representa un paso en la solucin correcta, pero slo se trata de una solucin parcial). Se han efectuado grandes mejoras en Java SE5, aadiendo por ejemplo los mecanismos de formateo de calida que siempre han estado soportados en prcticamente todos los dems lenguajes

Una vez que comprendamos el patrn de diseo Decorador y que comencemos a utilizar la biblioteca en aquellas situaciones donde haga falta la flexibilidad que sta proporciona, comenzaremos a sacar provecho de su diseo, siendo esa ventaja suficiente para compensar las lincas de cdigo adicionales que se requieren para incorporar esa funcionalidad. Puede cnconrrar las soluciono u los ejercicio cleccionados en el documento clcctromco 77** Thmking w Ja\u Anmnaied Solunmi (Juuie. disponible paro ta veniu en MincJl/f/acftfrwraf

class Circle extends Shape (

/Tipos enumerados

1 9

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.1

Las enumeraciones se han introducido bre\ emente al final del Captulo 5, metalizacin y limpieza. Sin embargo, ahora que comprendemos los temas ms avanzados de Java, podemos realizar un anlisis ms detallado de la funcionalidad de la enumeracin incluida en Java SF.5. Como veremos, las enumeraciones nos permiten realizar cosas enormemente interesantes, aunque este capitulo tambin nos permitir comprender mejor otras caractersticas del lenguaje que ya hemos presentado antes, como los genricos y el mecanismo de reflexin Tambin hablaremos de unos cuantos patrones de diseo adicionales. Caractersticas bsicas de las enumeraciones

Como hemos visto en el Captulo 5, Inicializacin y limpieza, podemos recorrer la lista 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 utilizar la matriz resultante en. por ejemplo, un bucle foreach.

Cuando se crea una enumeracin, el compilador genera por nosotros una clase asociada. Esta clase hereda automticamente de java.lung.F.nuni. lo que proporciona ciertas capacidades que se ilustran en el siguiente ejemplo; //: enumerated/EnumClass.java // Capacidades de la clase Enum mport static net .mindview.util. Print. enum Shrubbery ( GROUND, CRAWLING, HANGING ) public class EnumClass ( puble static void main(String[J args) ( for<Shrubbery s : Shrubbery.vales()) ( print (3 + " ordinal: M s. ordinal ()); printnbIs.compareTo(Shrubbery.CRAWLI NGi + " ")j printnb(s.equals (Shrubbery.CRAWLING) + " ")j print(s Shrubbery.CRAWLING); print(s.getDeclaringClass()); print(s.ame(1); print ("-------------"); // Generar un valor enum a partir de una cadena de caracteres: for(Srring s : "HANGING CRAWLING GROUND". split(" ")) { Shrubbery shrub = Enum.valueOf(Shrubbery.class, s)/ print(shrub) ' Joshua Bloch me hn ayudado enormemente en el desarrollo de ente capitulo.

) } / Output : GROUND ordinal: 0 -1 false false class Shrubbery GROUND CRAWLING ordinal: 1 true true class Shrubbery* CRAWLING
0

HANGING ordinal: 2 false false class Shrubbery HANGING


1

HANGING CRAWL ING GROUND ///:-

El mtodo ordinaK ) genera un valor int que indica el orden de declaracin de cada instancia cnum. comenzando en cero. Siempre podemos comparar con seguridad instancias enum utilizando =, y los mtodos cquals( ) y hasli('odc( ) 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 Seriali/able.

Si invocamos getDeclaringClass( ) para una instancia enum. podemos averiguar cul es la clase enum que se utiliza como envoltorio.

El mtodo name( ) devuelve el nombre tal como est declarado, y sto es lo que devuelve tambin el mtodo toString( ) El mtodo valueOf( ) es un miembro esttico de Enum y devuelve la instancia enum correspondiente al nombre (en forma de cadena de caracteres) que se le pase; si no se localiza ninguna correspondencia, se genera una excepcin. Utilizacin de importaciones estticas con las enumeraciones

Analizamos una variante del programa Burrto.java del Capitulo 5, inicializaein y limpieza; //: enumerated/5piciness.java package enumerated; public enum Spiciness ( NOT, MILD, MEDIUM. HOT, FLAMING ) ///<//: enumerated/Burrito.j ava package enumerated; import static enumerated.Spiciness.*; public class Burrito ( Spiciness degree; public Burrito(Spiciness degree: ( this.degree - degree;) public String toStringO { return Burrito is M+- 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 ///:-

La importacin estatica trae todos los identificadores de instancias enum al espacio de nombres loca), asi que no es necesario cualificarlos. Se trata de una buena idea o seria mejor ser explcito y cualificar todas las instancias enum'* La respuesta depender, probablemente, de nuestro cdigo. Fl compilador no nos permitir en ningn caso utilizar el tipo incorrecto, por lo que lo nico que nos debe preocupar es si el cdigo resultar confuso para el lector. En muchas situaciones. 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 predeterminado (al parecer, se produjeron algunas discusiones dentro de Sun sobre si debia permitir hacer esto). Adicin de mtodos a una enumeracin

Salvo por el hecho de que no podemos heredar de ella, una enumeracin puede tratarse de forma bastante similar a las clases normales. Esto quiere decir que podemos aadir mtodos a una enumeracin. Resulta incluso posible que una enumeracin disponga de un mtodo main( ).

Podemos, por ejemplo, generar para una enumeracin, descripciones que difieran de la proporcionada por el mtodo toString( ) predeterminado, que simplemente proporcione el nombre de esa instancia enum Para hacer esto, debemos proporcionar el constructor con el fin de capturar informacin adicional y mtodos adicionales que proporcionen una descripcin ampliada, como en el ejemplo siguiente: //: enumerated/OzWitch.java // Las brujas en la tierra de Oz. import static net .mmdview.util .Print. ; public enum OzWitch { // Las instancias deben definirse primero, antes de los mtodos: 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")j private String description; El constructor debe tener acceso de paquete o privado: private OzWitch(String description) ( this.description = description;
II

) public String getDescription<I { return description; } public static void main(String[] argsl { for(OzWitch witch : OzWitch.values()) print (witch -r " * witch.getDescriptlor.()) ;

) ) /* Output: WEST: Miss Gulch, aka the Wicked Witch of West NORTH: Glinda, the Good Witch of the EAST: Wicked Witch of the East, wearer of Ruby Slippers, crushed by Dorothy's house Good by inference, but missing *///:the North the SOUTH:

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 definirlas despus de algunos de los mtodos o campos, obtendremos un error en tiempo de compilacin.

El constructor y los mtodos tienen la misma forma que las de las clases normales, porque se trata de una clase norma!. slo que con algunas restricciones. As que podemos hacer prcticamente todo lo que queramos con las enumeraciones (si bien lo ms normal es que empleemos enumeraciones simples).

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 utilizarse para crear las instancias enum que se declaren dentro de la definicin de la enumeracin; el compilador no nos permitir utilizarlo para crear una nueva instancia una ve/ que est completada la definicin 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 instancias son correctos, pero queremos reformatcarlas de cara a su visualizacin. La sustitucin del mtodo toString() en una enumeracin es igual que la sustitucin en una clase normal: //: enumerated/SpaceShip.j ava public enum SpaceShip { SCOUT. CARGO. TRANSPORT. CRUISER. BATTLESHIP, MOTHERSHIP; public String toStringO { String id = ame(); String lower = id.substring(1).toLowerCase(); retum id.charAtIO) + lower;

) public static void main(String {] args) { for(SpaceShip s : vales{)) ( System, out. println (a) ,* 1

) ) /* Output; Scout Cargo Transport Cruicer Battleship Mothership

*'///:-

El mtodo toStrmg( ) obtiene el nombre de la instancia SpaceShip invocando nnme( ). 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 entero asociado y la posicin de una instancia puede obtenerse mediante el mtodo ordinaK ) (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 aqui un ejemplo que emplea una enumeracin para crear un pequea mquina de estados: //: enumerated/TrafficLight.java // Enumeraciones en instrucciones switch. import static net .mmdview. til. Print. ; // Define un tipo enum: enum Signa1 j GREEN, YELLOW, RED, } public class TrafficLight { Signal color = Signal.RED; public void change) ( switch (color) { // Observe que no hay por qu escribir Signal.RED // dentro de la instruccin case: case RED; color = Signal.GREEN; break; case GREEN:color = Siana1.YELLOW; break; case YELLOW: color Signal.RED; break;

public String toStringO ( retum "The traffic liaht is " + color;

) public static void mam (String [] args) { Traf f icLight t < new Traf f icLight f) ; forUnt i 0; i < 7; 1-H-) { print(t>; t.change( ) ; i

) / Output: Thetrafficlight isRED Thetrafficlight isGREEN Thetrafficlight isYELLOW Thetrafficlight IBRED Thetrafficlight isGREEN Thetrafficlight isYELLOW Thetrafficlight isRED

*///*-

El compilador no se queja de que no haya ninguna instruccin default dentro de la estructura switch, pero eso no se debe a que detecte que hay instrucciones case para cada instancia Signal. Si desactivamos una de las instrucciones case mediante un comentario, el compilador seguir sin quejarse. Esto quiere decir que es necesario tener cuidado y comprobar explcitamente que todos los casos estn cubiertos. Por otro lado, si estamos ejecutando la instruccin return dentro de las instrucciones case, el compilador si se quejar 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 TrafficLight.java de modo que no haya qu cualificar

tas instancias enum El misterio de values()

Como hemos indicado anteriormente, el compilador se encarga de crear automticamente todas las clases enum y esas clases heredan de la clase Enum. Sin embargo, si examinamos Enum. veremos que no hay ningn mtodo values( ). a pesar de que nosotros si que lo hemos estado utilizando. Existe algn otro mtodo oculto? Podemos escribir un pequeo programa basado en el mecanismo de reflexin para averiguarlo: //: enumerated/Reflectlon.java // Anlisis de enumeraciones utilizando el mecanismo de reflexin. import java.lang.reflect.; import java.til.*; import net.mindview.til.*; import static net.mindview.til.Print; enum Explore { HERE, THERE } public class Reflection { public static Set<String> analyze{Class<?> enumClass) ( print (" Analyzing " -f enumClass * print("Interfaces: " ) for(Type t : enumClass.getGenericInterfaces()} print(t): print(MSase: " enumClass.getSuperclass()); print I"Methods: n); Set<Strlng> methods = new ">;

TreeSet<String>O: forMethod m : enumClass.getMethodsO) met hods.add(m.ge tName t); print(methods) j return methods;

public static void mam (String [] args) { Set<String> exploreMethods = analyze(Explore.class); Set<String> enumMethods = analyze (Enum. class) print("Explore.containsAlIfEnum)? " + exploreMethods.containsAll(enumMethodsj ); printnb(N Explore.removeAl1{Enum): "); exploreMethods.removeAll(enumMethods); print(exploreMethods); // Descompilar el cdigo para la enumeracin: i OSExecute.command<Mjavap Explore);

) /* Output: - - - Analyzing class Explore Interfaces: Base: class java.lang.Enum Methods: IcompareTo. equals. getClass. getDeclaringClass. hashCode. name, notify, notifyAll, ordinal, toString, valueOf, values, wait] -- Analyzing class java.lang.Enum Interfaces: java.lang.Comparable<E> interface java.io.Serializable Base: class ] ava.lang.Object Methods: [compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, wait} Explore.concainsAll(Enu m)? true Explore.removeAll(Enum) : [values] Compiled from "Reflection.java final class Explore extends java.lang.Enum{ public static final Explore HERE; publicstatic final Explore THERE;

publicstatic publicstatic 1 static (};

finalExplore U values () ,* Explore valueOf(java.lang.String);

*///:-

Asi que la respuesta es que \alues() es un mtodo esttico aadido por el compilador. Debemos ver que tambin se aade a Explore el mtodo vaiueOf() dentro del proceso de creacin de la enumeracin. Esto resulta un tanto confuso, porque tambin existe un mtodo valueOff) que forma parte de la clase Enum, pero dicho mtodo tiene dos argumentos y el mtodo aadido slo dispone de uno. Sin embargo, la utilizacin del mtodo Set slo comprueba los nombres de los mtodos v no las signaturas, por lo que despus de invocar Explore.removeAll(Enum), lo nico que queda es |values|

A la salida, podemos ver que Explore ha sido definido como final por el compilador, por lo que no podemos heredar de una enumeracin. Tambin existe una clusula de inicializacin esttica, la cual podemos redeflmr como veremos ms tarde.

Gracias al mecanismo de borrado de tipos (descrito en el Capitulo 15. Genricos)r el descompilador no dispone de informacin completa acerca de Enum. por lo que muestra la clase base de Explore como una clase Enum simple, en lugar de Enum<Explore>

Puesto que values( ) es un mtodo esttico insertado dentro de la definicin de enum por el compilador, si generalizamos un tipo enum a Enum. el mtodo values ) no estar disponible. Observe, sin embargo, que existe un mtodo getEnumConstants() en Class, por lo que incluso values() no forma parte de la interfaz Enum. podemos seguir obteniendo las instancias enum a travs del objeto Class: //; enumerated/TJpcastEnum. java // No hay mtodo values{) si generalizamos la enumeracin enum Search ( HITHER, YON ) public class UpcastEnum ( public static void mam (String (] args) ( Search!) vals ^ Search.values(} ; Enum e Search. HITHER; // Upcast // e.values(); / / No hay mtodo values 1) en Enum for(Enum en : e.getClass <).getEnumConstants1 i System.out.printIn(en);

) } /* Output: HITHER YON

///:-

Como getEnumConstantst ) es un mtodo Class, podemos invocarlo para una clase que no tenga enumeraciones

//: enumerated/NonEnum.j ava public class NonEnum ( public static void main(String 11 args) ( Class<Integer intClass = Integer.class? try ( for(Object en : intClass.getEnumConstants0) SyBtem.out.println(en); } catch(Exception e) { System.out.println(e);

) ) / Output: java.lang.NullPointerException *///:-

Sin embargo, el mtodo devuelve null, asi que se generar una excepcin si tratamos de utilizar el resultado Implementa, no hereda

Ya hemos dicho que todas las enumeraciones amplan java.lang.Enum Puesto que Java no soporta la herencia mltiple, esto quiere decir que no se puede crear una enumeracin mediante herencia: enum NotPossible extends Pet ( ... // No funciona

Sin embargo, si es posible crear una enumeracin que implemente una o ms interfaces: //: enumerated/cartoons/Enumlmplementation.java // Una enumeracin puede implementar una interfaz package enumerated.cartoons; import java.util.*; import net.mindvlew.util; enum CartoonCharacter implements Generator<CartoonCharacter> [ SLAPPY, SPANKY, PUNCHY. SILLY. BOUNCY. NUTTY, BOB; private Random rand = new Random 147) ; public CartoonCharacter next() ( return values( ) [rand.nextInt(values().length)];

) public class Enumlmplementat ion { public static <T> void printNext(Generator<T> ra) ( System.out .orint (rg.next () M, ")?

} public static void main(Stringf] args' ( // Elegir cualquier instancia: CartoonCharacter cc = CartoonCharacter.BOB; for (int i = 0; i < 10; i+-H printNext (cc) ;

) / * Output: BOB. PUNCHY. BOB. 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, prntNext( ).

Ejercicio 2: (2) Fn lugar de implementar una interfaz, defina ie\t( ) como un mtodo esttico. Cules son las venta

jas y desventajas de esta solucin?

Seleccin aleatoria

Muchos de los ejemplos de este capitulo requieren efectuar una seleccin aleatoria entre varias instancias enum, como vimos en CartoonCharacter.next) Es posible generalizar esta tarea utilizando genricos e incluir el resultado en la biblioteca comn: //: net/mmdview/util/Enums.java package net.mindview.util; 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.aetEnumConstants ()) ;

> public static <T> T random(Ttl values) ( ) return values[rand.nexcInt(values.length) 1 ;

} ///:-

La extraa sintaxis <T extends Enum<T describe T como una instancia enum. Pasando Class<T>.

hacemos que este disponible el objeto clase, pudindose asi generar la matriz de instancia enum El mtodo randonH ) sobrecargado slo necesita conocer que se le est pasando un objeto T||, porque no necesita realizar operaciones de la clase Enum: slo necesita 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 random( ): //: enumerated/RandomTest.java import net.mindview.uti1. * ; enum Activity ( SITTING. LYING, STANDING, HOPPING, RUNNING. DODGING, JUMPING, FALLING, FLYING ) public class RandomTest { public static void mainfStringI] args) { for tint i * 0; i < 20; i++) System.out.print(Enums.random(Activity.class) * *

} ) /* Output: STANDING FLYING RUNNING STANDING RUNNING STANDING LYING DODGING SITTING RUNNING HOPPING HOPPING HOPPING RUNNING STANDING LYING FALLING RUNNING FLYING LYING

*///*-

Aunque E n u m es una clase no demasiado compleja, ya veremos en este capitulo que permite ahorrarse muchas duplicaciones. Las duplicaciones tienden a generar errores, asi que eliminar esas duplicaciones es un objetivo bastante importante Utilizacin de interfaces con propsitos de organizacin

La imposibilidad de heredar de una enumeracin puede resultar un tanto (rstante en algunas ocasiones. La razn para tratar de heredar de una enumeracin proviene en parte del deseo de aumentar el nmero de elementos de la enumeracin original, y por otro lado del deseo de crear subeategorias empleando subtipos.

Podemos realizar la categorzacin agrupando los elementos dentro de una interfaz y creando una enumeracin basada en esa interfaz. Por ejemplo, supongamos que tenemos diferentes clases de alimentos y queremos definirlas como enumeraciones, pero sin que por ello las distintas clases de alimentos dejen de ser un tipo de una dase denominada Food. He aqu un ejemplo: //: enumerated/menu/Food.java // Subcategorizacin de enumeraciones dentro de interfaces, package enumera ted. menu,* public interface Food { enum Appetizer implements Food ( SALAD, SOUP, SPRING_ROLLS,* > enum MainCourse implements Food { LASAGNE, BURRITO, PADJTHAI. LENTILS, HUMMOUS, VINDALOO;

) enum Dessert implements Food { TIRAMISU, GELATO, BLACK_FOREST_CAKE, FRUIT, CP. EME_ CARAMEL; ) enum Coffee implements Food ( BLACK_COFFEE. DECAF_COFFEE. ESPRESSO, LATTE, CAPPUCCINO, TEA, HERB_TEA; ) } ///.-

Puesto que el nico mecanismo de subtipos disponible para una enumeracin est basado en la implementaein de interface, cada enumeracin anidada implementa la interfaz envoltorio Food. Ahora si que podemos decir que todo es un tipo de Food, como podemos ver aqu: //: enumerated/menu/TypeOfFood.java package enumerated.menu; import static enumerated .menu. Food. * public class TypeOfFood ( public static void main(Stringt) args) ( Food food = Appetizer.SALAD; food = MainCourse.LASAGNE; food = Dessert .GELATO; food Coffae.CAPPUCCINO; ) } ///:-

La generalizacin a Food funciona para cada tipo enum que implemema Food, asi que todos ellos 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 disponer de una enumeracin de enumeraciones" podemos crear una enumeracin de nivel superior con una instancia para cada enumeracin de Food //: enumerated/menu/Course.java package enumerated.menu; import net.mindview.util.; public enum Course ( APPETIZERtFood.Appecizer.class). MAINCOURSE(Food.MainCourse.class). DESSERT < Food.Dessert.class) , COFFEE(Food.Coffee.class) ; private Food (] vales; prvate Course(Class<? extends Food> kind) ( vales = kind.getEnumConstants{); } public Food randomSelectionO { return Enums.random(vales); } } un-

Cada una de las enumeraciones anteriores toma el correspondiente objeto Class como argumento del constructor, pudiendo extraer de l todas las instancias enum utilizando getEnumConstunts() y almacenarlos. Estas instancias se utilizan posteriormente en randomSelectionf ). por lo que ahora podemos crear un men generado aleatoriamente seleccionando el elemento de Food de cada plato (Course): //: enumerate/menu/Meal.java package enumerated.menu; public class Meal ( public static void mam (Stringll args) ( for {int i * 0; i < 3; i-i-*) { for(Course course : Course.vales()) ( Food food = course.randomSelectionO System.out.println{food); ) System.out.println" ")

) ) } / Output: SPRING_ROLLS VINDALGO FRU1T DECAF_COFFEE SOUP VINDALOO FRU1T TEA SALAD BURRITO FP.UIT TEA SALAD BURRITO CREME_CARAMEL LATTE SOUP BURRITO TIRAMISU ESPRESSO ///:-

En este caso, la ventaja de crear una enumeracin de enumeraciones es que con ello podemos iterar a travs de cada objeto Course. Posteriormente, en el ejemplo YendingMachine.java. veremos otra tcnica de categori/acin que est basada en un conjunto diferente de restricciones.

Otra solucin ms compacta al problema de la catcgorizacin consiste en anidar enumeraciones dentro de otras enumeraciones, como en el ejemplo siguiente: //: emimerated/SecurityCategory .java // Una subcategorizacin ms sucinta, import net. mindview.util.* j enum Securit/Category { STOCKiSecurity.Stock.class). BOND(Security.Bond.class); SecurityU values; SecurityCategory(Claas<? extends Security kind) ( values = kind.getEnumConstants() ; I interface Security ( enum Stock implements Security { SHORT, LONG. MARGIN } enum Bond implements Security { MUNICIPAL, JUNK } } public Security randomSelection() i return Enums.random(values)r (

public static void mam (String [] args) ( for (int. i O j i < 10/ i-*-*) { SecurityCategory category * Enums.random(SecurityCategory.class); System, out .print In (category M: " category.randomSelection( ! ) ; > ) } /* Output: BOND: MUNICIPAL BOND: MUNICIPAL STOCK: MARGIN STOCK: MARGIN BOND: JUNK STOCK: SHORT STOCK: LONG STOCK: LONG BOND:

MUNICIPAL BOND: JUNK *///:-

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1764 Piensa en Java La interfaz Security es necesaria para recopilar todas las enumeraciones dentro de un tipo comn. Entonces, se realiza la categorizacin dentro de Security Category//: enumerated/menu/Meal2.java package enumerated.menu import net.mindview.util. ; public enum Mea12 { APPETIZER(Food.Appetizer.class), MAINCORSE (Food. MamCourse. cla68) , DESSERT(Food.Dessert.class), COFFEE(Food.Coffee.class) r private FoodU values; private Meal2(Class<? extends Food> kind) { values *> kind.getEnumConstantsI ) ; ) public interface Food ( enum Appetizer implements Food { SALAD. SOUP, SPRTNG_ROLLS;

) enum MamCourse implements Food { LASAGNE, BURRITO, PAD JTHAI, LENTILS, HUMMOUS, VINDALOO;

) enum Dessert implements Food { TIRAMISU, GELATO, BLACK_FOREST_CAKE, FRUIT, CREME_CARAMEL;

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1765 Piensa en Java ) enum Coffee implements Food ( BLACK_COFFEE, DECAF_COFFEE, ESPRESSO, LATTE, CAPPUCCINO, TEA, HERB_TEA;

) public Food randomSelection[) { return Enums.random(values);

) public static void main(String[] args) { fortint i 0; i < 5; i++l { for(Meal2 meal : Meal2.valuesi)) ( Food food = meal.randomSelection(); j System.out.printIn(food) j System.out .printing ;

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1766 Piensa en Java )

) } /* Misma salida que en Meal.java *///;-

Al final, se trata simplemente de una reorganizacin de! cdigo, pero permite obtener una estructura ms clara en algunos casos.

Ejercicio 3: (1) Aada un nuevo objeto Course a Course.java y demuestre que funciona en Mcal.java.

Ejercicio 4: (I) Repita el ejemplo anterior para Meal2.java. Ejercicio 5: (4) Modifique eontrol/VowebAndConMinants.java para que utilice tres tipos de enum; VOWEL.

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1767 Piensa en Java SOMF TIMES_A_VO\N EL y CONSONANT. LI constructor enum debe admitir las distintas letras que describen cada categora concreta de vocales y consonantes. Consejo: utilice varargs v recuerde que stos crean automticamente una matriz.

Ejercicio 6: (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 implcmentar Food? Utilizacin de EnumSet en lugar de indicadores

Un contenedor Sel es una especie de coleccin que slo permite aadir un ejemplar de cada tipo de objeto. Por >upuesto, una enumeracin requiere que todos sus miembros sean diferentes, por lo que podra parecer que tiene un comportamiento similar al de los conjuntos, pero como se puede aadir o eliminar elementos, las enumeraciones 110 resultan demasiado tiles como conjuntos. La clase EnumSet se ha aadido a Java SE5 para funcionar de manera conjunta con las enumeraciones. con el fin de crear un sustituto para los tradicionales bits indicadores* basados en valores enteros. Dichos indicadores se emplean para reflejar algn tipo de informacin de activado-desactivado, pero al final terminamos manipulando bits en lugar de conceptos, por lo que es bastante comn que escribamos cdigo bastante confuso

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1768 Piensa en Java LnumSet est diseada para maximizar la velocidad, ya que debe competir de manera efectiva con los bits indicadores tas operaciones de esta clase sern normalmente mucho ms rpidas que las de llashSct). Internamente, esta clase est representada (en caso de que sea posible) por un nico valor long que se trata como un vector de bits, asi que resulta extremadamente rpida y eficiente. La ventaja es que con ella disponemos de una forma mucho mas expresiva para indicar la presencia

o ausencia de una caracterstica binaria, sin necesidad de preocupamos acerca del rendimientos del programa.

Los elementos de un conjunto EnumSet deben provenir de una nica enumeracin cnum. Veamos un posible ejemplo donde se utiliza una enumeracin de los lugares de un edificio donde hay instalado un sensor de alarma: //: enumerated/AIarmPoints.java package enumeraced; public enurc AlarmPoints ( STAIR1. STAIR2, LOBBY. OFFICE1. OFFICE2. OFFICE3, OFFICE4, BATHROOM, TILITY, KITCHEN } ///-

Queremos utilizar el conjunto EnumSet para controlar el estado de alarma de los sensores //: enumerated/EnumSet3 .java // Operaciones con conjuntos EnumSet package enumeraced; import java.til.*; irnport static enumerated.AlarmPoints .* , import static net.raindview.til.Print;

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1769 Piensa en Java public ciass EnumSet:s ( public static void mainStringU aros) ( EnumSeL<AlarmPoints> pames = EnumSen..noneOf (AlarmPoints.caas); Conjunto vaco points.add(BATHROOM); print(points) r points.adAli(EnumSec.o STAIR1. STAIR2. KITCHEN)J; print(points1; points * EnumSet; .allOf lAlarmPoints.ciass} ; points.removeAll(EnumSet.o(STAIR1, STAIR2. fCITCHEN) ) : print(points1 ; points.removsAli(EnumSet.range OPFICE1# OFFICE4) t; print1 points); points * EnumSet.complementOf(points); print(Doints);

) } / Output: [BATHROOM] (STAIR1, STAIR2, BATHROOM, KITCHEN) [LOBBY, OFFICE1, 0FFICE2, 0FFICE3, OFFICE4, BATHROOM. TILITY] (LOBBY, BATHROOM, UTILITYJ [STAIR1, STAIR2, OFFICE1, OFFICE2, 0FFICE3, 0FFICE4, KITCHENl ///:-

Se utiliza una clausula 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 mismos en la documentacin del S D K . Cuando examine esta documentacin, podr ver un detalle

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1770 Piensa en Java 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 indicio de la preocupacin por el rendimiento que imperaba a la hora de disear la clase EnumSet. porque un nico mtodo of( ) usando varargs podra haber resuelto el problema, pero es ligeramente menos eficiente que si se dispone de argumentos explcitos. Asi. si invocamos 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 in\ocamos con un argumento, el compilador no construye la matriz varargs. asi que no existe ningn gasto de procesamiento adicional si se invoca dicha versin con un nico argumento.

Los conjuntos EnumSet se construyen a partir de valores long. cada valor long tiene 64 bits y cada instancia enum requiere un bit para indicar la presencia o ausencia. Esto significa que podemos tener un conjunto EnumSet para una enumeracin de hasta 64 elementos sin utilizar ms que un nico valor long. Qu sucede si tenemos ms de 64 elementos en la enumeracin? //: enumerated/BigEnumSet. java import java.util.*; public class BigEnumSet ( enum Big { AO, Al, A2, A3. A4. A5, A6. A7. A8. A9, A10, public static void main(String[] args) { EnumSet<Big> bigEnumSec EnumSet .allOf {Big.class) ; System.out.println(bigEnumSet) ,*

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1771 Piensa en Java ) ) / Output: [AO, Al, A2, A3, A4, A5, A6, A7, AB, A9, A10, All. A12, ///i-

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 funciona Utilizacin de EnumMap

elcdigo fuentecorrespondiente

aEnumSet

\explique

como

Un mapa EnumMap es un tipo de mapa especializado que requiere que sus claves formen pane de la misma enumeracin. Debido a las restricciones impuestas en las enumeraciones, un mapa EnumMap puede implementarse internamente como una matriz. Por tanto, son extremadamente rpidos, asi que podemos utilizar libremente los mapas EnumMap para bsquedas basadas en enumeraciones

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1772 Piensa en Java nicamente podemos invocar el mtodo put( ) para aquellas claves que formen parte de nuestra enumeracin, 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 diseo basado en comandos. Este patrn comienza con una interfaz que contiene (normalmente) un nico mtodo y crea mltiples implementaciones de dicho mtodo, cada una con un comportamiento distinto. Basta con instalar los objetos Command y el programa se encargar de llamarlos cuando sea necesario: //: enumerated/EnumMaps.java // Fundamentos de los mapas EnumMap. package enumerated; import java.ut11.*; import static enumerated. AlarmPoints .*; import static net.nundview.til.Print.; interface Command { void actiont); ) public ciass EnumMape ( public static void main(Stringl] aros) ( EnumMap AlarmPoints, Command em = r.ew EnumMapAiarmPoints, Command> (AlarmPoints . ciass); em. put l KITCHEN 4 new Command lJ ( public void actonO { print ("Kitchen fireflr } i* em.put(BATHROOM, new Commandi) { public void actionO ( print("Bathroom alert!"); )

}); for iMap.Entry<AlarmPoints,Command> e : em.entrySeci)) ( printnb(e.getKey O - ": "J; e.getValu().act ion(Ir i try { // Si no hay ningn valor para una clave concreta: em.get (UTILITY) .action {) ; ) catch(Exception e) ( printle);

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1773 Piensa en Java )

) } /* Output: BATHROOM: Bathroom alert! KITCHEN: Kitchen fire! 3 ava.lang.NullPointerException *///:-

Al igual que con EnumSet. el orden de los elementos en EnumMap est determinado por su orden de definicin dentro de la enumeracin enum.

La ltima parte de niaint ) muestra que siempre hay una entrada de clave para cada una de las instancias de la enumeracin, pero el valor ser nuil a menos que hayamos invocado put( ) para dicha clave.

lina ventaja de un mapa EnumMap con respecto a los mtodos especficos de constante (que se describen a continuacin) es que el mapa EnumMap permite modificar los objetos que representan los valores, mientras que, como veremos, los mtodos especficos de constante se fijan en tiempo de compilacin.

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1774 Piensa en Java Como veremos posteriormeme en el captulo, los mapas EnumMaps pueden utilizarse para tareas de despacho mltiple en aquellas situaciones en las que se disponga de mltiples tipos de enumeraciones que interaccionen entre s. Mtodos especficos de constante

La enumeraciones Java llenen una caracterstica muy interesante que nos permite asignar a cada instancia enum un comportamiento distinto, creando mtodos para cada una de ellas. Para hacer esto, definimos uno o ms mtodos abstractos como pane de la enumeracin, y luego definimos los mtodos para cada instancia enum. Por ejemplo: //: enumerated/ConstantSpecificMeth od.java import java.til.*; import java.text.*; public enum ConstantSpecificMethod { DATE_TIME { String getInfo O ( return ). ) DateFormat.getDateInstance(>.format(new DateO);

CLASSPATH ( String getInfo() ( return System getenvI"CLASSPATH");

)r

VERSION ( String getInfo(J { return System.getProperty(*java.version"); i

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1775 Piensa en Java }; abstract String getlnfod; public static void main(String!] args) { for(ConstantSpecificMethod csm : values!)) System.out.printIn(csm.getInfo());

) ) / (Execute to see output) *///:-

Podemos buscar e invocar los mtodos a travs de su instancia enum asociada. Esto se denomina a menudo cdigo conducido por tablas (observe, en especial, la similitud con el patrn de diseo Comando mencionado anteriormente).

En la programacin orientada a objetos, se asocia un comportamiento distinto con las diferentes clases. Dado que cada instancia de una enumeracin puede tener su propio comportamiento mediante mtodos especficos de constante, esto sugiere que cada instancia es un tipo de datos distinto. En el ejemplo anterior, cada instancia enum se trata como el tipo base C onstantSpecificMethod, pero obtenemos un comportamiento polimrico con la llamada a mtodo etlnfc>( ).

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1776 Piensa en Java Sin embargo, esa similitud no puede llevarse ms lejos. No podemos tratar las instancias enum como si fueran tipos de

clases: //: enumerated/NotClasses. java // {Exec: javap -c LikeClasses} import static net.mindview.util.Prin t.*; enum LikeClasses { WINKEN { void behavior() { print("Behavior1*)/ ) ). BLINKEN { void behavior) ( print("Behavior2"]; ) (, NOD { void behavior() { print("Behavior3" ) ; ) }; abstract void behavior();

) public class NotClasses ( // void fl (LikeClasses. WINKEN instance) {) //No se puede ) / Output: Compiled from "NotClasses.java" abstract class LikeClasses extends java.lang.Enum{ publicstatic final LikeClasses WINKEN; publicstatic publicstatic final LikeClasses final LikeClasses BLINKEN; NOD;

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1777 Piensa en Java ///:-

En fl( ). podemos ver que el compilador no permite utilizar una instancia enum como un tipo de clase, lo que tiene bastante sentido si consideramos que el cdigo generado por el compilador: cada elemento enum es una instancia de tipo static final de LikeClasses.

Asimismo, como son 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 \ 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: //: enumerated/CarWash.nava import java.Util - ,* import stat le net.mindvlew. til. Print. / public ciass CarWaeh { public enum Cycle { UNDERBODY ( void action O ( print("Spraying the underbody"J; } ). WHEELWASH { void actlcaU I print("Washing the wheels"); ) }. PREWASH { void action I I ( print("Loosening the dirt"); }

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1778 Piensa en Java ]. BASIC { void actionI ( print("The basic wash"); )

1. HOTWAX ( void action() { I.

print [MAoplying hot wax")/ )

RINSE { void action() { print ("Rinemg") ; )

IBIOWRY ( void action() { print("Blowing dry"); }

)/ abstract void act ion O;

i EnumSec<Cycle> cycles = EnumSet .of (Cycle.BASIC, Cycle.RINSEJ public void add(Cycle

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1779 Piensa en Java cycle) { cycles add (cycle) .* } public void washCar() ( for(Cycle c : cycles) c.action() t i public String toStnngd { return cycles. toStringI) t ) public static void main(StringU args) { CarWash wash * new CarWashO? print(wash); wash.washCarI); // SI orden de adicin no es importance: wash.add(Cycle.BLOWDRY); wash.add(Cycle.BLCWDRY); // Se ignoran los duplicadas wash.add(Cycle.RINSE); wash.add(Cycle.HOTWAX); print(wash); wash.washCarI);

) ) /* Output: [BASIC, RINSE] The basic wash Rinsing [BASIC, HOTWAX, RINSE, BLOWDRY] The basic wash Applying hot wax Rinsing Blowng dry *///:-

La sintaxis para definir un mtodo especifico constante es. en la prctica, la de una clase interna annima, pero ms sucinta.

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1780 Piensa en Java Este ejemplo muestra tambin otras caractersticas adicionales de los conjuntos EnumSet. Puesto que se trata de un conjunto. slo permitir almacenar un ejemplar de cada elemento, asi que las llamadas duplicadas con add( ) con el mismo argumento 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 determinado por el orden de declaracin dentro de la enumeracin.

Es posible sustituir los mtodos especficos de constante, en lugar de implementar un mtodo abstracto? Si que es posible, como podemos ver aqu: //: enumerated/OverrideConstantSpeci fic.java import 3tatlc net.mindview.til.Print; public enum OverrideConstantSpecific ( NUT, BOLT. WASHER ( void f) ( print("Overridden method"); )

). void f() { print("default behavior*); ) public static void main(String[] args) ( for(OverrideConstantSpecific oes : vales{)> { printnb(OC6 + " : ocs.f () ;

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1781 Piensa en Java } ) /* 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 experimentar con ellas como si tratara de clases normales. Cadena de responsabilidad en las enumeraciones

En el patrn de diseo Cadena de responsabilidad, creamos una serie de diferentes formas de resolver un problema y las encadenamos. Cuando tiene lugar una solicitud se la pasa a travs de la cadena hasta que se cncucmra una de las soluciones que pueda gestionarla.

Podemos implcmemar 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 posible entregarlo. Cada uno de los intentos puede considerarse como un tipo de Estrategia (otro patrn de diseo) y la lista completa forma una Cadena de responsabilidad.

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1782 Piensa en Java Comenzaremos describiendo lo que es un envo postal. Todas las diferentes caractersticas de inters pueden expresarse utilizando enumeraciones. Puesto que los objetos Mal (que representan los envos postales) se generarn aleatoriamente, la forma ms fcil de reducir la probabilidad de que. por ejemplo, a un envo postal le corresponda un valor VES para GeneralDeliverv (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.

Dentro de Mail, vemos el mtodo randomMail( ). que erea ejemplos aleatorios de envos postales de prueba. El mtodo generator( ) produce tin objeto Iterable que utiliza randomMaiU ) para generar una serie de objetos que representan envos postales, generndose un objeto cada vez que se invoca next( ) a travs del iterador. Csta estructura permite crear de manera sencilla un bucle foreach invocando Mail.generator( ): //: enumerated/PostOffice.java // Modelado de una oficina de correos. import j ava.ut il.*; import net.mindview.util.; import static net.mindview.util.Print; class Mail { f t Los valores NO disminuyen la probabilidad de la seleccin aleatoria: enum GeneralDelivery {YES,NOl.N021 N03.N04,N05) enum Scannability {UNSCANNABLE,YES1,YES2,YES3,YES4} enum Readability {ILLEGI3LE,YES1,YES2,YES3,YES4} enum Addre s s {INCORRECT.OKI,0K2,0K3.0K4,0K5.0K6) enum ReturnAddress {MISSING.OKI,OK2,0K3,OK4, 0K5) GeneralDelivery generalDelivery; Scannability scannability; Readability readability; Address address; ReturnAddress returnAddress static long counter = 0; long id = counter++; public String toStringO ( return "Mail " id; ) public String details 0 ( return toString() ",GeneralDelivery: " * generalDelivery -* , Address Scanability:" + scannability + ",Address Readability: " * readability + ",Address Address: " address

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1783 Piensa en Java ", Return address: H + returnAddress;

) f t Generar objeto Mail de prueba: public static Mail randomMail() ( Mail m = new Mailt); 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>0 ( public boolean hasNextO { return n*- > 0; } public Mail next U ( return randomMail<); ) public void remove 0 { // No implementado throw new UnsupuortedOperationException()*

Si utilizamos esta solucin con el ejemplo Food, el resultado seria: 1784 Piensa en Java )

);

1785 Piensa en Java }public class PcstOffice { enum MailHandler { GENERALJ3BLIVERY ( boolean handle(Mall m) ( switchim.generalDeliveryJ j case YES: print("Using general delivery for " - m) ; return true; default: return false;

1 I. KACHINE_SCAN ( boolean handle(Mail m) ( awitch(m.scannability) { case UNSCANNABLE: return false; default: switch(m.address) ( case INCORRECT: return false; default: print ("Delivering " m " automatically" J ,* return true;

public static void main(String[] args) {

1786 Piensa en Java ) )

) ).

VISUALINSPECTION ( boolean handle(Mail m) ( switch (in. readability) ( case ILLEGIBLE: return false; default: switchtm.address) { case INCORRECT: return false; default: print ("Delivering - m + " normally"); return true;

public static void main(String[] args) {

1787 Piensa en Java ). RETURN_TO_SENDER | boolean handle(Mail m) | switchim.returnAddrese) { case MISSING: return false; default: print ("Returning " + m " to sender") ; return true;

) l abstract boolean handle(Mail m); static void handle(Mail m) ( for(MailHandler handler : MailHandler.values() 1 ifihandler handle(m)) return; print(m + " is a dead letter");

) for(Mail mail * Mail.generator 110) ) ( print(mail.details I)); public static void main(String[] args) {

1788 Piensa en Java handle(mail); print )

} / Output : Mail 0, General Delivery: N02, Address Scanability: UNSCANNA3LE, Address Readability: YES3. Address Address: OKI, Return address : OKI Delivering Mail 0 normally *># Mail 1, General Delivery: N05, Address Scanability: YES3, Address Readability: ILLEGIBLE. Address Address: OK5. Return address: OKI Delivering Mail 1 automatically *T*f* Mail 2. General Delivery: YES, Address Scanability: YES3, Address Readability: YES1. Address Address: OKI, 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: 0K4 Returning Mail 3 to sender

**

Mail 4, General Delivery: N04, Address Scanability: UNSCANNABLE. Address Readability: YESl, Address Address: INCORRECT, Return address : OK2 Returning Mail 4 to sender ***** Mail S, General Delivery: N03, Address Scanability: YES1, Address Readability: ILLEGIBLE, Address Address: 0K4. Return addresst 0K2 Delivering Mail 5 automatically *-** Mail 6r Ccncsral Delivery: YES, Addresa Scanability; YE34, Address Readability: ILLEGIBLE, Address Address: 0K4, public static void main(String[] args) {

1789 Piensa en Java Return address : 0K4 Using general delivery for Mail 6 * * ft ft
*

Mail 7, General Delivery: YES, Address Scanability: YES3, Address Readability: YES4, Address Address: 0K2, Return address: MISSING Using general delivery for Mail 7 ***** Mail fl. General Delivery: N03, Address Scanability: YES I, Address Readability: YES3, Address Address: INCORRECT, Return address: MISSING Mail 8 is a dead letter Mail 9, General Delivery: NOI, Address Scanability: UNSCANNABLE, OKI, Address Readability: YES2, Address Address:

Return address: 0K4 Delivering Mail 9 normally *///: La Cadena de responsabilidad se expresa en la enumeracin enum Maill landler. y el orden de las definiciones enum determina el orden en el que se intentarn aplicar las diferentes estrategias para cada envo postal. Se intenta aplicar cada una de las estrategias por tumos 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:

(6) Modifique PostOffice.jnva para incluir la capacidad de reenviar correo

Ejercicio 9:

(5) Modifique la clase PostOfflce para que utilice un mapa EnumMap.

public static void main(String[] args) {

1790 Piensa en Java Proyecto:46 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 mquinas de estado. Una mquina de estado puede encontrarse en un nmero finito de estados especficos. Normalmente, la mquina pasa de un estado al siguiente basndose en una entrada, pero tambin existen estados transitorios: la mquina sale de estos estados en cuanto se lia realizado la correspondiente tarea.

Existen ciertas entradas permitidas para cada estado y las diferentes entradas cambian el estado de la mquina a diferentes nuevos estados. Puesto que las enumeraciones reducen el conjunto de posibles casos, resultan muy tiles para enumerar los diferentes estados y entradas.

Cada estado tiene tambin normalmente algn tipo de salida asociada.

*' Los proyectos son sugerencia que pueden utilizarse, por ejemplo, comu proyectos de fin de curso. Las soluciones a los proyectos oo se incluyen en la Guia de soluciones. public static void main(String[] args) {
46

1791 Piensa en Java Una mquina expendedora es un buen ejemplo de mquina de estados. En primer lugar, definimos las entradas dentro de una enumeracin: //: enumerated/Input.java package enumerated; import java.utii,*; public enum Input ( NICKEL (5) , DTMEUO), QUARTER (25) , DOLLAR(iOO), 700THPASTE1200}. CHIPS(75), SODA(IOO), SOAP50), ABORTJTRANSACTION { public int amountO ( // No permitir throw new RuntimeExcepcion (** ABORT. amount ()*);

).

STOP { // Esta debe ser la ltima instancia, public int amountO ( // No permitir throw new RuntimeExcept ion (' SHUT_DOWN. amount O ") / )

).int valu; // En centavos Input(int valu! ( this.valu = valu; ) InputO {) public static void main(String[] args) {

1792 Piensa en Java int amountO retum valu; ); // En centavos static Random rand = new Randoml47); public static Input randomSelection() ( f ! No incluir STOP: retum vales () [rand.nextInt (vales (). length 1)];

} ///:-

Observe que dos de las entradas Input tienen una cantidad asociada, asi que definimos el mtodo amunt() para representar 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 diseo un poco extrao (definir un mtodo en una interfaz y Juego generar una excepcin si se lo invoca para ciertas implementaciones). nos vemos obligados a utilizarlo debido a las restricciones de la enumeraciones.

public static void main(String[] args) {

1793 Piensa en Java El objeto \ VndingMacliinc (mquina expendedora) reaccionar a estas entradas categorizndolas primero mediante la enumeracin Category, para poder conmutar mediante switch entre las diferentes categoras. F.ste ejemplo muestra cmo las enumeraciones consiguen que el cdigo sea ms claro y ms fcil de gestionar: //t enumerated/VendingMachine.java // (Args: VendingMachineInput.txt) package enumerated; import java.utll.*; 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), QtTIT_TRANSACTION (ASORT_TRANSACTION) . SHUTJDOWN(STOP); private Input[] values; Category(Input... types) { values = types; ) private static EnumMapdnput, Category categories = new EnumMap<Input,Category>(Input. class); static { or(Category c ; Category.class.getEnumConst ants()) for(Input type : c.values) cateaories.put(type, c);

] public scatIC Category categorize(Input input) { return cateqories.get public static void main(String[] args) {

1794 Piensa en Java (input)

) public class VendingMachine { private static State state = State.RESTING; private static int amount = Oj private static Input selection = null; enum StateDuration { TRANSIENT ) // Enumeracin de marcacin enum State { RESTING \ void nextiInput input) ( switch(Category.c ategorize(input)) ( case MONEY: amount -*= input. amount t) ; state = ADDXNG_MONEY; break; case SHUT_DOWN: state = TERMINAL; default: }

public static void main(String[] args) {

1795 Piensa en Java ) h ADDING_MONEY ( void next(Input input) { switch(Category.categorize(input)) {case MONEY: amount += input.amountI}; break ; case ITEM_SELECTION: selection # input,* if(amount < selection.amount(J) print I "Insufficient money for " -* selection); else state = DISPENSING; break; case QUIT_TRANSACTIGN: state = GIVING_CHANGE; break; case SHUT_DOWN: State = TERMINAL; default?

DIS P ENSING(St at eDura 1 1on. TRANS I ENT) ( void next <) ( print("here is your " + selection); amount -= selection.amount(J; state - GIVING_CHANGE;

public static void main(String[] args) {

1796 Piensa en Java h )

GIVING_CHANGE(Stat eDuration.TRANSIENTI { void next () { if(amount >0) { i print ("Your change; = 0; amount); amount

state = RESTING:

).

TERMINAL { void output() ( print("Halted"); ) ); private boolean isTransient = false? State() {) State(StateDuration transl { isTransient = true; j void next(Input input) { throw new RuntimeExceptioni"Only call " *next(Input lnoutl for non-transient states"!;

) void next I) { throw new RuntimeException IOnly call next O for + "StateDuration.TRANSIENT statesH1);

public static void main(String[] args) {

1797 Piensa en Java ) void output 0 { print (amount) j )

I static void run IGenerator<Input gen) ( while(state 1= State.TERMINAL) ( Btate.next(gen.next(I); while(state.isTransient) state.next(); state.output{);

} public static void main (String [] args) { Generator<Input> gen = new RandomlnputGeneratorI); if (args. length 1,

public static void main(String[] args) {

i 19 Tipos enumerados 1798 'gen = new FilelnputGeneratcr{arga[0]l; run (gen) ;

> // Comprobacin bsica de que todo est en orden: ciass RandamlnputGer.erator implementa GeneratorInput> { public Input next() { retum Input .randomSelection 0 ; ) j // Crear objetos Input a partir de un archivo de cadenas separadas por * i class FiielnputGenerator implements Generator<Input> ( prvate Iterator<String> input; public PilelnputGenerator(String fileName) ( input * new TextFile(fileName, ** M) . iterator O ;

! public Input next(i ( i l input. hasNext I) ) return nuil:


if

retum Enum. valueOf (Input .claas, inuut .next O . trim ()) ;

i 19 Tipos enumerados 1799 ) ) / Output: 25 50 75 here is your CHIPS 0 1 0 0 2 0 0 here is your TOOTHPASTE 25 35 Your change: 35 0 25 35 InEufficient maney for SODA 35 60 70 75 Insufficient money for SODA 75 Your change: 75 0 Halted

i 19 Tipos enumerados 1800 ///:-

Puesto que la seleccin entre las distintas instancias enum se suele realizar con una instruccin a witch (observe el esfuerzo adicional que ha hecho el lenguaje para que se pueda aplicar fcilmente una instruccin switeh a las enumeraciones), una de (as cuestiones ms comunes que podemos preguntamos a la hora de organizar mltiples enumeraciones es "Qu es lo que quiero utilizar para la instruccin switeh?** Aqu, lo mas fcil es proceder en sentido inverso a partir del objeto VendlngMachine observando que en cada estado State, necesitamos conmutar con switcli 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 estos 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 categor/e( ) puede producir el elemento apropiado Category dentro de una instruccin switeh. Este mtodo utiliza un mapa EuumMap para realizar las bsquedas de manera eficiente y segura.

Si estudiamos la clase VendingMachine, podemos ver cmo cada estado es diferente y responde de forma distinta a las entradas. Observe tambin los dos estados transitorios. En runt ) la mquina espera una entrada Inpnt y no deja de pasar a travs de los estados hasta que deja de estar en un estado transitorio.

El sistema VcndingMachinc puede probarse de dos formas, utilizando dos objetos Gcnerator diferentes El objeto RandomlnputGenerator simplemente genera de forma continua una serie de entradas, exceptuando SHUT DOWN que hace que se pare la

i 19 Tipos enumerados 1801 mquina. Ejecutando este procedimiento durante un tiempo lo suficientemente largo, podemos comprobar que todo est en orden y verificar que la mquina no entrar en un estado incorrecto. El objeto FilelnputGenerator toma un archivo que describe las entradas en forma textual, las conviene en instancias enum y crea objetos Input. He aqu un archivo de texto utilizado para producir la salida mostrada en el ejemplo: //;1 enumerated/VendingM achineInput.txt QUARTEF.; QUARTER; QUARTER; CHIPS; DOLLAR; DOLLAR; TOOTHPASTE: QUARTER; DIME; ABOP.T_TRANSACTION ; QUARTER; DIME; SODA; QUARTER; DIME; NICKEL; SODA; ABORT TRANSACTION; STOP; //Ai-

Una limitacin de este diseo es que los campos de VendingMachine a los que acceden las instancias de la enumeracin enum 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 una implementacin real (Java embebido), ya que lo normal es que slo tengamos una aplicacin por cada mquina.

Ejercicio 10: (7) Modifique la clase KnumMap de modo que un programa

VendingMachine

(nicamente)

utilizando

i 19 Tipos enumerados 1802 pueda disponer de mltiples instancias de VendingMachine

Ejercicio 11: (7) En una mquina expendedora real, conviene poder aadir y modificar fcilmente el tipo de elementos

expedidos, porque los limites impuestos a Input por una enumeracin resultan poco prcticos (recuerde que las enumeraciones son para un conjunto restringido de tipos). Modifique YendingMachine.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.mindvieu.util. TextFUe).

Proyecto:47

Disee la maquina expendedora utilizando funcionalidades de internacionalizacin. de tal modo que una mquina pueda fcilmente ser adoptada en todos los paises.

Despacho mltiple

? Los proyecios son sugerencias que pueden utilizarse, por ejemplo. como provelos de fin de curso. l_aa soluciones o lo proyectos no se incluyen en lu Gula de solavientes
47

i 19 Tipos enumerados 1803 Cuando estamos tratando con mltiples tipos que intcractan entre si. los programas pueden llegar a ser especialmente complejos. Por ejemplo, consideremos un sistema que analice sintcticamente y luego ejecute expresiones matemticas Nos interesarla poder decir iNumber.plus(Numbor). Niimber.multiply(Numher). etc., donde Niimber 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 imcracten apropiadamente?

La respuesta comienza con algo en lo que probablemente no haya pensado hasta 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 invocare! mecanismo de acoplamiento dinmico para uno de esos tipos. Eslo no resuelve el problema descrito aqui. por lo que terminamos detectando unos tipos manualmente e implementado, en la practica, nuestro propio comportamiento de acoplamiento dinmico.

La solucin se denomina despacho mltiple (en este caso, slo hay dos despachos, por lo que el mecanismo se denomina despacho doble.). El polimorfismo slo puede tener lugar desde llamadas a mtodos, por lo que si queremos realizar un despacho doble, deben existir dos llamadas a mtodos: la primera para determinar el primer tipo desconocido y la segunda para determinar el segundo tipo desconocido. Con el despacho mltiple, debemos tener una llamuda virtual para cada uno de los tipos: estamos trabajando con dos jerarquas 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 produzca una llamada a mtodo virtual, permitiendo determinar as ms de un tipo a lo largo del proceso. Para obtener este efecto, necesitamos trabajar con mas de un mtodo. Har falta una llamada a mtodo para cada operacin de despacho Los mtodos del siguiente ejemplo (que implemento el juego piedra, papel, tijera") se denominan compete{) y cval( ) y ambos son miembros de un mismo tipo. Estos mtodos producen uno de tres posibles resultados:' //: enumerated/Outcome.java package enumerated;

i 19 Tipos enumerados 1804 public enum Outcome ( WIN, LOSE, DRAW ) ///:- i t i enumerated/RoShamBol.java // Ilustracin del mecanismo de despacho mltiple. package enumerated; import java.Util.; import static enumerated.Outcome.*; interface Item f Outcome competedtem it); Outcome eval(Paper pj; Outcome eval(Scissors sj; Outcome eval(Rock r); j class Paper implements Item ( public Outcome competedtem it) { retum it.eval (this); ) public Outcome eval(Paper p) { return DRAW; } public Outcome evalScssors s) < retum WIN; } public Outcome eval(Rock r) ( return LOSE; } public String toString) { retum "Paoer1w; ) i class Scissors implements Itera ( public Outcome ( retum it.eval (this) public public Outcome Outcome eval(Paper p) ( competedtem ,) it)

return LOSE; )

eval(Scissors si { retum DRAW; )

public Outcome eval (Rock r) { retum WIN; } public String toString) { retum "Scissors; J

i 19 Tipos enumerados 1805 ) class Rock implements Item ( public Outcome { return it.eval (this) public public Outcome Outcome evalfPaper p) { competedtem ;) it)

return WIN; )

eval(Scissors s) ( retum LOSE; )

public Outcome eval (Rock r) ( retum DRAW; ) public strina toStrmgO f retum "Rock"; )

) public class RoShamBol { static final int SIZE 20y private static Random rand * new Random 147); public static Item newItemO ( switch (rand.nextInt (3)} ( default: case 0: return new Scissors O; case 1: return new Paper I) j case 2: return new Rock ();

i 19 Tipos enumerados 1806 )

) public static void march(Item a, Item b) { System.our.printIn ( a *- " ve. M b "s "a.compete(b))j

) public static void main(Stringtl arasl { for lint i *= 0; i < SIZE,- i-M-) match(newltem(', newltem! ) ) j

) ) / Output: Rock vs. Rock: DRAW Paper vs. Rock: WIN

i 19 Tipos enumerados 1807 Paper vs. Rock: WTN Paper vs. Rock* WIN Scissors vs. Paper: WIN Scissors vs. Scissors: DRAW Scissors vs. Paper: WIN Rock vs. Paper: LOSE Paper vs. Paper: DRAW Rock vs. Paper*. LOSE Paper vs. Scissors: LOSE Paper vs. Scissors: LOSE Rock vs. Scissors: WIN Rock vs. Paper: LOSE

Item es lu interfaz para los tipos con los que se va a realizar el despacho mltiple. KoShamBol.matchf ) toma dos objetos Item e inicia el proceso de doble despacho llamando a la funcin llem.compe!e( ). El mecanismo virtual determina el tipo de a, por lo que se activa dentro de la funcin competet ) correspondiente al tipo concreto de a La funcin compete ) realiza el segundo despacho llamando a eva!() con el tipo restante. Pasndose a si mismo t this) como un argumento a cval() se genera una llamada a la funcin t*val() sobrecargada, preservndose as la informacin de tipo correspondiente al primer despacho. Al completarse el segundo despacho, conocemos el tipo exacto de ambos objetos Item

i 19 Tipos enumerados 1808 Preparar el mecanismo de despacho mltiple requiere de un montn de ceremonia, pero recuerde que la ventaja que se obtiene es la elegancia sintctica que se consigue al realizar la llamada: en lugar de escribir un cdigo muy complicado para determinar el tipo de uno o ms objetos durante una llamada, simplemente decimos: Vosotros dos. no me importa de qu tipo sois, pero interactuar apropiadamente entre vosotros!. Sin embargo, asegrese de que este tipo de elegancia sea importante para usted antes de embarcarse en programas que impliquen un despacho mltiple Cmo despachar con enumeraciones

Realizar una traduccin directa de KoMiamBol.java a una solucin basada en enumeraciones resulta problemtico, porque las instancias enum no son tipos, as que los mtodos cval( ) sobrecargados no funcionan: no se pueden utilizar instancias enum como argumentos de tipo. Sin embargo, existen distintas tcnicas para mplcmcntar tcnicas de despacho mltiple que permiten sacar provecho de las enumeraciones.

Una de sus tcnicas utiliza un constructor para inicializar una instancia enum con una Tila" de resultados; contemplado en su conjunto esto produce una especie de tabla de bsqueda: //: ermmerated/RoShamBo2.j ava // Conmutacin de una enumeracin basndose en ctrapackage enumerated; import static enumerated.Outcome. t public enum RoShamBo2 implements Ccmpetitar<-Ko5hamaa2> { PAPER{DRAW, LOSE, WIN), SCISSORSWIN, DRAW, LOSE), RCCK(LOSE, WIN, DRAW);

i 19 Tipos enumerados 1809 private Outcome vPAPER, vSCISSORS, vROCK; RoShamBo2(Outcome paper,Outcome scissors,Outcome rock) ( this.vPAPER = paper; this. vSCISSORS = scissors this.vROCK rock;

} public Outcome compete(RoShamBc2 it} { switch(it) { default: case PAPER: return vPAPER; case SCISSORS; return vSCISSORS; case ROCK: return vROCK;

public static void mam (String [1 args) ( RoShamBo.playlRoSharaBo2.class, 20);

i 19 Tipos enumerados 1810 } /* Output: ROCK vs. ROCK: 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 vs. SCISSORS : WIN SCISSORS vs. SCISSORS : DRAW ROCK VS. SCISSORS : WIN SCISSORS vs. PAPER: WIN SCISSORS VS. PAPER: WIN ROCK VB. FAPEK: LOSE ROCK vs. SCISSORS : WIN SCISSORS

i 19 Tipos enumerados 1811 vs. ROCK: LOSE PAPER vs. SCISSORS : LOSE SCISSORS vs. PAPER: WIN SCISSORS vs. PAPER: WIN SCISSORS VS. PAFER.* WIN SCISSORS vs. PAPER: WIN ///*-

Una ve/, que sc han determinado ambos tipos en compete( ). la nica accin consiste en devolver el resultado con Outcome Sin embargo, tambin podramos invocar otro mtodo, incluso (por ejemplo, n travs de un objeto Comando que hubiera sido asignado en cl constructor.

KoSbamBo2.java es ms pequeo y ms sencillo 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 RoShamBol.java, ambos despachos se realizaban mediante llamadas virtuales a mtodos pero aqui, slo se utiliza una llamada virtual a mtodo en el primer despacho. El segundo despacho emplea una instruccin switch, pero esta solucin es perfectamente vlida porque la enumeracin

i 19 Tipos enumerados 1812 limita las opciones disponibles en la instruccin switch.

El codigo relativo a la enumeracin lia sido separado para que pueda utilizarse en otros ejemplos. En primer lugar, la inter- faz Competitor define un upo que compite con otro Competitor: //: enumerated/Competitor.java // Conmutacin de una enumeracin basndose en otra, package enumerated; public Interface Competitor<T extends Competitor<T { Outcome compete(T competitor);

) ///:-

A continuacin, definimos dos mtodos estticos (se hacen estticos para evitar tener que especificar el tipo del parmetro explcitamente). En primer lugar, match( ) invoca a compete( ) para uno de los objetos Competitor comparndolo con otro, y podemos \er que en este caso el parmetro de tipo slo necesita ser Competitor<T>. Pero en play(). el parmetro de tipo tiene que ser tanto Emim<T> porque se lo utiliza en Knums.random( ) como Competitor<T> porque se le pasa a match( ) I I : enumerated/RoShamBo.java // Herramientas comunes para los ejemplos RoShamBo.

i 19 Tipos enumerados 1813 package enumerat ed; import net.mindview.util.; public class RoShamBo { public static <T extends Compettor<T>> void match(T a, T b) { Syste m .out.println( a + * V8, * t b t "i 11 + a.competetb));

} public static <T extends Enum<T> & Competitor<T void play(Class<T> rsbClass, int size) ( forint i Oj i c si2 e; i++.i match! Enums.random( rsbClass),Enums.random(rsbClass>);

) ///:-

i 19 Tipos enumerados 1814 El mtodo play ) no tiene un valor de retomo que implique el parmetro de tipo T. por lo que parece que podramos utilizar comodines dentro del tipo C!a*s<T> 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 permiten proporcionar diferentes implementaciones de mtodo para cada instancia enum. 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 instruccin switch I I : enumerated/RoShamBo3.java U Utilizacin de mtodos especficos de constante.

package enumerated; import static enumerated.Outcome.*; public enum RoSnamBo3 implements Competitor<RoShamBo3> ( PAPER ( public Outcome compete(RoShamEoS it) { switch(ifc) ( default: // Para aplacar ai compilador case PAFER: return DRAW; case SCISSORS: return LOSE; case ROCK: return WIN/

i 19 Tipos enumerados 1815 ). SCISSORS ( public Outcome compete(RoShamBo3 it) ( switch(it) ( default: ca3e PAPER: return. WIN; case SCISSORS; return DRAW; case ROCK: return LOSE;

) h ROCK ( public Outcome compete(RoShamBo3 it) ( switch(it) ( default; case PAPER: return LOSE; case SCISSORS: return

i 19 Tipos enumerados 1816 WIN; case ROCK: return DRAW;

); public abstract Outcome compete(RoSharaBo3 it); public static void main(StringI] args) ( RoShamBo.play(RoShamBo3.class, 20);

i 19 Tipos enumerados 1817 } / Misma salida que RoShamBo2.java ///:-

Aunque este programa funciona y no resulta irrazonable, la solucin de RoShainBo2Java parece requerir menos cdigo a la hora de aadir un nuevo tipo, por lo que resulta ms sencilla.

Sin embargo, RoShamBu3.java puede simplificarse v comprimirse: //: enumerated/RoShamBo4.java package enumerated; public enum RoShamBo4 implements Competitor<RoShamBo4> ( ROCK { public Outcome compete<RoShamBo4 opponent) ( return compete(SCISSORS, opponent);

) h SCISSORS { public Outcome compete(RoShamBo4

i 19 Tipos enumerados 1818 opponent) ( return compete(PAPER, opponent);

} }. PAPER { public Outcome compete(RoShamBo4 opponent) ( return compete(ROCK, opoonent);

) Outcome compete(RoShamBo4 loser, RoShamBo4 opponent) ( return ((opponent == this) ? Outcome.DRAW : ((opponent == loser) ? Outcome.WIN ! Outcome.LOSE));

i 19 Tipos enumerados 1819 ) public static void main(Stringf] args) { RoShamBo.play(RoShamBo4.class, 20)/

) ) /* Misma salida que RoSham3o2.java *///:-

Aqu, el segundo despacho es realizado por la versin de dos argumentos de compete(), que realiza una secuencia de comparaciones y es. por tamo, 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: //: er.umerated/RoShamBo5. java // Despacho mltiple usando un mapa EnumMap e mapas EnumMaps. package enumerated;

i 19 Tipos enumerados 1820 import java.til.*; import static enumerated.Outcome.*; enum RoShamBo5 implements Competitor<RoShamBo5> { PAPER, SCISSORS, ROCK; static EnumMap<RoShamBo5,EnumMap<RoShamBo5,Outcome>> Labie = new EnumMap<RoShamBo5, EnumMap<RoShamBo5,Ouccome>>(RoShamBo5.class); static { forRoSham3o5 it : RoShamBo5.vales()) table.put(it, new EnumMap<RoShamBo5,Outcome>(Ro ShamBo5.class)); initRow(PAPER, DRAW, LOSE, WIN) ; initRow(SCISSORS, WIN, DRAW, LOSE); nitRow(ROCK, LOSE, WIN, DRAW);

static void init-Row (RoSham3o5 it, Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK) { EnumMap<RoSharaBo5,Outcome> row = RoShamBo5.table.get(it); row.put(RoShamBoS.PAPER, vPAPER); row.put(RoShamBoS.SCISSORS, vSCISSORS); row.put(RoShamBo5.ROCK, vROCK);

public Outcome compete(RoShamBoS it) { return

i 19 Tipos enumerados 1821 table.get(this).get(it) ;

} public static void main(String[] args) ( RoShamBo.play(RoShamBoS.class, 20);

} } /* Misma salida que RoShamBo2.java *///:-

El mapa EnumMap se inicializa mediante una clusula static; podemos ver la estructura con forma de tabla ce llamadas a initRo\v(). Observe el mtodo compete( ), donde puede verse que ambos despachos tienen lugar en una nica instruccin. Utilizacin de una matriz 2-D

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

i 19 Tipos enumerados 1822 resultados, permite obtener la solucin ms pequea y directo (y tambin posiblemente la ms rpida aunque recuerde que EnumMap utiliza una matriz interna): //: enumerated/RoShamBo6.j ava // Enumeraciones utilizando "tablas" en lugar de despacho mltiple. package enumerated; itnpcrt static enumerated.Oucccme.; enum RoShamBo$ implements Ccmpetitor<RaSh.am3o6> ( PAPER, SCISSORS, ROCK; prvate ataticOutcomeU [] table { (DRAW, LOSE. WIN}, // PAPER {WIN, DRAW,LOSE}, // SCISSORS ( LOSE. WIN.DRAW). // ROCK

); public Outcome compete(RcShamBo6 other) ( i retum table(this.ordinal O l lother.ordinal O J ;

public static vod main(StringL) Args) ( RoShamBo.playiRaShamBo6.clase, 20) ;

i 19 Tipos enumerados 1823 ] ///:-

La labia table tiene exactamente el mismo orden que las llamadas a initRo\v( ) en el ejemplo anterior.

El tamao pequeo de este cdigo resulta muy atractivo si lo comparamos con los ejemplos anteriores, en pane porque parece 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 una 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 forma 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 mas compacta, tambin resulta bastante rgida, porque slo permite producir una salida constante a partir de unas entradas constantes. Sin embargo, no hay nada que nos impida tener una tabla que genera un objeto de funcin. Para ciertos tipos de problemas. el concepto de cdigo conducido por tablas puede ser muy potente. Resumen

i 19 Tipos enumerados 1824 An cuando los tipos enumerados no son terriblemente complejos por si mismos, hemos pospuesto este capitulo hasta este momento debido a que queramos analizar lo que se puede hacer con las enumeraciones al combinarlas con caractersticas tales como el polimorfismo, los genricos y el mecanismo de reflexin.

Aunque son significativamente ms sofisticadas que las enumeraciones de C o C+4-, las enumeraciones Java siguen siendo una caracterstica menor, algo sin lo que el lenguaje lia sobrevivido (aunque a costa de una gran complejidad) durante muchos aos. A pesar de ello, este capitulo 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 vf como hemos dicho en diversas ocasiones a lo largo del libro, la elegancia es importante y la claridad puede marcar la diferencia entre una solucin adecuada y otra que fracasa porque las dems personus son incapaces de entenderla.

Hablando de claridad, una fuente muy lamentable de confusin pro\ iene de la mala decisin que se tomo en Java l .0. consistente en emplear el trmino enumeration en lugar del termino 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 Knumcrution no pudo, por supuesto, eliminarse de manera directa, por lo que sigue estando presente en el cdigo antiguo (y a veces en el cdigo nuevo!), en la biblioteca y en la documentacin. Puede encontrar las solucione* a lo* cjcicieio* elecciumuiu* cu el documento clcetrnku Thv Thinkittg biJnui AWMittdSalutU'n CiulJi. disponible para l:t venta en ntw MinJUfw IW

.Anotaciones

20

Las anotaciones (tambin conocidas como meta Jatos) proporcionan una manera formalizada de aadir informacin a nuestro cdigo que nos permita utilizar fcilmente dichos datos en algn momento posterior. 48

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.

Jcremy Meyer tuvo la gentileza de acudir a Crestad tutte y pasar all tos semanas trabajando conmigo en este captulo. Su ayuda ha *idti extremadamente valiosa.
48

- Esto esui. sin ninguna duda, inspirado en otra caracterstica similar disponible en Ctf. La caracterstica de CP e* una palabra clave y no uiui anotacin, y esta impuesta por el compilador Un ornas palabras, si >e sobrecscribc un mtodo en Ctt, es necesario utilizar Ui palabra clave override, mientra* que en Java la anotacin ()> erride es opcional.

Las anotaciones son uno de los cambios fundamentales del lenguaje introducidos en Java SE5. Proporcionan informacin que hace falta para describir completamente el programa, pero que no puede expresarse en Java. De este modo, las anotaciones nos permiten almacenar informacin adicional en un formato que es probado v 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 mas limpio, un mecanismo de comprobacin de tipos de compilacin y la API anotaciones como ayuda para construir herramientas de procesamiento de las anotaciones. Aunque hay unos cuantos tipos de metadatos predefinidos en Java SE5. en general, el tipo de anotaciones que se aadan y lo que hagamos con ellas son responsabilidad completamente nuestra.

La sintaxis de las anotaciones es razonablemente simple y consiste principalmente en la adicin del smbolo (a al lenguaje. Java SE5 contiene tres anotaciones predefinidas de propsito general, que estn definidas en javaJang;

(a'Override, para indicar que la definicin de un mtodo pretende sustituir otro mtodo de la clase base. Esta anotacin genera un error de compilacin si se escribe mal accidentalmente el nombre del mtodo o si se proporciona una signatura incorrecta.

(a Deprecated, para que se genere una advertencia del compilador si se utiliza este elemento.

(a Suppress Warnings, para desactivar las advertencias de compilador inapropiadas. Esta anotacin est permitida, 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 tarca repetitiva, normalmente utilizaremos anotaciones para automatizar y simplificar el proceso. Buena pane del trabajo adicional en Enterprise JavaBeans (EJB), por ejemplo, se elimina mediante el uso de anotaciones en LJB3.0.

Las anotaciones permiten sustituir sistemas existentes como XDoclet. que es una herramienta independiente para doclet (consulte el suplemento contenido en Http://\findVe*\nel/Bo<)ks/BeUerJav) diseado especficamente para crear docets

con estilo de anotacin. Por contraste, las anotaciones son verdaderas estructuras del lenguaje y estn, por tanto, estructuradas. comprobndose sus tipos en tiempo de compilacin. Al mantener toda la informacin en el propio cdigo flente y no en comentarios. eJ cdigo resulta ms limpio y fcil de mantener. Utilizando y ampliando la API y las herramientas de anotaciones. o empleando bibliotecas externas de manipulacin del cdigo intermedio como veremos en este capitulo, podemos realizar potentes tareas de inspeccin y manipulacin del cdigo fuente y del cdigo intermedio Sintaxis bsica

[In el ejemplo siguiente, el mtodo testE\ecute() est anotado con te Test. Esto no hace nada por si mismo pero el compilador comprobar que existe una definicin de la anotacin 6 Test en la mta de construccin del programa. Como veremos posteriormente en el captulo, podemos crear una herramienta que ejecute este mtodo por nosotros a travs del mecanismo de reflexin. //: annotatione/Testable.java packaae annotaCions; import net.mindview.atunit.; public elaas Testable ( public vod executeO ( System.out .printlnI "Executlng. ;

) S Te s t vod testExecute() ( executel); } 1 ///:-

Los mtodos anotados no difieren de los dems mtodos. La anotacin (fr Test de este ejemplo puede utilizarse en combinacin con cualquiera de los modificadores como public. static o void Sintcticamente, las anotaciones se utilizan de forma 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 //: net/mindview/atunit/Test.java / / Ei marcador Test, package net.mindview.atunit import java.lang.annotation.r Target(EiementTvpe.METHQDi sftetent ion IKetent ionPolicy.RUNTIME) public Interface Test () ///:-

Aparte del smbolo a la definicin de (Test se parece bastante a la de una interfaz vaca. La definicin de una anotacin tambin requiere las meta-anotaciones ti Target y Ca Retention. fgTarget define dnde se puede aplicar esta anotacin (un mtodo o un campo) (a Retention define si las anotaciones estarn disponibles en el cdigo fuente (SOURCE), en los archivos de clase (CLASS). o en tiempo de ejecucin (RUNTIME).

Las anotaciones contendrn usualmente elementos para especificar valores para las anotaciones. Un programa o una herramienta 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 predeterminados.

Una anotacin sin ningn elemento, como la anotacin (o 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 conjunto 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 proyecto pueden encontrar fcilmente los casos de uso si necesitan actualizar o depurar las reglas de negocio utilizadas en el sistema. )/: annotations/UseCase.java import java, lang.annotation. ,* vTarget lElementType .method) Retention. {RetenE.ioR.PcIi.cy. RUNTIME * pubiic 'interface UseCase { public int i d ( ) ; public Strina descripcin() default "na description";

} ///:Observe que id y description se asemejan a declaraciones de mtodos. Puesto que el tipo de id es comprobado por el compilador, se traa 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 predeterminado 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 //: annotatiGns/PasswordUtils.java import j ava.utll.: public class Passwordtiis { -sUseCaaetid = 47, description =* "Passwords must contain at least ene numeric") public booiean validatePasswordIString password) ( retum (password.matches ( "Ww'WdWw*")) ;

) iUseCase(id = 48) public Strmg encryptPasnwordIString password) { retum new StrxngBuilder (password) .reverseO . toStringO

) UseCase(id = 49, description New passworda can't equai previouely ueed ones) public baolean checkForNewPassword( List<Slring> prevpssswords, String password) ( retum prevFasswords . con taina (password

) ///?Los valores de los elementos de anotacin se expresan como pares nombre-valor encerrados entre parntesis despus de la declaracin a t seCase A la anotacin para cncrvptPassord( ) no se le pasa un valor en el ejemplo para el elemento dev eription. por lo que cuando se procese la clase con un procesador de anotaciones aparecer el valor predeterminado definido en (a interfaee LseCase.

Podemos imaginamos fcilmente cmo podra emplearse un sistema como ste para esbozar un programa y luego rellenar la funcionalidad a medida que vamos completando el diseo. Meta-anotaciones Actualmente slo hay tres anotaciones estndar (descritas anteriormente) y cuatro meta-anotaciones definidas en el lenguaje Java. Las mcta-anotaciones se utilizan para anotar anotaciones:

La mayor pane del tiempo lo que haremos es definir nuestras propias anotaciones y escribir nuestros procesadores para tratar 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 proceso de uso de las anotaciones consiste en crear y utilizar procesadores de anotaciones Java SF.5 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.

Me aqu un procesador de anotaciones muy simple que lee la clase anotada Password Utils v emplea el mecanismo de reflexin para buscar marcadores @ UseCase. Dada una lista de valores id, enumera los casos de uso y localiza aquellos que falten. informando de esa ausencia: //: annotations/UseCaseTracker.java import java.lang.reflect.; import java.util; public class UseCaseTracker { public static void trackUseCases(List<Integer> useCases, Class<?> cl) ( for(Method m : cl.getDeclaredMethcas{)) ( UseCase uc = m.getAnnotation(UseCase.class! if (uc t = null) { System.out.println("Found Use Case:" - uc.idr " 2u uc.description0)/ useCases.remove(new Integerluc.id()I);

forint i : useCases) { System.out .println(Warning: Missing use case-" i) : ii public static void main(String I] argsl { List<Integer* useCases = new ArrayList<lnteger>(); Collections.addAll(useCases, 47, 48, 49, 50); tracksecases{useCases, PasswordUtils.class) ;

) ) / 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 case50 *///:-

Este ejemplo utiliza tanto el mtodo de reflexin gerl)eclaredMethods( ) como el mtodo getAnnotation( ), que proviene de la interfaz AnnotatedElemcnt (clases como Class. Method y Field implemenian esla interfaz). Este mtodo devuelve el objeto anotacion 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 nuil Los valores de los elementos se extraen invocando id( ) 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 dcscription( ) para esa anotacin concreta. Elementos de anotacin

El marcador a UseCase definido en UseCase.java contiene el elemento id de tipo int y el elemento description de tipo String. He aqui una lista de los tipos permitidos para los elementos de anotacin:

Todas las primitivas (int. float, hoolean etc.) String 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 permitido utilizar ninguna de las clases envoltorio de los tipos primitivos, pero gracias a la caracterstica de conversin automtica, esto no es una verdadera limitacin. Tambin podemos tener elementos que sean ellos mismos anotaciones. Como veremos un poco mis adelante, 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 valares proporcionados por la clase que utilice la anotacin.

Existe otra restriccin y es que ninguno de los elementos de tipo no primitivo pueden tener nuil como valor, ni a la hora de declararlos en el cdigo fuente ni cuando se los defina como valor predeterminado dentro de la interfaz de anotacin. Esto hacc que resulte difcil 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 vacias

o valores negativos

//: annotationa/Simulatir.gNuIl. j ava import java.lang.annotation.; Target(ElementType.METHOD)

Retent ion iRetent ionPolicy.RUNTIME) public nterface SiraulatingNuil ( public int id O dsfaulc -1; oublic Scrinq description(1 default H/ ) Ifh-

Esta estructura sintctica 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 informacin adicional como acompaamiento del cdigo fuente. Tecnologas como Enterprise JavaBeans (en las versiones anteriores a EJB3) requieren numerosas interfaces y descriptores de implantacin que formun una especie de 'plantilla" de cdigo, definida de la misma forma para cada componente bean Los servicios web, las bibliotecas de marcadores personalizados y las herramientas de mapeo objeto relacional como Toplink y Hib*mate a menudo requieren descriptores XML que son externos al cdigo. Despus de definir una clase Java, el programador debe llevar a cabo la tediosa tarea de volver a especificar informaciones tales como el nombre, el paquete, etc., es decir, informaciones que ya existen en la clase original. Cada ve/ que utilizamos un archivo descriptor externo, terminamos con dos fuentes de informacin separadas de una clase, lo que normalmente 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 !a creacin de una tabla de base de datos con el fin de almacenar un componente Javatean. Podramos utilizar un archivo descriptor XML para especificar el nombre de la clase, cada de sus miembros y la informacin acerca de su mapeo sobre la base de datos. Sin embargo, utilizando anotaciones, podemos mantener toda la informacin en el archivo fuente del componente JavaBean. Para hacer esto, necesitamos anotaciones para definir el nombre de la tabla de base de datos asociada con el componente bean. sus columnas y los tipos SQL que hay que hacer corresponder con los

propiedades del componente bean.

He aqui una anotacin para un componente bean que le dice al procesador de anotaciones que tiene que crear una tabla de base de datos:

//: annotations/database/DBTabie.java package annotations.database; iraport java.lang.annotacin.;

iTargec E-lementType. TYPE1 // Slo se aplica a clases Retent i on/Re t enti onPoli ey.RUNTIME i public *interface DBTable ( pufclic String ame O deiiault I ///:-

Cada tipo de elemento KlementType que especifiquemos en la anotacin fa Target es una restriccin que le dice al compilador que nuestra anotacin slo se puede aplicar a esc tipo concreto. Podemos especificar un slo valor de la enumeracin enum ElementType, o bien podemos especificar una lista formada por cualquier combinacin de valores separados por comas Si queremos aplicar la anotacin a cualquier tipo de elemento ElementType, podemos omitir la anotacin a Target, aunque esta solucin es bastante poco comn.

Observe que (a DRTuhle 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

//: annotationa/datahase/Constraints, java package annotations.darabase; import java.lang.annocarion./

Target, fElementType. FTBLD)

* Retent

i onRetentlonPolicy.RUNTIME1 ginterface Conatraints (

public

boolean primaryKey() default falae; boolean allowNullO default truej boclean umque 1 default Calse;

) ///:-*

//: annotations/database/SOLString,java package annotations.database; import ^ava-lang.annotatior,. * ;

VTarget \ElementType. PIELD /i {RetentionPollcy. RURTIME) public SQLScring ( int valu O default 0;

Retention interface

String namefj default ""j

Conat.rainr9 conatra.nts 0 default Constraints;

) /// //: annotatior.s/database/SQLInteger.ja va package annc t atlona.dat abase;

Lmport java.lang.annotation.; TargetlElementType.FIELD) Retention(RetentionPclicy.RUNTIMEJ public -finterface SQLInteger { String name() default Constralnts constraints{) default Constraints;

\ ///:-

La anulacin (a Constraints permite a] procesador extraer los metadatos relativos a ta tabla de la base de datos Esto representa un pequeo subconjunlo de las restricciones que generalmente ofrecen las bases de datos, pero nos permite hacemos una idea general. Los elementos priman Key( ). allowNull( ) y unique( ) tienen asignados valores predeterminados adecuados. de modo que en la mayora de los casos un usuario de la anotacin no tendr que escribir demasiado texto.

Las otras dos anotaciones (a interface definen tipos SQL. De nuevo, para que este sistema sea ms til, necesitamos definir una anotacin para cada tipo SQL adicional. En nuestro ejemplo dos tipos sern suficientes.

Cada uno de estos tipos tiene un elemento itame( ) 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 upo de columna. Observe que el valor predeterminado para el elemento

cuntraintsf ) es rg.Constraints Puesto que no hay valores de elementos especificados entre parntesis despus de este tipo de anotacin, el valor predeterminado de constralnts! ) es en la prctica una anotacin (ja Constralnts con su propio conjunto de valores predeterminado Para definir una anotacin ftConstralnrs anidada donde la caracterstica de unicidad est definida como irue de manera predeterminada, podemos definir su elemento de la forma siguiente: //: annotations/database/Uniqueness.java / / Ejemplo de anotaciones anidadas package annotations.database; public finterface Uniquenese { Constralnts cansrraintaO default ^Constralnts(uniaue=true);

He aqu un componente bean simple que utiliza estas anotaciones: //: annot at ions/dat abase/Member.j ava package annotations.database; DBTab1e i ame * "MEMBER"I public class Member ( SQLStringf301 SLrlng firstName; SQLString(50) String lastName; SQLInteger inteaer age; wSQLString i valu = 30, constraints = Constraints(primaryKey = trueM String handle; static int memberCounr; public String get-Handle() ( return handle; ) public String aetFirstName) ( return firstName; ) public String getLastName() ( return lastName,* } public String toStringO ( return handle; } public Integer getAgeO

{ return age; )

} ///.-

A la anotacin de clase (a 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, respectivamente. Estas anotaciones son interesantes por dos razones: en primer lugar, utilizan el valor predeterminado en la anotacin (2 Constraints anidada, y en segundo lugar emplean una caracterstica especial de abreviatura. Si definimos un elemento de una anotacin con el nombre valu, entonces no ser necesario utilizar la sintaxis basada en parejas de nombre-valor siempre y cuando sea el nico tipo de elememo especificado; podemos limitamos a especificar el valor entre parntesis. Esto puede aplicarse a cualquiera de los tipos de elementos legales. Por supuesto, con esto estamos obligados a llamar a nuestro elemento valu", pero en el caso anterior, nos permite emplear una especificacin de anotacin semnticamente significativa y muy fcil de leer: aSQLStringi30)

El procesador utiliza este valor para establecer el tamao de la columna SQL que cree.

Aunque la sintaxis relativa a los valores predeterminados es bastante limpia, puede volverse muy rpidamente muy compleja. Observe la anotacin correspondiente al campo handle. Tiene una anotacin fa SQLString. pero tambin necesita ser una clave principal de la base de datos, por lo que es necesario activar el tipo de elemento en primaryKcy en la anotacin (5 Constraint anidada. Aqui es donde el ejemplo comienza a ser confuso. Ahora estamos forzados a utilizar la forma, bastante larga, basada en una pareja de nombre-valor para esta anotacin anidada, volviendo a especificar el nombre de elemento y el nombre de la interfaz a interfaee. Pero, como el elemento de nombre especial valu ya

no es el nico valor de elemento que se est especificando, no podemos emplear la forma abreviada. Como puede ver. el resultado no es precisamente elegante. Soluciones alternativas

Existen otras formas de crear anotaciones para llevar a cabo esta tarea. Podramos, por ejemplo, tener una nica clase de anotacin denominada $ TableC'olumn con un elemento enum que definiera valores como STRING, INTEGER, FLOAT. etc. Esto elimina la necesidad de definir una anotacin (a interfaee para cada tipo SQL. pero hace que sea imposible cualificar los tipos con elementos adicionales como size (tamao) o precisin (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- CHAROO) o 'INTEGER Esto nos permite cualificar los tipos, pero nos fuerza a fijar en el cdigo la correspondencia entre el tipo .lava y el tipo SQL, lo cual no es una buena prctica de diseo. No conviene tener que rccompilar las clases si cambiamos las bases de datos, seria ms elegante limitamos a decirle a nuestro procesador de anotaciones que estamos usando 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: (a Constraints y el tipo SQL relevante (por ejemplo, (a SQLInteger), para anotar el campo deseado. Esto resulta ligeramente complicado, pero el compilador nos permite utilizar tantas anotaciones diferentes como queramos sobre un mismo objetivo de anotacin. Observe que. al utilizar mltiples anotaciones, no podemos emplear la misma anotacin doj veces. Las anotaciones no soportan la herencia

No podemos utilizar la palabra clave extends con (a interfaces. Es una pena, porque una solucin elegante seria definir una anotacin (a TableColumn, como hemos sugerido anteriormente, con una anotacin anidada de tipo (a SQLType. De esa forma, podramos heredar todos nuestros tipos SQL, como di SQLInteger y (u SQLString de fin 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 anotaciones soporten el mecanismo de herencia en versiones futuras del lenguaje, por lo que los ejemplos anteriores parecen 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 // Procesador de anotaciones basado en el mecanismo de reflexin. // {Args: annotations.database.Member) package annotations.database; import java.lang.annotation.*; import java.iang.reflect; import java.util.* ; public class TableCreator ( public static void main(String[] args) throws Exception ( if{args.length < 1) { System.out.princln<"arguments : annotated classes"); System.exit(0) ;

) for(String className : args) { Class<?> cl * Class.forName(className); DSTable dbTable = cl.getAnnotation(DBTable.class); if(dbTable null) { System.out .pnntlnl

MNo DBTable annotations in class " + className) ; continue;

) String tableName = dbTable.name I); // Si el nombre est vacio, utilizar el nombre de la clase: if ItableName.length() < 1) tableName cl.getName().topperCase( ) t List<String> columnDefs = new ArrayList<String>l); forField field : cl.getDeclaredFields()) { String columnName = null; Annotation!] anns = field.aetDeclaredAnnotationsU; if(annG.length < 1) continue; // No es una colurana de la tabla de base de datoa if(anns[0] mstanceof SQLInteger) { SQLInteger slnt = (SQLInteger) anns[01 ; // Otilizar el nombre de campo si nombre, if(slnt.namei).length() < 1) no se especifica un

columnName = field.getName().topperCasef); else columnName = slnt.name(); columnDefs.add(columnName + " INT" + getConstraints\ slnt.constraints{)));

) if (anns (0] mstanceof SQLString) { SOLString sString = (SQLString) anns[Q]; // Otilizar el nombre de campo si no nombre, if IsString.name 0.length () < 1) se especifica un

columnName = field.getName().toUpperCaset); else columnName = sString.name(); columnDefs. add (columnName" VARCHAR ( " +-

sString.valuel) * ) M + getConstraints(sString.constraints()));

StringBuilder createCommand - new StringBuilder( "CREATE TABLE tableName tcreateCommand. append ( "\n " // Eliminar coma final String tabieCreate = createCommand.substring{ 0, createCommand.length(J - 1) + M);M; System.out .println I ''Table Creation SQL for " className t " is ;\nM + tabieCreate); ; columnDef -t ",H); for(String columnDef : columnDefs)

20 Anotaciones 1847 )private ecatic String geeConstraintsiConstraints con} ( String constraints


if
49

";

{Icon.allowNulK) ) constraints += *' NOT NULL;

If(con.primaryKey()) constraints = MM PRIMARY KEY"; if( con.unique() } constraints +* * UNIQUE; return constraints;

} ) / Output: Table Creation SQL for annotations.database.Member is : CREATE TABLE MEMBER< FIRSTNAME VARCHAR(30)); Table Creation SQL for annotations.database.Member is : CREATE TABLE MEMBER( FIRSTNAME VARCHAR (30>, LAS TN AME VARCHAR(50)); Table Creation SQL for annotations.database.Member is ; CREATE TABLE MEMBER ( FIRSTNAME VARCHAR30). LAS TNAME VARCHARI50), l.o* proveci* son nugerencijw que pueden utilizante, por ejemplo, como ptoyeetm ik fin de cun. Las solucione a lo* proyecto! no c incluyen en ta Guia de soluciona.
49

1848 Piensa en Java AGE INT); Table Creation SQL for annotations.database.Member is : CREATE TABLE MEMBER( FIRSTNAME VARCHAR(301 , LASTNAME VARCHAR(50) . AGE INT. HANDLE VARCHAR(30J PRIMARY KEY) ;

*///:-

El mtodo main( ) recorre sucesivamente cada uno de los nombres de clase en la linea de comandos. Cada clase se carga utilizando for.Name() y se la comprueba para ver si incluye la anotacin Ca DTablc utilizando getAnnutation(DB lulik* .class). Si se incluye esa anotacin, se localiza el nombre de la labia y se almacena, entonces se cargan y se comprueban todos los campos de la clase con gctDeclaredAnnotationsi ). Este mtodo devuelve una matriz con todas las anotaciones definidas para un mtodo concreto. Se utili/a el operador instanceof para determinar que estas anotaciones son de tipo (a SQL Integer y (a SQL.String. y en cada caso se crea entonces el fragmento de cadena de caracteres relevante, con el nombre de la columna de la tabla. Observe que como no existe posibilidad de herencia en las interfaces de anotacin, la utilizacin de get Declared Annotation*!) es la nica forma con la que podemos aproximarnos al comportamiento polimrfico.

20 Anotaciones 1849 La anotacin ui C onstraint anidada se pasa al mtodo gctConstraints( ). que construye un objeto de tipo String que contiene las restricciones SQL.

Merece la pena mencionar que la tcnica mostrada anteriormente es una forma un tanto ingenua de definir un mapeo objeto-rclacional Disponer de una anotacin de tipo @DBTable, que toma el nombre de la tabla como parmetro, nos obliga a recompilar el cdigo Java cada vez que queramos cambiar el nombre de la tabla, lo que puede resultar no muy conveniente Hay disponibles muchos sistemas para mapear objetos sobre bases de datos relacinales, y cada vez un nmero mayor de ellos est haciendo uso de las anotaciones.

Ejercicio 1:

(2) Implementc ms tipos SQL en el ejemplo de la base de datos.

Proyecto: Modifique el ejemplo de la base de datos para que se conecte con una base de datos real c intcractc con

ella utilizando JDBC.

1850 Piensa en Java Proyecto: Modifique elejemplo de labase compatibles con XML en lugar de escri de datos para que cree archivos

bir cdigo SQL. Utilizacin de apt para procesar anotaciones

La herramienta de procesamiento de anotaciones api es la primera versin de Sun de este tipo de herramienta Puesto que se trata de una versin relativamente joven, la herramienta sigue siendo un poco primitiva, pero dispone de una serie de caractersticas que pueden facilitamos lu tarea.

Como javac. api est diseada para ejecutarse con archivos fuente Java en lugar de con clases compiladas. De manera predeterminada. api compila los archivos fuente una ve/ que ha terminado de procesarlos. Esto es til s estamos creando automticamente nuevos archivos fuente como parte del proceso de construccin de la aplicacin. De hecho, apt comprueba si existen anotaciones en los archivos fuente recin creados y los compila, todo ello en una pasada.

20 Anotaciones 1851 Cuando el procesador de anotaciones crea un nuevo archivo iente, dicho archivo es comprobado a su ve/ en busca de anotaciones. en lo que constituye una nueva ronda (como se denomina en la documentacin) de procesamiento. U herramienta continuar efectuando ronda tras ronda de procesamiento hasta que no se cree ningn archivo fuente. Entonces, compilar todos los archivos fuente existentes.

Cada anotacin que escribamos necesitar su propio procesador, pero la herramienta apt permite agrupar fcilmente varios procesadores de anotacin La herramienta nos permite especificar mltiples clases que haya que procesar, lo cual resulta ms fcil que tener que iterar manualmente a travs de una sene de clases File. Tambin podemos agregar lo que se denominan procesos escucha para recibir una notificacin cuando se complete una ronda de procesamiento de anotaciones.

En el momento de escribir estas lincas, apt no est disponible como tarea Ant (consulte el suplemento en http:MindVew net/Books/BettcrJava), pero mientras tanto se puede, obv iamente, ejecutar la herramienta como tarea externa desde Ant. Para compilar los procesadores de anotaciones descritos en esta seccin, es necesario tener tool&.jar en la ruta de clases; esta biblioteca tambin contiene las interfaces com.sun.mirror.*

apt funciona utilizando una factora de procesadores de anotaciones ( Annotat ionProcessorFactory) para crear el tipo apropiado de procesador para cada anotacin que encuentre. Cuando se ejecuta api. 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 Developing an A n no futan Pivcessor de la documentacin de Sun

1852 Piensa en Java Cuando creamos un procesador de anotaciones para utilizarlo con api, no podemos emplear los mecanismos de reflexin de Java porque estamos trabajando con cdigo fuente, no con clases compiladas.50 La API mirror* resuelve este problema permitindonos 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:

//: annocations/Extractlnterface.java // Procesamiento de anotaciones basado en APT- package annetationa; import java.lang.annotation.*;

Target(ElementType.TYPE)

Retention l RetentiQnPollcy. 3QRCE) public fcinterface Extractlnterface ( public String valu Sin embargo. uiili/.nnJo la opcin mi Cimlar-Xilnm** \*I)eclv M: puede trabajar con anotaciones que estn contenido en clases compiladas
50

20 Anotaciones 1853 O;

) ///:-

El valor de RetencinPollcy es SOURCF. 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 formar parte de una interfaz:

//: annocations/Multipiier.java // Procesamiento de anotaciones basado en APT. package annotations; Extract Inter face ("IMultiplier") public class Multiplier { public int multiply(int x. int y) { Int total 0; for(int i * 0; i < x; i++) total = add(total, y); return total;

1854 Piensa en Java > private int add(int x, int y) ( return x -* y; ) public static void main(StringU args) ( Multiplier m = new Multipliert) ; System.out.Drintln("11*16 = " m.multiply(11, 16)); t

) /* Output: 11*16 176 ///:-

La clasc Multiplier (que slo funciona con enteros positivos) tiene un mtodo multiply! ) que invoca numerosas veces el mtodo privado add( ) para llevar a cabo la multiplicacin. F.l mtodo add( ) no es pblico, as que no forma pane de la interfaz. A la anotacin se le asigna el valor de I Multiplier, que es el nombre de la interfaz que hay que crear.

20 Anotaciones 1855 Ahora necesitamos un procesador para realizar la extraccin: U : annotations/InterfaceExtr actcrProcessor.java // Procesamiento de anotaciones basado en APT. // (Exec: apt -factory // annotations.InterfaceExtractorProcessorFactory // Multiplier.java -s ../annotations) package annotations; import com.sun.mirror.apt.*; import com.sun.mirror.declaration.*; import j ava.io.; import java.util.*; public class IncerfaceExtractorProcess or implements AnnotationProcessor ( private final AnnotationProcessorEnvironment env; private ArrayList<MethodDeclaration> interfaceMethods * new ArrayList<MethodDeclaration>(1 ; public InterfaceExtractorProcessor( Annotat ionProcessorEnvironment env) ( this, env = env; ) public void process I) { for(TypeDeclaration typeDecl : env.getSpecifiedTypeDeclarations0) { Extractlnterface annot = typeDecl.getAnnotation(E xtractInterface.class)/ if(annot * null) break; for(MethodDeclaration m : typeDecl.getMethods()) if(m.getModifiers().contain s(Modifier.PUBLIC) && !

1856 Piensa en Java (m.getModifiers().contains( Modifier.STATIC))) interfaceMethods.add(m); if(interfaceMethods.size() > 0) { try { PrintWriter writer env.getFiler().createSou rceFile(annot.value()); writer .printIn ("package " typeDecl.getPackage() .getQualifiedName() writer.printIn("public interface " + annot.value() + " (); for(MethodDeclaration m : interfaceMethods) ( wrter .print (" public **); writer .print (m. getRetumType () ")/ writer .print (m.getSimpleName (1 4 iMn); int i = 0; for(ParameterDecla ration parm : m.getParameters{)) ( writer.print Iparm.getType () M * * parm.getSimpieN ameO ) ; ift++i < m.getParameters () .size ()) writer.printl, "); l writer .println (") ,*") /

20 Anotaciones 1857 wri ter.println("}") ; writer.cise(); ) catchIIOExcepton loe) ( throw new RuntimeException(ioe);

) i 1 III:-

El lugar donde se realiza lodo el trabajo es en el mtodo process() La clase MethodDeclaracin y su mtodo get!V1odfers() se usan para identificar los mtodos pblicos (pero ignorando los estticos) de la clase que se este procesando. 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.

1858 Piensa en Java Observe que al constructor se le pasa un objeto AnnotationProcessorFnvronmenc Podemos consultar este objeto para determinar todos los tipos (definiciones de clase) que la herramienta apC est procesando, y podemos usaio para obtener un objeto Messager y un objeto Filer El objeto Messager nos permite emitir mensajes dirigidos al usuario, por ejemplo cualquier error que pueda haberse producido en el procesamiento, junto con el lugar del cdigo fuente donde se haya producido. El objeto Filer es un tipo objeto PrintWriter a travs del cual se crean nuevos archivos. La principal razn de emplear un objeto Fer. en lugar de un objeto PrinCWriter simple es que permite a apC llevar la cuenta de los nuevos archivos que creemos, para asi poder comprobar si contienen anotaciones y compilarlas, en caso necesario.

Tambin podemos ver que el mtodo createSourceFile() abre un flujo de salida ordinario con el nombre correcto para nuestra interfaz o clase Java. No existe ningn tipo de soporte para la creacin de estructuras sintcticas del lenguaje Java, asi que hay que general el cdigo fuente Java utilizando los mtodos print() y printlii( ), un tanto primitivos Esto quiere decir que tenemos que aseguramos de que los corchetes estn bien emparejados y de que el cdigo sea sintcticamente correcto.

La herramienta apC invoca al mtodo process( ). porque la herramienta necesita una factora para proporcionar el procesador adecuado: //: annotatior.s/InterfaceExtractorProcessorFactory.java // Procesamiento de anotaciones basado en APT. packaae annotations; import com.sun.mlrror.apt.*/ import com. sur..mirror.declaracin. *; import java.util.*,public class InterfaceExtractorProcessorFact ory implements AnnotationProcessorFactory { public AnnotationProcessor

20 Anotaciones 1859 getProcessorFor( Set<AnnotationTypeDeclaration> atds, AnnotationProcessorEnvironment env) ( return new InterfaceExtractorProcessorlenv );

) public Collection<Strmg> supportedAnnorationTypes <) { return Collections.singleton(Mannotations.Extractlnterface") , publc ColIection<String> supportedOptions{) { return Colleccions.emptySet); l } m-.-

Slo hay tres mtodos en la inierfaz AnnotatlonProcessorFactory Como puede ver. el que proporciona el procesador es gctProcessorFor(), que toma un conjunto Set de declaraciones de tipo (las clases Java para las que se est ejecutando la herramienta apt), y el objeto AnnotationProcessorEnvironment, que ya hemos visto cmo se pasaba al procesador Los otros dos mtodos, supportedAnnotationTvpes( ) y supportedOptiom( ). sirven para poder comprobar que disponemos de procesadores para todas las anotaciones encontradas por api y que soportamos todas las anotaciones especificadas en la lnea de comandos. El mtodo getProctssorFor( ) es particularmente importante, porque si no devolvemos el nombre de clase completo de nuestro tipo de anotacin dentro de la coleccin String. apt emitir una advertencia informando de que no existe el procesador correspondiente y terminar su ejecucin sin hacer nada.

1860 Piensa en Java El procesador y la factora se encuentran en el paquete annotations. asi que. para la estructura de directorios anterior, la linca de comandos est incrustada en el marcador de comntanos Exec' al principio de InterfaceExtractorProcessor. java Esto le dice a apt que tiene que utilizar la clase factora definida anteriormente y procesar el archivo Multlpller.java. La opcin -s especifica que los nuevos archivos deben crearse en el directorio annotations. El archivo IMultipiier.java generado, como podemos adivinar examinando las instrucciones println( ) en el procesador anterior, tiene el aspecto siguiente: package annotations; public mterface IMultiplier ( public int multiply (int x, int y);

Este archivo tambin ser compilado por apt. asi que vera 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 relativamente simple que slo interpreta una anotacin, a pesar de lo cual requiere de una cierta complejidad para poder llevar a cabo su tareaPara evitar que la complejidad crezca desmesuradamente cuando tengamos ms anotaciones y ms procesadores. la API mirror proporciona clases para dar soporte al

20 Anotaciones 1861 patrn de diseo Vistame. Este patrn de diseo es uno de los patrones clsicos del libro Design Pat tenis de Gamma et al.. y tambin puede encontrar una explicacin ms detallada en T/tinking n Pal te rus.

Un Visitante reconc una estructura de datos o coleccin de objetos, realizando una operacin con cada uno. La estructura de datos no necesita estar ordenada, y la operacion que se realice con cada objeto ser especifica 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 operaciones 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 considerarse como una coleccin de objetos TypeDeclaration, FieldDeclaration. MethndDeclaratiun. etc. Cuando utilizamos la herramienta apt con el patrn Visitante, proporcionamos una clase Visftor que tiene un mtodo para gestionar cada tipo de declaracin que visitemos. De este modo, podemos implementar el comportamiento apropiado para las anotaciones asociadas 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. //: annotations/database/TableCreationProcessorFactory.java // Ei ejemplo de la base de datos usando el patrn de diseo Visitante. f f (Exec: apc -factory

1862 Piensa en Java // annotations.database.TableCreationProcessorFactory // database/Member.java -s database) package annotations.database ;import com. sun. mi rror. apt. ; import com.sun.mlrror.declaration.*; import com. sun. minor .util. i import java.util.j import static com.sun.mirror.util.DeclarationVisitors.*; public class TableCreationProcessorFactory implements AnnotationProcessorFactory ( public AnnotaticnPrccessor getProcessorFor( Set<AnnotatlonTypeDeclaration> atda, AnnotationProcessorEnvironment env) { return new TableCreationProcessor(env);

) public Collection<Strina> supportedAnnotationTypes0 { return Arrays.asList( "annotat ions.database.DBTable ", "annotations .database. Constraints**, "annotations. database. SQLString", i "annotat ions.database.SQLInteger"J;

public Collection<String> supportedOptions I) ( return Collections.emptySet(1; I private static class TableCreationProcessor implements AnnotationProcessor ( private final AnnotationProcessorEnvironment env; private String sql - *'H; public TableCreationProcessori AnnotationProcessorEnvironment env) { this. env = env;

20 Anotaciones 1863 ) public void process0 ( for(TypeDeclaration typeDeci ; env.getSpecifiedTypeDeclarations<)) ( typeDeci.accept(getBeclaratianScanner( new TableCreationVisitor0# NO_OP)); sql = sql.substring(0, sql.length0 - i) * Ml;"; Systemout.printliWcittdtion SQL is :\n" sql); sql M";

} private class TableCreationVisitor extends SimpleDeclarationVisitor ( public void visitClassDeclaration( ClassDeclaration d) ( DBTable dbTable * d.getAnnotation(DBTable.class) if(dbTable J- null) { sql sql "CREATE TABLE ** ; idbTable.nameO .length() < 1)

? d.getSimpleName().toUpperCase{) : dbTable.name(); sal +* " (**;

1864 Piensa en Java )

) public void visitFieldDeclarationi FieldDeclaration d) ( String columnName = if(d.getAnnotation(SQLInteger.class) i= null) (SQLInteaer slnt = d.ge:Annotation ( SQLInteger.class)/ t t Utilizar el nombre del campo si no se especifica un nombre, if fslnt .ame \. lengthO < 1) columnName =* d.getSimpleName(3 .toUpperCase() ; el se columnName = slnt. ame O; sql +* "\n " + columnName + " INT" -* getCcnstraintsIslnt.constraintsO) ",M;

) if(d.getAnnotationSQLStrina.class) != nuil) ( SQLString BString d.getAnnotaticnI SQLString.class ; // Utilizar el nombre del campo si no se especifica un nombre, ifisString.nameO .length) < 1J columnName = d.getSimpleName(>

20 Anotaciones 1865 .toUpperCase!); else columnName = sString.amefI; sql += 'Vn " * columnName - " VARCHA5U" * sString.valu(1 - getConscraintssString.constraintsO) -+ *, "i

) prvate String getConstraints(Constraints con) ( String constraints * if( Icon.allowNull()) constraints -= " NOT NULL"; if(con.primaryKeyl)1 constraints = " PP.IKARY KEY" ; If(ccn.unique <}) constraints += UN1QUE"/ return constraints;

} i ) nh-

La salida es idntica a la del ejemplo DBIablc anterior.

1866 Piensa en Java El procesador y el visitante son clases internas en este ejemplo. Observe que el mtodo process( ) slo aade la clase visitante e micializa la cadena SQL.

Los dos parmetros de getDecIurationScanncrt ) son visitantes; el primero se utiliza antes de visitar cada declaracin y el segundo despus Fste procesador slo necesita el visitante previo visita, asi 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.

TahleCreationVlsitor ampla SimpleDeclarationVisitor. sustituyendo los dos mtodos visitClassI)eclaration( ) y visitFieldDecaration() SimpleDeclarationVisitor es un adaptador que implementa todos los mtodos de la interfaz DeclarationVisitor. por lo que podemos concentrarnos en aquellos que necesitemos. En vistClassDeclaration( ). se comprueba el objeto ClassDeclaration en busca de la anotacin DBTabtc, y en caso de encontrarla, se iniciali/a la primera parte del objeto String de creacin de la cadena SQL. En vlstFeldDeclaration( ), 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 anteriormente en el capitulo.

Podra parecer que esta forma de hacer las cosas resulta ms complicada, pero permite obtener una solucin ms escalablc. 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.javn soporte para ms tipos SQL

20 Anotaciones 1867 Pruebas unitarias basadas en anotaciones Las pruebas miliarias son la practica de crear una o mus pruebas para cada mtodo Je una clase, con el fin de comprobar de forma metdica las distintas partes de una clase y verificar que su comportamiento es correcto La herramientas ms popular de pruebas unitarias en Java se denomina JlJnit, en el momento de escribir estas lneas. JUnit estaba a punto de ser actualizada a su versin 4, para poder incorporar anotaciones 51. 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 u lo largo del tiempo, pero las anotaciones permitirn simplificar todava mas el proceso ile pruebas. Con las versiones de JUnit que no disponan de soporte de anotaciones, era necesario crear una clase separada para definir las pruebas unitarias. Con las anotaciones, podemos incluir las pruebas unitarias dentro de !u clase que hay pmbar, reduciendo asi al mnimo el tiempo y la complejidad de las pruebas unitarias. Este tcnica tiene la ventaja adicional de permitir comprobar tanto los mtodos privados como los pblicos Puesto que este marco de trabajo para pruebas que utilizamos como ejemplo est basado en anotaciones, lo denominaremos (Unir. La forma ms bsica de pruebas, que es la que utilizaremos la mayor parte del tiempo, slo necesita la anotacin (a Test para indicar lo que hay que probar. Una opcin es que los mtodos de prueba no tomen ningn argumento y devuelvan un valor boolean para indicar el xito o el fallo de la prueba. Podemos utilizar cualquier nombre que queramos para los mtodos de prueba. Asimismo, los mtodos de prueba 'a l nit pueden tener cualquier tipo de acceso que deseemos, incluyendo prvate. Para utilizar a Un!, todo lo que hace falta es importar nct.nrindview.atunit, 52 marcar los mtodos y campos apropiados con marcadores de prueba (a l'nit (los cuales veremos en los siguientes ejemplos) y hacer que el sistema de construccin ejecute (a lJnit con la clase resultante. He aqu un ejemplo simple: /1; annotations /AttJnitSxamplel .java package annotat ions; import net.mindview.atunit.; import net.mindview.til.; public class AtUnitExamplel { public String methodOneO f recum "This is metnodOne';

] Originalmente, pense en disear una "versin avanzada de JUnit basada en el diseo mostrado aqu Sin embanjo. parece que JUnit tambin incluye muchas e las ideas que aqu se presentan, asi que me resulta md* sencillo utilizar la herramienta disponible 52 lista biblioteca o parte del cdigo del libro, disponible en * .\fuult)trwjtel
51

1868 Piensa en Java public int methodTwoO ( System.out.printin(This methodTwo'*) \ return 2; is

} #Test boolean methodOueTest() ( return methodOne () . cquals l "Thi.s is methodOne"), 1 Test boolean m 2 ( ) { return methodTwoO = * 2 ; ) Test prvate boolean m3 M { return true; ) // Muestra la salida en caso de fallo: STest boolean failureTest() { return f a l s e ; } Test boolean anocherDisappoint.ment. () { return false; ) public static void main(StringU args) throws Exception { OSExecute.commandI java net.mindview.atunlc.AtUnit AtnitExamDlel"); j } /* Output: annotations.AtUnitExamplel . me^hodOneTesc . m2 This is raethodTwo . m3 . failureTest (failed) . anotherDisappoin tment (failedl (5 tests! > 2 FAILURES < annotations.AtUnitExamplel: failureTest annotations.AtUnitExamplel: anotherDisappointment

20 Anotaciones 1869 *///:-

Las clases que hay que probar con (a L'ni deben encontrarse en paquetes.

La anotacin ( Test que va ames de los mtodos methodOne IesK ), m2(). m3(). failureTest() v unotherDisappoint- menl() le dice a (aUnit que ejecute estos mtodos como pruebas unitarias. Tambin garantiza que esos mtodos no tomen ningn argumento y devuelvan un valor boolean o void Nuestra nica responsabilidad a la hora de escribir la pnieba unitaria consiste en determinar si la prueba tiene xito o falla, y devuelve truc o false. respectivamente (para los mtodos que devuelvan un valor boolean).

Si esta familiarizado con JUnit. tambin se habr fijado en que (gjUnit proporciona 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 dice las clases y pruebas que han producido errores.

No estamos obligados a incluir los mtodos de pnieba dentro de nuestras clases, si esa solucin no nos sirve. La forma ms fcil de crear pruebas no embebidas es mediante el mecanismo de pnieba:

1870 Piensa en Java // : annotat ions/AtUnitExteraalTest. java // Creacin de pruebas no embebidas, package annotations; import net.mindview.atunit.*; import net.mindview.til.*; public class AtUnitExternalTest extends AtUnitExamplel ( Test boolean _methodOne() { return methodOne().equals("This is methodOne");

} -STest boolean _methodTwo(> { return methodTwoU = 2; J public static void main(String[J args) throwo Exception { OSExecute.command t "java net.mindview.atunit AtUnit AtUnitExtcmalTcat")

} ] / Output: annotation9.AtUnitExternalTest . _methodOne . _methodTwo This is methodTwo OK (2 tests)

20 Anotaciones 1871 *///:-

Este ejemplo tambin ilustra el valor de los esquemas de denominacin flexibles (a diferencia del requisito de JUnit que exige que todas nuestras pniebas comiencen por la palabra test*). Aqu, los mtodos @Test que estn probando directamente 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 utilizar el mecanismo de composicin para crear pruebas no embebidas: //: annotations/AtUnitComposition.java // Creacin de pruebas no embebidas, package annotations; import net.mindview.atunit. import net .mindview.til .* ,* public cla3s AtUnitCompositi on { AtUnicExample1 testObject new AtnitExamplel(); STest boolean _methodOne( return testObject.methodOneO.equals("This is methodOne*);

} Test boolean

1872 Piensa en Java _methodTwo1> { return testObject.methodTwo0 * 2;

} public static void main (String [] args) throws Exception { OSExecute.commandi "java net.mindview.atunit.AtUnit AtdnitComposition"|;

) } / Output: annotations.AtUnirComposition . _methodOne . _methodTwo This is methodTwo OK (2 tests)

*///:-

20 Anotaciones 1873 Para cada prueba se crea un nuevo miembro testObject, ya que se crea para cada prueba un objeto AtUnitComposition.

No existen mtodos especiales assert" como en JUnit. pero la segunda forma del mtodo (a Test permite devolver void (o boolean, si seguimos queriendo devolver true o false en este caso). Para comprobar que la prueba ha tenido xito, podemos utilizar instrucciones assert de Java. Las aserciones de Java normalmente tienen que ser habilitadas con el indicador -ea en la linea de comandos java, pero a lw;nit se encarga automticamente de habilitarlas. Para indicar el fallo, podemos incluso emplear una excepcin. Uno de los objetivos de diseo de (a Unit es imponer la menor cantidad posible de sintaxis adicional, y las excepciones e instrucciones assert de Java son lo nico necesario para informar acerca de las errores Una asercin fallida o una excepcin generada dentro del mtodo de prueba se tratan como una prueba fallida pero fa Unit no se detiene en este caso, sino que contina hasta haber ejecutado todas las pruebas. //: annotations/AtUnitExample2.java // Se pueden utilizar aserciones y excepciones en las pruebas. package annotations; import j ava.lo.; import net.mindview.at unit.*; import net.mindview.util.; public class AtUnitExampl e2 ( public String methodOneO { return "This is methodOne";

1874 Piensa en Java ) public int methodTwo() ( System, out .println { "This is methodTwo"); return 2;

i STest void assertExample() { assert methodOne() .equals (This is methodOne") ;

) Test void assertFailureExample () ( assert 1 2: "What a surprise!";

) dTest void exceptionExample0 throws IOException ( new FileInputStream("nofile.txt"); // Genera excepcin

) Test boolean assertAndRetum () {

20 Anotaciones 1875 // Asercin con mensaje: assert methodTwo() 2: "methodTwo must equal 2"; return methodOne().equals("This is methodOne"); public static void main(String[] aras I throws Exception ( OSExecute.command I *1ava net.mindview.atunit.AtUnit AtUnitExample?");

) } /* Output: annotations.AtUnitExample2 assertExample . assertFailureExample java.lang.AssertionError: What a surprise! (failed . exceptlonExample ]ava.io.FileNouFoundException: nofile.txt (The system cannot find the file specified) (failed) . aBsertAndRetum This is methodTwo (4 tests) > 2 FAILURES < annotations.AtUnitExample2; assertFailureExample annotations.AtUnitExample2: exceptionExample ///:-

1876 Piensa en Java He aqu un ejemplo utilizando pruebas no embebidas con aserciones, en el que se realizan algunas pruebas simples de juva.util.HashSet: //: annotations/HashSetTest.java package annotations; impere java.utll.*; import net.mindview,atunit *; import net.mindview.util. * t public class HashSetTest ( HashSet<String> testObject = new HashSet<String>(); Test void initializati on() { assert testObject.i sEmotyCl; i Test void _conta ms() { restOb ^ect.a ddion e 1 ; assert teaiOb j ecc . contai ns < "out?" ! ; l liTesc void _reraovel) { testObject.addi"one") , testObject .remove {"one") / assert cesrObject.isEmpcyI);

) public static void mam (String[J argsi throws Exception ( OSExecute.command

20 Anotaciones 1877 i ) / * O u t p u t : a n n o t a t i o n s . H a s h S e t T e s t . i n i t i a l i z a t i o "java net .mindview.atunit .AtUnit HashSetTeBt *) :

1878 Piensa en Java n . _ r e m o v e . _ c o n t a i n s O K ( 3 t e s t s )

V//:-

20 Anotaciones 1879 La solucin basada en la herencia parece ms simple, en ausencia de otras restricciones. Ejercicio 4: prueb (3) Verifique que se crea un nuevo objeto lestObject antes de cada

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1880 aEjercicio 6: (I) Pruebe I.inkedUst utilizando la tcnica mostrada en HashSetTest.java

Ejercicio 7:

(l) Modifique el ejercicio anterior para utilizar la solucin basada en la herencia

Para cada prueba unitaria, (a Unit crea un objeto de la clase que se est probando Utilizando un constructor predeterminado. Se invoca la prueba para dicho objeto, y o continuacin se descarta el objeto para evitar que se deslicen efectos secndanos en otras pruebas unitarias. Esta solucin utiliza el constructor predeterminado para crear los objetos. Si no disponemos de un constructor predeterminado o necesitamos un mecanismo ms sofisticado de construccin de los objetos, hay que crear un mtodos esttico para generar el objeto y asociar la anotacin (q TestObjectCreute. como en el ejemplo siguiente //: annotations/AtUnitExampieS. java package annotatlona; import net. mindview.atunit.*, import net.mindview.util.*/ public claes AtUnitExample3 { private int n; public AtUnitExaraple3lint n) ( this.n n; | public int getNl) ( return n; ) public String methodOne!) ( return *This is methodOne;

) public int methodTwoO ( System, out. print Ini "This is methodTwo'*} ; return 2}

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1881 ) TestObjectCreate static AtUnitExample3 create!) ( return new AtUnitExamDle3(47);

) Test boolean initialization 0 ( return n == 47; ) Test boolean metbodOneTest () ( return methodOne (J .equals ("This is methodOne")?

} 'Test boolean m2 () ( return methodTwo () 2 ? ) public static void mam (String [1 arqa) throws Exception ( OSExecute.command( "java net.mindview.aturut.AtUnit AtUnitExampleV) ;

} ) / Output: annotations.AtUm txample3 . initialization . methodOneTest . m2 This is methodTwo OK (3 tests I *///:-

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1882 El mtodo @TestObjectCrcate 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 (aTest Property 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 String que se descompone utilizando el mtodo String.splitt ) Esta entrada se emplea para generar objetos de prueba: //: annotations/AtUnitExample4.java package annotations; import java.til.*; import net.mindview.atunit. ; import net.mindview.util; import static net.mindview.util.Print. *; public claES AtUmtExamp 1 e4 ( static String theory = "All brontosauruses " * "are thin at one end, much MUCH thicker in the t "middle, and then thin again at the far end."; private String word; private Random rand = new Random 0? // Semilla basada en tiempo public AtUmtExample4 (String word) { this.word = word; ) public String getWord(J ( return word; ) public String scrambleWord() ( List<Character> chars = new ArrayList<Character>(); for(Character c : word.toCharArray()) chars.add(c); Collections.shuffle(chars, rand); StnngBullder result * new StrmgBuilder () ; for(char ch ; chars) result.append(ch); return result. toStnnq () ;

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1883 Test Property static List<String> input * Arrays.asList(theory.split(" H)); i&Test Property static Iterator<Stnng> words = input. iterator 0 ; TestObjectCreate static AtUnitExample4 create() ( if(words.hasNext()) return new AtUnitExample4 (words. next ()) / else return null;

) Test boolean words() ( print (* " + getWora () + " * M); return getword().eaualsI"are");

} fiTest boolean scramble!() ( // Cambiar a una semilla especifica para cbtener resultados verificables: rand = new Random(47) ; print { " -r getword() + ' ) j String scrambled scrambleWord(1; print(scrambled); return scrambled. equals (" 1A1") ;

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1884 ) siTest boolean scramble2 (i ( rand = new Random (74) ; print < * " t- getWord () t M "*) ; String scrambled e scrambleWord(); print(scrambled); return scrambled.equals ("tsaeboromussu") ;

) public static void main(String[J args) throws Exception { System.out.println("starting14) ; OSExecute.command( "java net.mindview.atunit.AtUnit AtUnitExample4");

) ) / * Output: starting annotations.AtUnitExample^ . scramblel 'All' LAI. scrambles brontosauruses' tsaeboromussu . words * are * OK O tests!

///t-

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1885 Tambin podemos usar a TestProperty para marcar mtodos que pueden ser utilizados durante las pruebas, pem que no sean pruebas en si 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 micializacin que requiera una posterior limpieza, podemos aadir opcionalmente un mtodo esttico (a TestObjectCleanup para realizar la limpieza cuando hayamos terminado de usar el objeto de pnieba. En este ejemplo, fu TcstObJectCreate abre un archivo para crear cada objeto de prueba, asi que es necesario cerrar el archivo antes de descartar el objeto de prueba: //: annotatlona/AtOntExample5.java package annotations; impon: 3 ava. io. *; import net.mindview.atunit. * ; import net.mindview.til.*; public class At0nitExample5 ( private String text; public AtUnitExamples(String text) ( this.text = text: ) public String toStringl) { return text; ) ^TestProperty static PrintWriter output; 'TestProperty static int counter; iTestObjectCreate static AtUnitExampleS create() { String id = Integer.toString(counter^-); try { output * new PrintWriter("Test" id ".txt"1; } catch(IOException e) {

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1886 i throw new RuntimeException(e);

return new AtUnitExample5{id);

) TesfcObjectClpanup static void cleanupAcUnitExample5 tobjI { System.out.printIn("Running cleanup") / output .closed i

) fcTest boolean testl() ( output.print{"te st1 "); return true; I iTeat boolean test2I) { output.print("te st2 " ) j return true;

} Test boolean test3() ( output.print I"test3"); return

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1887 true;

} public static void main (String [] args) throws Exception ( OSExecute.command( "java net.mindview.atunit.AtUnit AtUnitExampleS");

) } /* Output: annotations. At Uni tExampleo . testl Running cleanup . teat2 Running cleanup . test3 Running c1eanup OK 1 3 tests)

*///?-

Podemos ver. analizando la salida, que despus de cada prueba se ejecuta automticamente el mtodo de limpieza. Utilizacin de @Unit con genricos

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1888 Los genericos plantean un problema especial, porque no resulta posible probar genricamente'. Debemos efectuar las pruebas para un parmetro de tipo especfico o un conjunto de parmetros de tipo. La solucin es simple: heredar una clase de prueba a partir de una versin especificada de la clase genrica.

He aqu una implcmentacin simple de una pila: //: annotations/StackL.java / / U n pila construida sobre un contenedor IinkedList. package annotations; import java.util.; public class StackL<T> ( private LinkedList<T> list = new LinkedList<T>l ) ; public void push(T v) ( list.addFirst(v! ; | public T top(j { return list.getFirst(); } public T pool) { return list.removeFirst( ) ; )

) ///=-

Para probar una version String. hereda una clase de prueba de StackL<String>: / / : annotations/StackLStringTest.java // Aplicacin de Unit a genricos, package annotations; impart net.mindview.atunit.*; import net. mindview.util.j public class StackLStringTest extends 5tackL<String> j Test void _p'-ih () ( push < "one*4);

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1889 assert top I > equals (one) , push I"two"); assert topi).equals<"two");

) *Teat void _popO ( push("one"); push("two"); assert pop().equals("two"); assert pop().equals("one"I; 3>Test void _top 11 { push ("A*) ,* push^H") / assert top 0.equals("B"l; assert top().equals("B" ) ;

public static void main(String[] argsl throws Exception ( OSExecute.command I "nava net.mindview.atunit.Atnit StackLStrinaTest"I;

) } /* Output: annotatlona.ScackLStringTest . _push . _pop

_toP OK (3 tests!

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1890 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 constituyo un problema, podemos definir el mtodo en cuestin como protectcd, o aadir un mtodo no privado Test Prope rry que invoque al mtodo privado (el mtodo (gTestPropcrty ser luego eliminado del cdigo de produccin por la herramienta AtUnit Remover que se muestra ms adelante en el captulo),

Ejercicio 8: (2) Cree una clase con un mtodo privado y aada un mtodo (TestPropert> no privado como se ha des

crito anteriormente. Invoque este mtodo en su cdigo de pruebas.

Ejercicio 9: (2) Escriba pruebas (a'Unit bsicas para llashMap

Ejercicio 10: (2) Seleccione un ejemplo de algn otro lugar del libro y aada pruebas (a Dnit No hace falta ningn "agrupamiento

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1891 Una de las grandes ventajas de @linlt sobre JUnit es que no hacen falta agrupamicntos. En JUnit, necesitamos poder decir de alguna forma a la herramienta de pruebas unitarias qu es lo que necesitamos probar, y esto requiere la introduccin de agrupamientos" de pruebas, para que JUnit pueda encontrarlos V ejecutar las pruebas

(a t'nit simplemente busca archivos de clase que contengan las anotaciones apropiadas, y ejecuta a continuacin los mtodos (Test. Uno de los principales objetivos que me plante al desarrollar el sistema de pruebas (rlinit es que fuera enormemente transparente, para que los desabolladores pudieran comenzar a utilizarlo simplemente aadiendo mtodos (a 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 difcil escribir pruebas sin aadir nuevos errores, como para tambin perder el tiempo con complicaciones innecesarias, asi que tTJnit trata de hacer que la tarea de definir las tarcas unitarias sea trivial. De esta forma, resulta ms probable que el diseador se anime a escribir esas pruebas Implementacin de @Unit

En primer lugar, necesitamos definir todos los ttpos de anotacin. Se trata de marcadores simples que no tienen ningn campo El marcador (5/Test ya se ha definido al principio del capitulo y aqui estn el resto de las anotaciones: //: net/mindview/atunit/TestObjectCreat e.java // El marcador Unit TestObjectCreate package net.mindview.atunit; import java.lana.annotation.*; Target (ElemsntType . METHOD) SRetentionlRetent i onPolley.RUNTIME) public -nterface TestQbjectCreate {} ///^ // ; net/mindview/atunit/TeetObjectClean up. java // El marcador lUnlt tfTestObjectCleanup. package net.

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1892 mindview. atunit ; import java.lang.annotation.*; Target IElemencType.METHOD) Retention RetenticnPollcy.RUNTIME) public fcinterEace TestObjectCleanup {) ///://: net/mindview/atunit/TestProperty.java f f El marcador Unit TestProperry, package net.mindview.atunit; import java.lang.annotation.#; // Se pueden marcar como propiedades tanto ios campos como los mtodos: Target({ElementType.FIELD, ElementType.METHOD}) iP.et ent ion (Ret ent ionPolicy. RUNTIME) public interface TestProperty {] ///:-

Todas las pruebas tienen un tipo retencin igual a Rl!NTIM. porque el sistema (a Unit debe descubrir las pruebas en el cdigo compilado.

Para implementar el sistema que ejecuta las pruebas, utilizamos el mecanismo de reflexin para extraer las anotaciones. F.I programa utili/a esta informacin para decidir cmo construir los objetos de prueba y para ejecutar las pruebas sobre ellos. Gracias a las anotaciones el sistema es sorprendentemente pequeo y sencillo: //: net/rair.dview/atunit/AtUnit. java
II

Un sistema de pruebas unitarias basadas en anotaciones.

// (RunByHand} package net.mindview.atunit; import java.lang.reflect.; import java.io.*; import java.util.*;

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1893 import net.mindview.util.j import static net.mindview.util.Print.*; public class AtUnit implements ProcessFil.es.Strategy ( static Class<?> testClass; static List<String> failedTests= new ArrayList<String>(); static long testsRun = 0; static long failures * 0 ; public static void main{String{] args) throws Exception ( ClassLoader.getSystemClassLoader{) .setDefaultAssertionStatus(true); // Habilitar aserciones new ProcessFiles(new AtUnit0, "class").start(args); if(failures 0) print("OK tH + testsRun + " tests)"); else ( print I" <" 4 testsRun + M tests)"),- print (u\n> "failures * FAILURE + (failures > 1 ? "S" ; m m ) + " <")/ for(String failed : failedTests) print I" " + failed);

) public void process(File cFilei ( try ( String cName = ClassNameFinder.thisClass( BinaryFile.read(cFi le)); if {! cName. contains (*."})

Ejercicio 5:(1) Modifique el ejemplo anterior para utilizar la solucin basada en la herencia. 20 Anotaciones 1894 return; f t Ignorar clases no empaquetadas testClass = ClaBS.forName(cName); ) catch(Exception e) { throw new RuntimeExceotionie) ;

) TestMethods testMethods = new TestMethods(); Method creator = null; Method cleanup = null; for(Method m : testClass.getDeclaredMethodsi> ) ( testMethods .adalfTestMethod (m); if(creator null

20 Anotaciones 1895 )creator = checkForCreatorMethcd(m); if (cleanup == null) cleanup c checkForCleanupMethod(m) ;

) if(testMethods.size 0 > 0 ) ( if(creator == null) try { if(I Modifier.isPublic(testClass .getDeclaredConstructor().getModifiers())) ( print ("Error: " -r testClass " default constructor must be public"); System.exit fl) ;

) ) catch(NoSuchMethodException e) { // Constructor determinado sintetizado; OK i print' testClass .getName () ) ,*

) i

20 Anotaciones 1896 foriMethod m : testMethods) ( printnb ( " try ( Object testObject = createTestObject(creator); boolean success * false; try { if(m.getReturnType().equals(boolean.class)) success = (Boolean)m.invoke(testObject); else ( m.invoke(testObj ect); success = true; // Si no falla ninguna asercin j j catch(InvocationTargetException e) ( // La excepcin en s est dentro de e: print(e.getCause() ); . " ra. getName () " " J ;

) print(success ? : "(failed) * ) testsRun--^; if(success) { failures**; falledTests. add (testClass. getName () * M *. " * m.aetName());

) if(cleanup I* null) cleanup.invoke(testObject, testObject) ; ) catch(Exception e) [ i

20 Anotaciones 1897 throw new RuntimeException(e);

) static class TestMethods extends ArrayList<Method> ( void addIfTestMethod(Method m) { if(m.getAnnotation(Test.class) null) return; if(I(m.getReturnType().equals(boolean.class) || m.getReturn'rype I) .equals (void.class)) ) throw new Runt imeExcept ion ("STest method0 + " must return boolean or void),* m. setAccessible (true) ; // En caso de que sea privado, etc. add(m); )private static Method checkrorCreatorMethod(Method ml { if(m.getAnnotation(TestObjectCreate.claEsl == null) return null; if ilm.aetRetumTypelI .equals itestClass)I throw new KuntimeException i i

20 Anotaciones 1898 H534TetObject Create " -r "must return instance of Class to be tested"); if i Im.getModif iers 1 1 & java.lang.reflect.Modifier. STATIC) < 1) throw new RuntimeException("TestObjectC reate " + mus e be static, )/ m.setAc cessibl eitrue) j return m;

) private static Method checkForCleanupMethod(Method raj ( ifim.getAnnotation(Te3t0bjec tCleanup.class) == null) return null; iff im.getRetumType O . equal e (void, class) 1 throw new RuntimeExceptiont"TestQbjectCleanup " "mus t return void")j if(im.g etModif iers() h. java.lang.reflect.Modifier. No cst duro poi qu el constructor predeterminado de la el se quo CtenU probando debe ser publico, pero w no lo es. la llamada a new Instame 1 >c i
53

20 Anotaciones 1899 STATIC) < 1) throw new RuntimeException "JTestQbjectCleanup MH "must be static.); if(m.getParameterTypes(J -length == 0 || m.getParameterTypes()IQ] 1= testClassi throw new RuntimeException '54'TestObjectCleanup + "must take an argument of the tested type."); m.setAccessible(true); reLurn m;

) private static Object createTestObject(Method creator) ( ificreator !- null) { try { return creator - invoke ItestClassJ; | catch(Exception e) ( throw new RuntimeException(Couldn't run t "TestObject (creator) method.");

54

CUelgo (sin enerar una excepcin) " Jeremy Meyer y yo nu* pasamos la mayor pane de una jomada rrniando de descubra la solucin

20 Anotaciones 1900 } ) else { // Utilizar el constructor predeterminado: try I return testClass.newlnstanceI); } catchiException e) ( throw new RuntimeExceptionCouldn't create a " -* "test object. Try using a TestObject method.uH);

) ) i

) ///r-

Atlinit.java utiliza la herramienta ProcessFiles de net.mindvieu.util. La clase Alt nit implementa ProcessFUes.Strategv. que contiene el mtodo process( ). De esta forma, se puede pasar una instancia de AtUnit al constructor ProcessFiles. Et segundo argumento del constructor le dice a ProcessFUes que busque todos los archivos que tengan la extensin class. i

20 Anotaciones 1901 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 archivos de clase (con o sin la extensin .class) o directorios. Puesto que ( llnlt encuentra automticamente las clases y mtodos que son susceptibles de prueba, no hace falta ningn mecanismo de agrupamicnto.*

Un de los problemas que At Unit, ja va debe resolver cuando descubre archivos de clase es que el nombre de clase real cualificado (incluyendo 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.0 Por tanto, lo primero que sucede cuando se localiza un archivo .class es que se abre y sus datos binarios son leidos \ entregados a ( lassNameFinder.thisClass( ) Aqu, nos estamos introduciendo en el campo de la "ingeniera de cdigo intermedio*1, porque lo que estamos haciendo es analizar el contenido de un archivo de clase: //: nec/mindview/afunit/ClassNarae?inder.3 ava package net.mindview.atunit; import java.io.*; import java.util.*; import net.mindview.util.; import static net.mindview.util.Pr int. public class ClassNameFinder ( public static String thisClass(byteU class3ytes) ( Mapdnteger, Integer> offsetTable * new HashHap<Integer,In teger>(); Map<Integer,Strin g* classNameTable = new HashMapcInteger,S tring>(); try { DatalnputStream data = i

20 Anotaciones 1902 new DataInputStream( new ByteArraylnputStreamicl assBytes) ) ; int magic = data.readlnt(); / / Oxcafebabe int minorVersion data.readShort(); int majorVersion = data.readShort()j int constant_pool_count = data.readShort{); mt(] constant__pool = new inc lconstant_pool_counc] j for (int i - 1; i < cons tan t_pool_count; ii+J ( int tag = data, read(); in t ca bl eS iz e; sw it ch (t ag ) { ca se 1: // UT F int length = data.readShort\ ) charf) bytes = new char [length] ; for(int k = 0 ; k < bytes.length; k++) bytes[kj * I char)data.read(); String className new String(bytes); claseN&meTable.put( i, className) : break; case 5: // i

20 Anotaciones 1903 LONG case 6 : // DOUBLE dat3 .readLong(); // descartar 8 bytes I*-*; // Salto especial necesario break; case 7: // CLASS int offset = data.readShort (J; offsetTable.pu cii. offset) \ break; case 8 ; // STRING data.readShort(); // descartar 2 bytea break; case 3: // INTEGER case 4://FLOAT case 9://FIELD_REF case 10: U METHOD_REF case 11: // INTERFACE_METH QD_RE? case 12: // NAME_AND_TYPE data.readInti); // descartar 4 bytes; brale ,* default . throw new RuntimeException("Bad tag " + tag) ;

) i ahort access_flags = daca.readShort(1; irit thisclass =

20 Anotaciones 1904 data.readShort(); nt super_class * data.readShort('; retum classNameTable.get ( offsetTable.get<this_class)).replace('/' * ' ') ; ) catch(Exception e) { throw new RuntimeException(e);

} // Ilustracin: public static void main(String11 args) throws Exception { if(args.length >0) ( forfStrmg arg : args) print <thioClass (BinaryFile. read (new File (arg) ))) ; ) else // Recorrer todo el rbol: for(File klass : Directory.walk.H, .*\\.class")) print: (ciisClass (BinaryFile. read (klasaj) l ;

20 Anotaciones 1905 } J ///i-

Aunque no podemos analizar aqui todos los detalles, cada archivo de clase se ajusta a un formato 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 realizada en el flujo de entrada. Por ejemplo, los primeros 32 bits de cualquier archivo de clase son siempre el nmero mgico oxcafehabe, 55 y los dos siguientes valores short son la informacin de versin. La seccin de constantes contiene las constantes del programa y es, por tanto, de tamao variable; el siguiente valor short nos dice cul es el tamao para poder asignar una matriz del tamao apropiado. Cada entrada de la seccin de constantes puede ser un valor de tamao fijo o variable, asi que tenemos que examinar el marcador con el que comienza cada uno para ver qu hay que hacer con l. sa es la razn de la instruccin snitch. Aqu, no estamos tratando de analizar con precisin todos los datos del archivo de clase, sino simplemente recorrer sta y extraer los fragmentos de inters, as que, como puede ver en el ejemplo, se descarta mui gran cantidad de datos. La informacin acerca de lis clases est almacenada en las tablas classNamcTable y offset Tahle. Despus de leer la seccin de constantes, podemos encontrar la informacin this__class que es un ndice para la tabla offsetCable, que produce un ndice para la tabla dassNameTable, en la que podemos leer el nombre de la clase.

Volviendo a AtLnit.java. el mtodo proccss() ahora dispone del nombre de la clase y puede tratar de determinar si contiene 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.forName( ). Ahora podemos analizar la clase en busca de anotaciones a)Unit

Hay vuriui leyendo* relativa ni significado de este numen i mgico, pero como .lava fue creado por autnticos fnkies. podemos suponer, razonabletiicn- le. que tiene algo que ver con fantasas adolescentes acerca de una mujer en una caFetcria. i
55

20 Anotaciones 1906 Slo necesitamos buscar tres cosas: mtodos (a Test, que estn almacenados en una lista TestMethods, y si existen mtodos (q TcstObjeetCreate y (a 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 (ja. Test, se imprime el nombre de la clase para que podamos ver lo que est sucediendo, a continuacin de lo cual se ejecuta cada prueba. Esto implica imprimir el nombre de un mtodo, luego invocar createTestObject( ), el cual utilizara el mtodo a TestObjectCreate si existe o utilizar el constructor predeterminado si no existe, l'na vez creado el objeto de prueba, se invoca el mtodo de prueba para dicho objeto Si la prueba devuelve un valor booleun. se captura el resultado. Si no. presuponemos que la prueba lia tenido xito a menos que se genere una excepcin (que es lo que sucedera en caso de que se produzca una asercin fallida u cualquier otro tipo de excepcin) Si se genera una excepcin, se imprime la informacin de excepcin para mostrar la causa. Si se produce cualquier fallo, se incrementa el contador de faJlos y se aade el nombre de la clase y el mtodo a failedTests para poder incluirlos en el informe que se genera al final de la ejecucin.

Ejercicio 11: (5) Aada una anotacin (TeslNote a (fe-Unit, para que la nota asociada se visualice simplemente duran

te las pruebas. Eliminacin del cdigo de prueba i

20 Anotaciones 1907 Aunque en muchos proyectos no pasa nada si dejamos el cdigo de prueba en el cdigo fmai (especialmente si definimos todos los mtodos de prueba como pmate. cosa que siempre podemos hacer), en algunos casos conviene eliminar el cdigo de prueba, para que el tamao del producto sea menor o para que ese cdigo no est al alcance del cliente.

listo requiere prcticas de ingeniera de cdigo intermedio demasiado sofisticadas como para realizarlas manualmente. Sin embargo, la biblioteca de cdigo abierto Javassist" hace posible la ingeniera de cdigo intermedio. lil siguiente programa admite un indicador -r opcional como primer argumento: si incluimos el indicador, eliminar las anotaciones . Test. mientras que si no lo incluimos se limitar a mostrar esas anotaciones. Tambin se emplea aqu ProcessFlles para recorrer los archivos y directorios que hayamos elegido: //: net/mindview/atunit/AtUnitRemover,java // Visualiza las anotaciones @Unic existentes en los archivos de // clase compilados. Si el primer argumento es *-r"r se eliminan // las anotaciones Unit. // (Args: ..) // (Requires: javassist.bytecode.ClassFile; / i Debe instalar la biblioteca Javassist disponible en // http://sourceforge.net/projects/jboss/ } package net.mindview.atunit; import javass i 31. import javassist. e x p r ; import j avassist.bytecode.*; import j avas3ist.bytecode.annotation.*; import java.io.*; mport java.util.*; import net.mindview til. ; i import static net.mindview.til.Print. j

20 Anotaciones 1908 public class AtUnitRemover implements ProcessFlles.Str ateay ( private static boolear. remove = false; public static void main (String [] args) throws Exceptxon ( if (args .length > 0 && args[0].equals("-r"1J ( remove ^ true; StringM nargs = new Stringtaxgs.length - 1]; System.arraycopy(args, 1, nargs. O, nargs.lengthl; args = nargs;

! new ProcessFlles( new AtUnitRemover(), "class"!.start(args);

) public void process(File cFile) ( boolean modified = false; try ( String cName = CiassNameFmder, thisClass ( i

20 Anotaciones 1909 Gracias al Dr Shigcni Chiba por crear ola biblioteca, v por tinta la ayuda que ine prest a la hora de desurrollar Allenii Remo* er.java BinaryFile.read(crile)); if{lcName.cnntans return; // Ignorar clases no empaquetadas ClassPool cPool = ClassPool.getDefault(); CtClass ctClass cPool.get(cName); for'CtMethod method : ctClass.getDeclaredMethodsi)) { Hethodlnfo rm = method.getMethodlnfoli; Annotat ior.aAt tribute attr - l AnnotatiGnsAttribute} mi.getAttribute(AnnotationsAt tribute.visibleTag)/ ifattr == nuil) continu; for (Annotatior. ann : attr .getAnnotations l J J ( if(ann.getTypeName() .startsWith(net.mindview.atunit")) print(ctClass.getNa mef) + " Method: " mi.getName 0 + " " annl ; if(remove) ( ctClass.rem oveMethod(m ethod); modified = true; (

20 Anotaciones 1910 ) // Los campos no se eliminan en esta versin (vase el texto) . ifimodifledl ctClass.toBytecode(new DataOutputStream( new FileOutputStream(cFile))); ctClass.detach(); ) catchlBxceptlon e) ( i throw new RuntimeException(e);

) ///-

ClassPool es una especie de resumen de lodas las clases del sistema que estemos modificando. Garantiza la coherencia de todas las clases modificadas. Podemos extraer cada clase CtClass de ClassPool, de forma similar a como el cargador de clases y Class.for,Nanie( ) cargan las clases en la mquina JVM.

20 Anotaciones 1911 CtClass contiene el cdigo intermedio de un objeto de clase y nos permite generar informacin acerca de la clase y manipular el cdigo de la misma. Aqui, invocamos getDeclaredMrthods( ) (al igual que el mecanismo de reflexin de Java) y obtenemos un objeto Methodlnfo a partir de cada mtodo ClMethod. Con esto, podemos examinar las anotaciones. Si

algn mtodo tiene una anotacin en cl paquete nct.mindview.atunit, se elimina dicho mtodo.

Si la clase ha sido modificada, se sobreescribc cl archivo de clase original con la nueva clase. F.n cl momento de escribir estas lincas, se acababa de aadir la funcionalidad de eliminacin" de Javassistn1', y descubri

mos que eliminar los campos fa TestPropcrty resulta ms complejo que eliminar los mtodos. Dado que pueden existir operaciones de inicial i/acin esttica que hagan referencia a esos campos, no podemos limitamos a borrarlos. Por tanto, la versin anterior del cdigo slo elimina los mtodos llnt. Sin embargo, consulte cl sitio web de Javassist para ver si existen actualizaciones; es posible que en el futuro se aada la funcionalidad de eliminacin de campos Mientras tanto, observe que cl mtodo de prueba extemo mostrado en AtUnltExternalTest.java permite eliminar todas las pruebas simplemente borrando todos los archivos de clase creados por el cdigo de prueba. i

20 Anotaciones 1912 Resumen

Resulta muy de agradecer que se hayan aadido las anotaciones a Java. Constituyen una forma estructurada (y con comprobacin de tipos) de aadir metadatos al cdigo sin hacer que ste se complique innecesariamente y resulte ilegible. Las i: 1 Dr. ShiyiMTi Chiba fue lan amable de aadir l mtntit CiC'lflssrcuiovcY!cthod() a solicitud nuestra.

anotaciones pueden ayudarnos a eliminar la tediosa tarea de escribir descriptores de implantacin y otros archivos venerados. ti hecho de que el marcador Javadoc (.a dcpricated haya sido sustituido por la anotacin (a Deprecated es simplemente una indicacin de hasta que punto las anotaciones son mucho ms convenientes que los comentarios para describir la informacin de las clases.

Java SF.5 slo incluye un pequeo nmero de anotaciones Esto quiere decir que. si no utiliza una biblioteca de otro fabricante. necesitar crcar sus propias anotaciones, junto con la lgica asociada. Con la herramienta apt. podemos compilar los archivos recin generados en un nico paso, facilitndose asi el proceso de construccin de aplicaciones, pero actualmente la API mirror tan slo incluye la funcionalidad bsica para ayudamos 3 identificar los elementos de las definiciones de clases Java. Como hemos visto, podemos utilizar Javassist para las tareas de ingeniera de cdigo intermedio, o bien podemos escribir a mano nuestras propias herramientas de manipulacin de cdigo intermedio. i

20 Anotaciones 1913 1.a situacin mejorar sin ninguna duda en el futuro, y los fabricantes de interfaces API y sistemas comenzarn a proporcionar anotaciones como pane de sus herramientas. Como puede imaginarse ai analizar el sistema <a Unit. resulta bastante previsible que las anotaciones provoquen cambios significativos en la forma de programar en Java Puede encuMiur la solucione* u ton ejercicios seleccionado en el documento electrnico The Thinkmg nJcno Anmmteii Solutton Cuhte, disponible pnra la veniu cu h'hil AiwlWtm-.nci

.Concurrencia

21

Hasta este momento, hemos estado hablando de programacin secuencia!. Todo lo que sucede en un programa sucede paso a paso.

Una gran cantidad de problemas de programacin pueden resolverse utilizando 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 realmente de manera simultnea.

La programacin paralela puede mejorar enormemente la velocidad de ejecucin de los programas, proporcionar un modelo ms 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 situado a un nivel superior que las tcnicas de programacin 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 ejecutando en paralelo comienzan a interferir entre s. Esto puede suceder de una manera 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 escribir programas concurrentes 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 tan infrecuente que jams aparezcan 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 ocasionalmente, 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 proteccin como la verificacin en tiempo de compilacin o las excepciones comprobadas, para informamos de cundo hemos cometido un error. Con la concurrencia, toda la responsabilidad recae sobre nosotros, y slo si somos suspicaces y agresivos podremos escribir cdigo multihebra en Java que sea lo 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 independientemente y que los pocos casos en los que la concurrencia aparece durante las tareas cotidianas de programacin (como por ejemplo, con las interfaces grficas de usuario) pueden tratarse sin necesidad de recurrir a estructuras especiales del lenguaje. Por qu introducir un tema tan complejo si podemos evitarlo?

Ojal tuera 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 cdigo basado en hebras. Por ejemplo, los sistemas web constituyen una de las aplicaciones Java ms comunes y la clase bsica de la biblioteca web, servlef, 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 serviet puede

parecer muy simple, es necesario que entendamos los problemas de la concurrencia con el fin de utilizar los serviets apropiadamente. 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 mecanismos para la seguridad de las hebras resulta difcil 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 conscientes de su existencia. Como resultado, existen muchos programas Java que funcionan simplemente por accidente o que funcionan la mayor pane de las veces y que fallan misteriosamente de vez en cuando debido a problemas de concurrencia no localizados. En ocasiones, estos fallos son benignos, pero otras veces pueden representar la perdida de datos de gran valor, y si no somos al menos conscientes de los problemas concurrencia, podemos terminar 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 comportamiento incorrecto.

La programacin concurrente es como desembarcar en un nuevo mundo y aprender un nuevo lenguaje, o al menos un nuevo conjunto de conceptos del lenguaje. Comprender la programacin concurrente tiene cl mismo nivel de dificultad que comprender la programacin orientada a objetos. Si nos aplicamos, podemos llegar a entender el mecanismo bsico, pero generalmente 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 entender los conceptos y se puedan escribir programas multihebra de una complejidad razonable, pero tenga en cuenta que resulta 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 problema que resolver utilizando la concurrencia y ms de una tcnica para implcmcntar la concurrencia, y no existe una clara correspondencia entre estos dos aspectos (e incluso, a menudo, las lneas de separacin con completamente difusas). Como resultado, estamos obligados a tratar de entender todos los problemas y los casos especiales para poder emplear la concurrencia 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 descomponemos 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.

Sidisponemos de una mquina multiprocesador se pueden distribuir mltiples tareas entre los distintos procesadores, lo que permite incrementar enormemente el rendimiento. Esto es lo que suele suceder con los potentes servidores web multiprocesador, que pueden distribuir un gran nmero 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.

Ksto puede parecer poco intuitivo. Si pensamos en ello, un programa concurrente que se est ejecutando en un nico procesador 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 cambio de contexto.

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 operaciones 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 sucesos. 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 duracin 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 escribamos. Si lo hiciramos, el cdigo tendra un aspecto terrible, y adems no existira ninguna garanta de que un programador no se olvidara de realizar esa comprobacin. Sin la concurrencia, la nica forma de tener una interfaz grfica 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 pane 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 operaciones y al mismo tiempo devolver el control al resto del programa. De hecho, parece que esto friera 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 proceso 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 mltiples copias de seguridad redundantes del estado actual del libro. Hacia una copia en un directorio local, otra en un dispositivo UStt, 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. Inicialmcnte, realizaba todas las copias secuencialmente, esperando a cue cada una se completara antes de dar comienzo a la siguiente. Pero entonces me di cuenta de que cada operacin de copia requera una cantidad de tiempo distinta, dependiendo de la velocidad de E/S del medio. Puesto que estaba utilizando un sistema operativo multitarea, poda iniciar cada operacin de copia como un proceso separado y dejarlas ejecutarse en paralelo, lo 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 tarca 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 importante es que no hay ningur.a 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 garantizar 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,56 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.

Eric Raymond, por ejemplo, hace un encendida defensa de esta idea en The Ar oj UNIXPmgramming (Addison-Wesley. 2004).
56

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 concurrente dedicado, como Erlang.

Java adopt la solucin ms tradicional que consiste en aadir cl soporte para hebras por encima de un lenguaje secuencial. En lugar de iniciar procesos externos en un sistema operativo multitarea, el mecanismo de hebras crea las distintas tarcas dentro de un nico proceso, representado por el programa que se est ejecutando. Una de las ventajas que esta solucin proporciona es la transparencia con respecto al sistema operativo, que era uno de los principales objetivos de diseo en Java. Por ejemplo, las versiones pre-OSX del sistema operativo Macintosh (que era objetivo relativamente importante para las primeras versiones de Java) no soportaba la multitarea. Si no se hubiera aadido el mecanismo multihebra a Java, los programas Java concurrentes no habran podido portarse a Macintosh ni a otras plataformas similares, incurriendo as en el requisito de que los programas deberan escribirse una vez y ejecutarse en todas partes.2 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 proporciona 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 mayora de las personas han tenido la oportunidad de ver algn tipo de simulacin u otro, bien en forma de juego informtico 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 esc 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 fiiera una tarea independiente.

Una simulacin de gran envergadura puede incluir un 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 tambin las puertas o las piedras. Los sistemas multihebra Este requisito nunca lia llegado a satisfacerse del todo y Sun ya no pone tanto nfasis en l. Irnicamente, una de las razones de que este requisito no llegara a satisfacerse puede sen precisamente, los problemas relativos al sistema de hebras, y puede que se hayan solventado en Java SE5.
2

tienen a menudo un lmite relativamente pequeo en cuaito al nmero de hebras disponibles, estando dicho lmite, en ocasiones, en el borde de las decenas o los centenares. Este nmero puede variar iera del control del programa: puede depender de la plataforma, o en cl 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 mecanismo de hebras de Java es con desalojo, lo que significa que hay un mecanismo de planificacin que proporciona franjas temporales 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 terga asignada. En un sistema cooperativo, cada tarea cede el control voluntariamente, lo que requiere que el programador inserte a propsito 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 lmite 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 cooperativos no estn diseados para distribuir las tareas entre los distintos procesadores, lo que puede resultar muy restrictivo.

En el otro extremo, la concurrencia representa un modelo muy til (porque refleja muy bien lo que sucede) cuando estamos trabajando con los modernos sistemas de mensajera, que involucran a muchas computadoras independientes distribuidas a Se podra argumentar que tratar de agregar la concurrencia a un lenguaje secuencia! es un enfoque condenado aJ fracaso, pero que cada cual saque sus propias conclusiones.
2

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 informacin ni introduzca informacin en los instantes incorrectos. Incluso si no pretende utilizar la concurrencia a menudo en el futuro inmediato, resulta muy til entender los conceptos implicados para poder comprender las arquitecturas de mensajera, que se estn convirtiendo en la forma predominante de crear sistemas distribuidos.

La concurrencia tieue 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 tarcas 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 tarca 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 tiempo dentro de un mismo programa: el procesador ir saltando de una tarea a otra, asignando a cada una parte de su tiempo.57 Cada tarca 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). 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 utilizacin 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 multiproccsador. 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 Runnuble. Para definir una tarea, basta con implementar Runnable y escribir un mtodo run() 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: //: concurrency/LiftOff.java // Ilustracin de ia interfaz Runnable. public class LiftOff implements Runnable { Esto es cierto citando el sistema utiliza un mecanismo de franjas temporales (por ejemplo, Windows). Solaris utiliza un modelo de concurrencia basado en una cola FTFO: a menos que se despierte una hebra de mayor prioridad, la hebra actual continuar ejecutndose hasta que se bloquee o termine. Eso significa que otras hebras con la misma pnondad no podrn ejecutarse hasta que la hebra actual ceda el control de procesador.
57

protected int countDown = 10; // Predeterminado prvate static int taskCount = 0; prvate final int id = taskCount++; public LiftOff() {} public LiftOff(int countDown) { this.countDown = countDown;

) public String status{) { retum #" id + "(" + (countDown > 0 ? countDown : "Liftoff!") + "), " g

} public void run() { while(countDown-- >0) { System.out.print(status()); Thread.yieldO ;

} ///:-

El identifcador 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 necesaria, 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 infinito, 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 pane 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 tarca no est dirigido por una hebra separada, sino que simplemente se le invoca desde main() (en realidad, s i que estamos usando una hebra: la que siempre se asigna a main( )): //: concurrency/MainThread.j ava public class KainThread { public static void main(String[] args) { LiftOff launch = new Liftoff(); launch.runO ;

} } /* Output: #0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #C(Liftoff!),

*///*-

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 explcitamente una tarea a una hebra. La clase Thread

La forma tradicional de transformar 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: //: concurrency/BasicThreads.j ava // El uso ms bsico de la clase Thread. public class BasicThreads { public static void main(String[] args) { Thread t new Thread (new Liftoff ()),- t.start () ; System.out.println("Waiting for LiftOff");

} ) /* Output: (90% match) Waiting for Liftoff

#0(9), #0(8), #0(7), #0(6), #0(5) , #0(4), #0(3), #0(2), #0(1), #0(LiftoffI),

*///:-

Un constructor Thread slo necesita un objeto Kunnable. Al invocar el mtodo start( ) de un objeto Thrcad 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 haciendo una llamada a un mtodo de larga duracin, podemos ver a la salida (el mensaje Waiting for LiftOfiP* 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 siendo 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 ejecutan de manera concertada:u //: concurrency/MoreBasicThread s.java // Adicin de ms hebras. public class More3asicThreads { public static void main(String[] args) { for(int i - 0; i < 5; i+ +) new Thread(new Liftoff()).start{ ) j System.out.printlnp'Waiting for Liftoff") ;

} } /* Output: (Sample) Waiting for una LiftOff u En este caso, nica hebra (main{)) est creando todas las hebras I.iftOff. Sin embargo, si tenemos mltiples hebras creando hebras LiftOff es posible que ms de una hebra LiftOff tenga el mismo valor id. Posteriormente en el capitulo veremos cual es la razn.

#1(LiftOff1)r #2(LiftOff1), #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 mquina, el procesador de hebras distribuir de manera transparente las hebras entre los distintos procesadores.58

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 siguiente. Por ejemplo, una versin anterior del JDK no efectuaba demasiado a menudo la conmutacin de hebras, por lo que la hebra l 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 .TDK 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, asi 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.

58

Esto no era as en algunas de las versiones anteriores de Java.

1929 Piensa en Java 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 si mismo, por lo que existe de hecho una referencia a ese objeto en algn lugar, y el depurador de memoria no puede borrar el objeto hasta que la tarea salga de su mtodo run( ) y termine. Podemos ver. analizandola salida, que todas las tareas se ejecutan efectivamente hasta su conclusin, por lo que una hebra crea una hebra de ejecucin separada que persiste despus de que se complete la llamada a start( ).

Ejercicio 1: (2) Implemente una clase Runnable. Dentro de rtm( ): 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 terminacin cuando la tarea termine. Cree varias de estas tareas y contrlelas utilizando hebras.

Ejercicio 2: (2) Siguiendo la forma de generics/Fibonacci.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.concurrent 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 una tarea, en lugar de ejecutar una tarea directamente, hay un objeto intermedio que se encarga de ejecutar la tarca. Los ejecutores permiten gestionar la ejecucin de tareas asincronas 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 (Executor) en lugar de crear explcitamente objetos public class FixedThreadPool {

1930 Piensa en Java Thread en MoreBasicThreads.Java. Un objeto LiftOff sabe cmo ejecutar una tarea especfica; al igual que el patrn de diseo Comando, expone un nico mtodo para ser ejecutado. Un objeto ExecutorService (un objeto Executor con un ciclo de vida de servicio, por ejemplo, apagar) sabe cmo construir el contexto apropiado para ejecutar objetos Runnable. En el siguiente ejemplo, el objeto CachcdThreadPool crea una hebra por cada tarca. Observe que se crea un objeto ExecutorService utilizando un mtodo Executors esttico que determina el tipo de objeto Executor que tiene que ser: //: concurrency/CachedThr eadPooi.java import j ava.Util.concurrent.* ; public class CachedThreadPooi { public static void main(String [] args} { ExecucorService exec = Executors.newCachedThreadPool( ); forint i = 0; i < 5,- i++) exec.execute(new LiftOff()); exec.shutdown() ;

*///:A menudo, puede utilizarse un nico Executor para crear y gestionar todas las tareas del sistema.

I-a llamada a shutdown() impide que se enven nuevas tarcas a ese objeto Executor. La hebra actual (en este caso, la que controla main()) continuar ejecutando todas las tareas enviadas antes de shutdown(). El programa finalizar en public class FixedThreadPool {

1931 Piensa en Java cuanto finalice todas las tareas del objeto Executor.

Podemos sustituir fcilmente el objeto CachedThreadPooi del ejemplo anterior por un tipo diferente de Executor. Un objeto FixedTlireadPool utiliza un conjunto limitado de hebras para ejecutar las tareas indicadas: //: concurrency/FixedThre adPool.j ava import j ava.til.concu rrent.*; public static void main(String[] args) { // El argumento del constructor es el nmero de tareas: ExecutorService exec = Executors .newFixedThreadPool (5) , forint i = 0; i < 5; i++) exec.execute(new Liftoff()); exec.shutdown();

} } /* Output: (Sample) #C (9), #0(8), #1(9)i#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), #C (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 i) ,#1(1), #2(1), #3(1), #4(1), #1(Liftoff!), #2(Liftoff J), #3(LiftcffI), #4(Liftoff!),

public class FixedThreadPool {

1932 Piensa en Java *///:-

Con FixedThreadPool, realizamos la costosa asignacin de hebras una nica vez, por adelantado, con lo que limitamos el nmero de hebras. Esto permite ahorrar tiempo, porque no tenemos que pagar constantemente el coste asociado a la creacin 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 conjunto preasignado. Con esta solucin, no podemos agotar los recursos disponibles porque el objeto FixedThreadPool utiliza 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 CachedThreadPooi, tambin puede considerar utilizar FixedThreadPool en el cdigo de produccin. CachedThreadPooi crear generalmente tantas hebras como necesite durante la ejecucin de un programa y luego dejar de crear nuevas hebras a medida que vaya pudiendo reciclar las antiguas, por lo que resulta razonable elegir en primer lugar este tipo de objeto Executor. Slo si esta tcnica causa problemas necesitaremos cambiar a un conjunto de hebras de tipo FixedThreadPool.

public class FixedThreadPool {

1933 Piensa en Java Un ejecutor SingleThreadExecutor es como FixedThreadPool pero con un tamao de una nica hebra.59 Esto resulta til para cualquier cosa que queramos ejecutar de manera continua en otra hebra (una tarca de larga duracin), como por ejemplo una tarea que se dedique a escuchar 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 tarcas se pondrn en cola y cada una de ellas se ejecutar hasta completarse antes de que se inicie la siguiente tarea, utilizando todas ellas la misma hebra. En el siguiente ejemplo, podemos ver cmo cada tarca se completa en el orden en que fue enviada antes de que d comienzo la siguiente. As, un ejecutor SingleThreadExecutor sealiza las tareas que se le envan y mantiene su propia cola (oculta) de tareas pendientes. //: concurrency/SingleThr eadExecutor.j ava import j ava.til.concurren-.* ; public class SingleThreadExecutor { public static void main(String[] args) { ExecutorService exec = Executors.newSing leThreadExecutor(); for(int i * 0; i < 5; i++) exec.execute(new LiftOff()); exec.shutdown();

Tambin ofrece una importante garantia de concurrencia que los otros Cipos de ejecutores no ofrecen: no se pueden invocar concurrentemente dos tareas. Esto hace que cambien los requisitos de bloqueo de las tareas (hablaremos sobre el bloqueo ms adelante en el captulo). public class FixedThreadPool {
59

1934 Piensa en Java } } /* Output :#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff1), #1(9),#1(8), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(Liftoff!) , #2(9), #2(8), #2(7), #2(6), #2(5),#2(4), #2(3), #2(2), #2(1), #2(Liftoff1) ,#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 SingleThread Executor para garantizar que en cada momento slo haya una tarea ejecutndose en cualquier hebra. De esta forma, no tenemos que preocupamos de la sincronizacin en lo que respecta 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. Serializando las tareas, podemos eliminar la necesidad de sealizar los objetos.

public class FixedThreadPool {

1935 Piensa en Java Ejercicio 3: (l) Repita el Ejercicio 1 utilizando los diferentes tipos de ejecutores mostrados en esta seccin.

Ejercicio 4: (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 Runnable. Callable, introducida en Java SE5, es un genrico con un parmetro de tipo que representa el valor de retomo del mtodo call) (en lugar de run()), y debe invocarse utilizando un mtodo subniit() de ExecutorService. He aqu un ejemplo simple: //: concurrency/Ca llableDemo.jav a import j ava.util.concu rrent.*; import java.util.*; class TaskWithResult implements Callable<String> { private int id; public TaskWithRe sult(int id) { this.id = id; public class FixedThreadPool {

1936 Piensa en Java ) public string call() { return "result of TaskWithResult " + id;

} public class CaliableDemo { public static void main(String(] args) { ExecutorService exec = Executors.newCachedThreadPool() ; ArrayList<Futur e<String>> results = new ArrayList<? uture<String>>( ); for (int i = 0; i < 10; i++) results.add(exec.submit(new TaskWithResult(i))) ; for(Future<String> fs : results) try { // get() se bloquea hasta completarse: System.out.println(fs.get()); } catch(InterruptedException e) { Syst era.out.printIn(e); return; } catch(ExecutionException e) { System.out.println(e); } finally { public class FixedThreadPool {

1937 Piensa en Java

exec.shutdo wn();

) El mtodo subniit( ) 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 completado y dispone de un resultado, podemos invocar get() para extraer ste. Tambin podemos simplemente invocar get() sin comprobar isDone(), en cuyo caso gct() se bloquear hasta que el resultado est listo. Podemos asimismo invocar get() con lina temporizacin, o invocar isDone( ) para ver si la tarea se ha completado, antes de tratar de llamar a get() para extraer el resultado.

public class FixedThreadPool {

1938 Piensa en Java El mtodo sobrecargado Executors.callable() toma un objeto Runuable y produce un objeto Callable. ExecutorService tiene algunos mtodos de invocacin que ejecutan colecciones de objetos Callable.

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 ejecucin de dicha tarea durante un cierto tiempo. En la clase LiftOT, si sustituimos la llamada a yield() por una llamada a sleep(), obtenemos lo siguiente: //: concurrency/SleepingTask.java // Llamada a sleep) para detenerse durante un tiempo, import j ava.util.concurrent.*; public class SleepingTask extends Liftoff { public void run() { try { public class FixedThreadPool {

1939 Piensa en Java while(countDown-- >0) { Sys tem.out.print(s tatus()); // A la antigua usanza: // Thread.sleep(100); // Al estilo Java SE5/6: TimeUnit.MILLISECONDS.sleep(100);

} } catch(InterruptedException e) { System.err.printIn("Interrupted");

} public static void main(String I] args) { ExecutorService exec = Executors.newCachedThreadPool( ) ; for (int i = 0; i < 5; i+ +) exec.execute(new SleepingTask()); exec.shutdown(); public class FixedThreadPool {

1940 Piensa en Java } } /* Output:

public class FixedThreadPool {

1941 Piensa en Java #0(9), #3(8), #1(6), #4(5), #2(3), #0(1), #1(9).#2(9), #4(8),#0(7), #2(6),#3(6), #0(4),#1(4), #3(3),#4(3), #1(1),#2(1), #3(9), #4(9), #1(7), #2(7), #4(6), #0(5), #2(4), #3(4), #0(2), #1(2), #3(1), #4(1), #0(8), #1(8), #3(7), #4(7), #1(5), #2(5), #4(4), #0(3), #2(2), #3(2), #0(Liftoff!), #2(8), #0(6), #3(5), #1(3), #4(2),

#1(Liftoff!), #2 (Liftoff!). #3 (Liftoff!), #4(Liftoff!),

*///:-

La llamada a sleep() puede generar una excepcin IntcrruptedException, y como podemos ver, esta excepcin se atrapa en ruu( ). Puesto que las excepciones no se propagan entre unas hebras y otras para volver a main()> es necesario gestionar de manera local dentro de cada tarea todas las excepciones que puedan generarse.

Java SE5 ha introducido la versin ms explcita de sleep() como pane de la clase TlmeUnit, como se muestra en el ejemplo anterior. Esto proporciona una mayor legibilidad, al permitimos especificar las unidades del retardo asociado con slcep(). TimeUnit tambin puede usarse para realizar conversiones, como veremos ms adelante en el capitulo.

public class FixedThreadPool {

1942 Piensa en Java 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 tarca pasa a dormir (se bloquea), lo 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, lo mejor que podemos hacer es emplear controles de sincronizacin (descritos ms adelante) o, 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 especificado.

Ejercicio 6: (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. Prioridad

La prioridad de una hebra le indica al planificador la importancia de esa hebra. Aunque el orden en que el procesador ejecuta 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 public class FixedThreadPool {

1943 Piensa en Java 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 manipular las prioridades de las hebras suele ser un error.

He aqu un ejemplo que ilustra los niveles de prioridad. Podemos leer la prioridad de una hebra existente con getPriority() y cambiarla en cualquier momento con setPriority(). //: concurrency/SimplePriorities.java // Muestra el uso de las prioridades de las hebras. import j ava.til.concurrent.*; public class SimplePriorities iraplements Runnable { private int countDown = 5; private volatile double d; // Sin optimizacin private int priority; public SimplePriorities(int priority) { this.priority = priority; i public String toStringO { return Thread.currentThread() + : nw + countDown;

} public void run() { public class FixedThreadPool {

1944 Piensa en Java Thread.currentThreaaO.setPriority(priority); while(true) { // Una operacin costosa, interrumpible: forint i = 1 ; i < 100000 ; i++) { d += (Math.PI + Math.E) / (double)i/ if(i % 1000 == 0) Thread.yield() ;

} System.out.println(t his); if{--countDown =- 0) return;

> public static vod main(String[] args) { fixecutorService exec = Executors.newCachedThreadPoo! public class FixedThreadPool {

1945 Piensa en Java (); for {int i 0; i < 5; ir+) exec.execute( new SimplePriorities(Thread.MI NPRIORITY)); exec.execute( new SimplePriorities(Thread.MA X_PRIORITY)); exec.shutdown(>;

} } /* Output: (70% match) Thread[pool-1thread6,10,main]: 5 Thread[pool-1thread6,10,main]: 4 Thread[pool-1thread6,10,main]: 3 Thread[pool-1thread6,10,main]: 2 Thread[pool-1thread6,10,mainj : 1 Thread[pool-1thread3,1,main]: 5 Thread[pool-1thread2,1,main]: 5 Thread[pool-1thread1,1,main]: 5 Thread[pool-1thread5,1,main]: 5 Thread 'pool-lthread4,1,main]: 5 public class FixedThreadPool {

1946 Piensa en Java *///:-

toString( ) se sustituye para utilizar Tkread.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-1-thread-l, pool-l-thiead-2, etc. B1 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 objeto Executor no ha comenzado la tarca 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 double. La variable d es de tipo voladle 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 mediante 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 comportamiento 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 interrumpida (en caso public class FixedThreadPool {

1947 Piensa en Java contrario, la visnalizacin 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 necanismo de planificacin intervenga, conmute dos tareas y preste atencin a las prioridades, de modo que las hebras de alta prioridad obtienen preferencia. Sin embargo, para garantizar que se produzca un cambio de contexto, se invocan regularmente instrucciones yield().

Aunque el JDK tiene 10 niveles de prioridad, este nmero no se corresponde excesivamente bien con los mecanismos utilizados por muchos sistemas operativos. Por ejemplo, Windows tiene 7 niveles de prioridad que no son fijos, por lo que esa correspondencia es indeterminada en el caso de este sistema operativo. El sistema Solaris de Sun tiene 231 niveles. El nico enfoque portable consiste en limitarse a utilizar MAX_PRIORlTY, NORM PRIORITY y MTN JPRIORITY 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 run(), 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 impleraentacin 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.

LiftOfT.java utiliza yield() para distribuir de manera adecuada el procesamiento entre las public class FixedThreadPool {

1948 Piensa en Java diversas tareas LiitOff. Pruebe a desactivar mediante comentarios la llamada a Thread.yield( ) en LiftOff.run( ) para ver la diferencia. Sin embargo, en general, no podemos confiar en yield() para ninguna tarea seria de control o de optimizacin de la aplicacin. De hecho, 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 termina, terminando tambin durante el proceso todas las hebras demonio. A la inversa, si existe alguna hebra no demonio que todava se est ejecutando, el programa no puede terminar. Existe, por ejemplo, una hebra no demonio que ejecuta el mtodo main(). //: concurrency/Simp 1 eDaetr.ons.java // Las hebras demonio no impiden que el programa termine. import java.util.concurrent.*; import static net.mindview.util.Print.*; public class SimpleDaemons implements Runr.abie { public void run() { try { while(true) { Timenit.MILLISECONDS. sleep (100) ; orint (Thread. currentThread ()" " + this);

} } catch(InterruptedSxception e) { print ("sleep () interrupted") ,* public class FixedThreadPool {

1949 Piensa en Java }

} public static void main(String 11 args) throws Exception { for {int i = 0; i < 10; i++) { Thread daemon = new Thread(new SimpleDaemons()); daemon.setDaemon(true); // Hay que invocarla antes de start() daemon.start();

) print("All daemons started"); TimeUnit.MILLISECONDS.sleep(175);

} } /* Output: (Sample) All daemons started Thread [Thread-0, 5,main] SimpleDaemons<3530daa public class FixedThreadPool {

1950 Piensa en Java Thread[Thread 1,5,main] SimpleDaemons@a62fc3 Thread[Thread-2,5,main] SimpleDaemons@89ae9e Thread[Thread-3,5,main] SimpleDaemons@1270b73 Thread[Thread 4,5,main] SimpleDaemons@60aeb0 Thread[Thread-5f5,main] SimpleDaemons@16caf43 Thread[Thread-6,5,main] 3impleDaemons@66848c Thread[Thread-7,5,main] SimpleDaemons@8813f2 Thread[Thread-8,5,main] SimpleDaemonsld58aae Thread [Thread9,5, main] Sinipl eDaemons@83cc67

*///:-

Debemos definir la hebra como demonio invocando setDaemon() antes de iniciarla.

No hay nada que impida al programa terminar una vez que main( ) finaliza su trabajo, ya que lo nico que qucca ejecutndose son hebras demonio. Para poder ver el resultado de iniciar todas las hebras demonio, la hebra inain() se pone brevemente a dormir. Sin esto, public class FixedThreadPool {

1951 Piensa en Java 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 Thread 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: //: net/mindview/util/Daemo nThreadFactory.java package net.mindview.til; import java.util.concurrent.*; public class DaemonThreadFactory implemento ThreadFactory { public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setDaemon(true); recurn t;

} } III:-

La nica diferencia con respecto a un objeto ThreadFactory normal es que ste asigna el public class FixedThreadPool {

1952 Piensa en Java valor true al indicador que identifica las hebras demonio. Ahora podemos pasar un nuevo objeto DaemonThreadFactory como argumento a Executors.newCachedThreadPooI(): //: concurreney/DaemonFromFactory.j ava // Utilizacin demonios. de una factora de hebras para crear

import java.util.concurrent.*; import net. mindview.til.*; import static net.mindview.til.Print.*; public class DaemonFromFactory impiements Runnable { public void run() { try { while(true) { TimeUnit.MILLISECONDS.oleep(100); print(Thread.currentThread() + " " this);

} } catch(InterruptedRxception e) { print("Interrupted");

public class FixedThreadPool {

1953 Piensa en Java ) public static void main(String[] arg3) throws Exception { ExecutorService exec = Executors .r.ev/CachedThreadPool ( new DaemonThreadFactory()); for(int i = 0; i < 10; i-+) exec.execute(new DaemonFromFactory()); print("Al1 daemons startedn); TimeUnit.MILLISECONDS.sleep500); // Ejecutar durante un tiempo

} } /* (Bjecutar para ver la salida) *///:-

Cada uno de los mtodos de creacin estticos ExecutorService se sobrecarga para tomar un objeto Thread Factory que se utilizar para crear nuevas hebras.

Podemos llevar este enfoque DaeuuonThreadPooIExecutor:

un

paso

ms

all

crear

una

utilidad

//: net/mindview/util/DaemonThreadPoo lExecutor.java package public class FixedThreadPool {

1954 Piensa en Java netmindview.util; import j ava.uti1.concurrent.*; public class DaemonThreadPoolExecutor extends ThreadPoolExecutor { public DaemonThreadPoolExecutor() { super(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable >(), new DaemonThreadFactory());

} } ///-

Para obtener los valores para llamada al constructor de la clase base, simplemente hemos echado un vistazo al cdigo tiien- te Executors.java.

Podemos averiguar si una hebra es de tipo demonio invocando i.sDaemon(). Si una hebra es un demonio, entonces todas las hebras que cree sern tambin demonios automticamente, como demuestra el siguiente ejemplo: //: concurrency/Daemons.j ava public class FixedThreadPool {

1955 Piensa en Java // Las hebras demonio crean otras hebras demonio. import j ava.uti1.concurrent.*; import static net.mindview.util.Print.*; class Daemon implements Runnable { private Thread[] t = new Thread[10] ; public void run() { for(int i = 0; i < t.length; i++) { t[i] = new Thread(new DaemonSpawn()); t [i].start (); printnb( "DaemonSpawn " -f i + " started, ") ;

} for (int i 0; i < t. length; i++) printnb("t[" i + "] .isDaemonO * " + tii].isDaemon() + ") ; while(true) Thread.yield();

public class FixedThreadPool {

1956 Piensa en Java } class DaemonSpawn implements Runnable { public void run() { while(true) Thread.yield();

} public class Daemons { public static void main(StringU args) throws Exception { Thread d = new Thread (new DaemonO); d.setDaemon(true); d.start(); printnb( "d. isDaemon () = ' + d. isDaemonO + ", "); // Permitir que las hebras demonio finalicen // sus procesos de arranque: Timenit.SECONDS.sleep(1);

public class FixedThreadPool {

1957 Piensa en Java > } /* Output: (Sample) d.isDaemon() = true, DaemonSpawn 0 started, DaemonSpawn 1 started, DaemonSpawn 2 started, DaemonSpawn 3 started, DaemonSpawn 4 started, DaemonSpawn 5 started, DaemonSpawn started, DaemonSpawn 7 started, DaemonSpawn 8 started, DaemonSpawn 9 started, t [0].isDaemon() = true, t [1].isDaemon() = true, t [2].isDaemon() = true, t [3].isDaemon()= true,t [4].isDaemon() t [5].isDaemon()= true,t [ 6 ] . isDaemon() t [7].isDaemon()= true,t [81.isDaemon() t [9].isDaemon()= true, ^ = = true, true, true,

*///:-

La hebra Daenion se configura en modo demonio. A continuacin, esa hebra inicia una serie de otras hebras (que no se definen explcitamente como demonios), para demostrar de todos modos que esas hebras sern de tipo demonio. A continuacin, Daemon entra en un bucle ininito que llama a yicld() para ceder el control a los otros procesos.

public class FixedThreadPool {

1958 Piensa en Java Es necesario tener en cuenta que las hebras demonio terminarn sus mtodos run( ) sin ejecutar clusulas tinally: //: concurrency/DaemonsDontRunFinaliy.j ava // Las hebras demonio no ejecutan la clusula finally import java.til.concurrent.*/ import static net.mindview.til.Print.*; class ADaemon implenents Runnable { public void run() { try { print("Starting ADaemon"); TimeUnit.SECONDS.sieep(l); } catch(InterruptedException e) { print (11 Exiting via InterruptedException"); } finally { print("This should always run?");

public class FixedThreadPool {

1959 Piensa en Java } public clacc DaemonaDontRunPinally { public static void main (String [] args) throws Exception { Thread t = new Thread(new ADaemon()); t.setDaemon(true); t.start();

> } /* Output: Starting ADaemon *///:-

Cuando ejecutamos este programa, vemos que la clusula finally no se ejecuta, pero si desactivamos mediante comentarios la llamada a setl)aemon(), la clusula finally s que se ejecutar.

Este comportamiento es correcto, an cuando resulte algo inesperado, teniendo en cuenta las explicaciones dadas antes para finally. Los demonios se terminan abruptamente cuando termina la ltima de las hebras no demonio. Por tanto, en cuanto salimos de main( ), la mquina JVM termina todos ios demonios inmediatamente sin ninguna de las formalidades que cabra esperar. Dado que no se pueden finalizar los demonios de una manera limpia, las hebras demonio no suelen ser muy convenientes. Generalmente, public class FixedThreadPool {

1960 Piensa en Java 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 posteriormente en el captulo, esa terminacin tiene lugar, en este caso, de una manera ordenada. Ejercicio 7: (2) Experimente con en Daemons.java, para diferentes ver lo tiempos de que sucede dormir

public class FixedThreadPool {

1961 Piensa en Java .Ejercicio 8: (1) Modifique MoreBasicThreads.java para que todas las hebras sean de tipo demonio y el programa ter

mine en cuanto main() sea capaz de terminar.

Ejercicio 9: (3) Modifique SimplePriorities.java para que una factora personalizada ThrcadFactory establezca las

prioridades de todas las hebras. Variaciones de cdigo

En los ejemplos que hemos visto hasta ahora, todas las clases de tareas implementan la interfaz Runnable. En algunos casos muy simples, podemos utilizar la tcnica alternativa de heredar directamente de Thread, como en este ejemplo: public void run() { while(true) {

1962 Piensa en Java //: concurrer.cy/SimpleThrea.java // Herencia directa de la clase Thread. public class SmpleThread extends Thread { private int countDown = 5; private static int threadCount = 0; public SmpleThread() { // Almacenar el nombre de la hebra: super(Integer.toSt ring(+ +threadCount)); start () ;

} public String toStringO { return +getNameO+"(" +countDown + "), ";

} public void run() { while(true) { System.o ut.print (this); if(-countDow n == 0) return; public void run() { while(true) {

1963 Piensa en Java }

) public static void main(String[] args) { for (int i = 0; i < 5; i++) new SimpleThreadO ;

} } /* 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),

*///:-

public void run() { while(true) {

1964 Piensa en Java Proporcionamos a los objetos Thread nombres especficos invocando el constructor Thread apropiado. Este nombre se extrae en toString() utilizando jetNamc( ).

Otra estructura de cdigo que puede que se encuentre alguna vez es la del objeto Runnable auto-gestionado: //: concurrency/SelfManaged.java // n objeto Runnable que contiene su propia hebra directora. public class SelfManaged implements Runnable { private int countDown = 5; private Thread t = new Thread(this) ; public SelfManaged {) { t.start O; } public String toStringO { return Thread.currentThread().getNameO + " ( " + countDown + " ) , " ; }System.ouC.print(this) if{--countDown == 0) return;

} public static void main(String[] args) { for (int i = 0; i < 5; i++) new SelfManagedO ; } Output: Thread0(5) /* Thread1(5), Thread, 2(5), ThreadThread-0(4) , 2(4) , 3(5), ThreadThreadThread1(4), 3(4), 4(5), *///:ThreadThread-

public void run() { while(true) {

1965 Piensa en Java 4(4), Thread0(3), Thread) 1(3), Thread2(3), Thread3(3), Thread-2(2) Thread-1(1) Thread-3(2) Thread-2(1) Thread-4(2) Thread-3(1) Thread-0(2) Thread-1(2) Thread-4(1 Thread-0(1) Thread4(3),

Esta tcnica no difiere especialmente de la de heredar de Thread, 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. Bste 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, porque otra tarca 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: //: concurrency/ThreadVaria tions.java // Creacin de hebras con clases public void run() { while(true) {

1966 Piensa en Java internas, import java.til.concurrent.*; import static net.mindview.til.Print .*; // Utilizacin de ana clase interna nominada: class InnerThreadl { privat-e int countDown = 5; private Inner inner; private class Inner extends Thread { Inner(String ame) ( super(ame); start();

) public void run() { try { w h i l e ( t r u e ) { p r i n t ( public void run() { while(true) {

1967 Piensa en Java t h i s ) ; if(-countDown 0) return; sleep(lO);

} } catch(Interrupt edException e) { print("interr upted");

} public String toStringO { retum getNameO + ": " + countDown; public void run() { while(true) {

1968 Piensa en Java }

} public InnerThreadl(String ame) ( inner = new Inner(ame) ;}

} // utilizacin de una clase interna annima: class InnerThread2 f 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 == 0) return; sleep(10);

} } catch(InterruptedException e) { print("sleep{) interrupted"); public void run() { while(true) {

1969 Piensa en Java )

} public String toStringO { return getNameO " + countDown;

} it t.start();

> // Utilizacin de una implementacin Runnable nominada class InnerRunnable! { public void run() { while(true) {

1970 Piensa en Java 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 == 0) return; Timenit.MILLISECONDS.sleep(10);

} } catch(InterruptedException e) { print("sleep() interrupted);

} public void run() { while(true) {

1971 Piensa en Java } public String toStringO { return t. getNameO + ": n + countDown;

} public InnerRunnablel(String name) { inner = new Inner(name);

} // Utilizacin de una implementacin Runnable annima: class InnerRunnable2 { public void run() { while(true) {

1972 Piensa en Java private int countDown = 5 ;private Thread t; public InnerRunnable2(String name) { t = new Thread(new Runnable() { public void run() { try { while(true) { print(this); if(--countDown == 0) return; TimeUnit.MILLISECONDS.sleep(10);

} } catch(InterruptedException e) { print("sleep() interrupted" ) j

} public String toStringO { return Thread.currentThread().getName() - ": public void run() { while(true) {

1973 Piensa en Java " + countDown;

} } , name); t.start () ;

} // 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 = name; } public void runTask() { if(t null) { t = new Thread(name) { public void run() { try { while(true) { print(this); if (--countDown == 0) return; sleep(10);

} public void run() { while(true) {

1974 Piensa en Java } catch(InterruptedException e) { print("sleep() interrupted");

} public String toStringO { return aetNameO + ": " + countDown;

}; t.start();

public void run() { while(true) {

1975 Piensa en Java }

} public class ThreadVariations { public static void main (String [] args) { new InnerThreadl("InnerThreadlM) ; new ZnnerThread2("InnerThread2"); new InnerHunnablel("InnerRunnabl el"); new InnerRunnable2("XnnerRunnable2"); new ThreadMethod{ThreadMethod").runTask();

} } /* (Ejecutar para ver la salida) *///: ~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. LnnerThread2 muestra cul es la alternativa: dentro del constructor se crea una subclase interna annima de Thread y se la public void run() { while(true) {

1976 Piensa en Java generaliza a una referencia 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 Kunnable emplean la clase Thread.

La clase ThreadMethod muestra la creacin de una hebra dentro de un mtodo. Si invocamos el mtodo una vez que estemos 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

public void run() { while(true) {

1977 Piensa en Java 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 suhmit( ). 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 describir la tecnologa de programas concurrentes, especialmente en aquellos casos que hay implicadas hebras.

A estas alturas, debera 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 nosotros). 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 impresin que se obtiene al leer esto es que la hebra es la tarea, y cuando yo tropec con las hebras de Java, esta public void run() { while(true) {

1978 Piensa en Java impresin era tan fuerte que para m exista una relacin de tipo es-un muy clara, y de lo 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 debera 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 lo que deberamos poder definir una tarea y decir ejecutar sin preocupamos de los detalles. Pero fsicamente las hebras pueden resultar muy costosas de crear, por lo que es necesario conser/arlas y gestionarlas 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 C, que es una solucin en la que es necesario sumergirse y en la que es preciso entender todos los detalles de lo que est ocurriendo. Parte de esta naturaleza de bajo nivel ha terminado deslizndose en la implementacin Java, por lo que para permanecer en un nivel mayor de abstraccin, es necesario ser disciplinado a la hora de escribir el cdigo (trataremos de mostrar esa disciplina a lo 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 limitarnos a emplear el trmino tarea sin necesidad de mencionar en absoluto el mecanismo encargado de dirigir esa tarea. Absorcin de una hebra

public void run() { while(true) {

1979 Piensa en Java 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 tjoin() sobre otra hebra I, entonces la hebra invocante se suspende hasta que la hebra objetivo t finalice (cuando t.isAlive() es false).

Tambin podemos invocar join() con un argumento de fin de lemporizacin (en milisegundos o en milisegundos y nanose- gundos). de modo que si la hebra objetivo no finaliza en dicho perodo de tiempo, la llamada a join( ) vuelve de todos modos.

La llamada a join() puede abortarse invocando interrupt ) sobre la hebra invocante, para lo que hace falta una clusula try-catcb.

Todas estas operaciones se ilustran en el sigiente ejemplo: //: concurreney/Joining. j ava // Ejemplo de join(). import static net.mindview.til.Print.*; class Sleeper extends Thread { private int duration; public void run() { while(true) {

1980 Piensa en Java public Sleeper(String name, int sleepTime) { super(name); duration = sleepTime; start();

} public void run() { try { sleep(duration); } catch(InterruptedException e) { print (getName () + 11 was interrupted. " + "islnterrupted(): " + islnterrupted()); return;

} print(getName() + " has awakened");

} public void run() { while(true) {

1981 Piensa en Java } class Joiner extends Thread { private Sleeper sleeper; public Joiner(String name, Sleeper sleeper) { super(name); this.sleeper = sleeper; start();

} public void run() { try { 3leeper.join(); } catch(InterruptedExc eption e) { print("Interrupted ");

} print(getName() + " join completed");

public void run() { while(true) {

1982 Piensa en Java }

} public class Joining { public static void main(String[] args) { Sleeper sleepy * new Sleeper{Sleepy", 1500), grumpy = new Sleeper("Grumpy0, 1500); Joiner dopey = new Joiner("Dopey", sleepy), doc = new Joiner("Doc", grumpy); grumpy.interrupt();

> } /* Output: Grumpy was interrupted. islnterrupted): false Doc join completed Sleepy has awakened Dopey join completed *///:**

public void run() { while(true) {

1983 Piensa en Java Un objeto Slecper es una hebra que pasa a dormir durante un tiempo especificado en su constructor. En run(), la llamada a sleep() puede terminar cuando finaliza el tiempo especificado, pero tambin puede ser interrumpida. Dentro de la clusula catch, se informa de la interrupcin, junto con el valor de islnterrupted(). Cuando otra hebra invoca iuterrupt() 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 catch. El indicador se utiliza 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( ) sobre ese objeto SIeeper. En main(), 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.concurrent de Java SE5 contienen herramientas tales como CyclicBarrier (que se ilustra ms adelante en este capitulo) que pueden ser ms apropiadas que join(). 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 usuario de rpida respuesta. Aunque no vamos a sumergirnos en las interfaces grficas hasta el Captulo 22, Interfaces grficas de public void run() { while(true) {

1984 Piensa en Java 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 inserta el clculo dentro de una tarea y puede, por tanto, estar realizando a la vez el clculo y escuchando la entrada de la consola. //: concurrency/ResponsiveUI.j ava // Capacidad de respuesta de la interfaz de usuario // {RunByHand} class Unrespons iveUI { private volatile double d = 1/ public UnreeponsiveUI{) throwc Excepcin { while(d > 0) d = d (Math.PI + MaCh.E) / d; System.in.read(); // Nunca llega aqu

} public class ResponsiveUI extends Thread { private static volatile double d 1; public ResponsiveUI() { setDaemon(true); start();

public void run() { while(true) {

1985 Piensa en Java } public void run() { while(true) { d = d (Math.PI + Ma^h.E) / d;

) public static void main (String [] args) throws axception { //! new Unrespons iveUI ()//Hay que matar proceso new ResponsiveUI(); System.in.read(); System.out.println(d); // Mostrar el progreso este

public void run() { while(true) {

1986 Piensa en Java } ///:-

UnresponsiveUl realiza un clculo dentro de un bucle while infinito, 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 alcanzablc utilizando el while condicional). Si desactivamos mediante un comentario la lnea que crea una interfaz UnresponsiveUl, tendremos que matar el proceso para poder salir.

Para haccr 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 ei 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 palabras de Joshua Bloch,60 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, asi que se puede simplemente ignorar su existencia Effective JavaTM Programming Language Guide, de Joshua Bloch (AddisonWesley,2001), p. 211. public void run() { while(true) {
60

1987 Piensa en Java 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 laureado con el Premio Nobel tiene una filosofa de la vida que podra aplicarse aqu.61 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 especiales para capturar esas excepciones vagabundas. Antes de Java SE5, se utilizaban los grupos de hebras para capturar estas excepciones, pero con Java SE5 podemos resolver el problema mediante objetos Executor, por lo que deja de ser necesario saber nada acerca de los grupos de hebras (salvo para entender el cdigo heredado, vase Thinking in Java, 2Edicin, descargable de www.MindView.net, para conocer ms detalles sobre los grupos de hebras).

lie aqu una tarea que siempre genera una excepcin que se propaga fuera de su mtodo run( ) y un mtodo main() que muestra lo que sucede al ejecutarlo: //: concurrency/Exception Thread.j ava // {ThrowsException} import 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- sultoria en bastantes proyectos en los que esta filosofa era aplicable. public void run() { while(true) {
61

1988 Piensa en Java java.util.concurrent public ciass ExceptionThread implements Runnable { public void run() { throw new RuntimeException();

} public static void main(String[ ) args) { ExecutorService exec Executors.newCachedThreadPool() ; exec.execute(new ExceptionThread());

} ///:-

public void run() { while(true) {

1989 Piensa en Java La salida es (despus de quitar algunos cualiftcadores para que quepa en la pgina): j ava.lang.Runt imeExcept ion at ExceptionThread.run{ExceptionT hread.java:7) at ThreadPoolExecutor$Worker.runT ask{Unknown Source) at ThreadPoolExecutor$Worker.run( Unknown Source) at j ava.lang.Thread.run{Unknown Source)

No se pierde nada al encerrar el cuerpo del mtodo principal dentro un bloque try-catch: //: concurrency/NaiveExceptionKandling.j ava // {ThrowsException} import java.util.concurrent.*; public class NaiveSxceptionHandling { public static void main (String [] args) { try { ExecutorService exec = Executors.newCachedThreadPool( ); exec.execute(new ExceptionThread()); } catch(RuntimeException ue) { // i Esta instruccin NO se ejecutar! System.out.printIn("Exception has been handled!");

public void run() { while(true) {

1990 Piensa en Java }

} ///-

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. Thread.UncaughtExcep- tionHandler es una nueva interfaz en Java SE5; que nos permite asociar una rutina de tratamiento de excepciones a cada objeto Thread. Thread.UncaughtExceptionHandler.uncaughtException() se invoca automticamente cuando esa hebra est a punto de morir debido a una excpecin no capturada. Para usarla, creamos un nuevo tipo de factora ThreadFactory que asocia un nuevo objeto ThreadX'ncaughtExceptionHandler a cada nuevo objeto Thread que crea. Pasamos dicha factora al mtodo Executors que crea un nuevo objeto ExecutorService: public void run() { while(true) {

1991 Piensa en Java / /: concur rency/Cap tureUncaught Except ior.. j ava import java.util.concurrent.*; class ExceptionThread2 implements Runnable { public void run() { Thread t = Thread.currentThread(); System.out.println("run() by " + t) ; System.out.printIn( eh = " + t.getncaugntExceptionHanlerO); throw new RuntimeException() ;

} class XyUncaughtExceptionKandier implements Thread.UncaughtExceptionHandle r { public void uncaughtException(Thread t, Throwable e) { System.out.printIn("caught " + e);

public void run() { while(true) {

1992 Piensa en Java }

} class HandlerThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { System, out. print In (this + 11,f creating new Thread"); Thread t = new Thread(r); System.out.println("created " + t) ; l.setUncaughtExceptionKandler( new MyUncaughtExceptionHandlerO); System.out .println ( "eh = " + t.getUncaughtExceptionHa ndler()); return t;

public void run() { while(true) {

1993 Piensa en Java } public class CaptureUncaughtException { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool( new HandlerThreadFactory(}); exec.execute(new ExceptionThread2());

} } /* Output: (9C% match) HandlerThreadFactory@de6c ed creatnng new Thread created Thread[Thread0,5,main] eh = MyTJncaughtExceptionHandl erlib8ee3 run () by Thread [Thread-0, 5, main] eh = MyUncaughtExceptionHandle r@lfb8ee3 caught j ava.lang.RuntimeExcept ion *///**

Hemos aadido facilidades de traza adicionales para verificar que las hebras creadas por la factora reciben el nuevo objeto UncaughtExccptionHandler. Podemos ver que las excepciones no capturadas estn siendo ahora capturadas uncaughtExceptioo.

public void run() { while(true) {

1994 Piensa en Java El ejemplo anterior nos permite 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 predeterminada de tratamiento de excepciones no capturadas, que configura un campo esttico dentro de la clase Thread: //: concurrency/SettinoDefaul tHandler.java import j ava.util.concurrent.*; public class SettingDefaultHandler { public static void main(String[1 args) { Thread.setCefaultncaughtExcep tionHandler( new MyUncaughtExceptionHandler()); ExecutorService exec = Executors.newCachedThreadPool( ); exec.execute(new ExceptionThread());

} } /* Output: caught java.lang.Runt imeException *///:-

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 public void run() { while(true) {

1995 Piensa en Java dispone de una rutina y, en caso contrario, mira a ver si el grupo de hebras especializa su mtodo uncaugh(Exception(); 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 espacio de problema y que hace una sola cosa cada vez. Puesto que slo hay una entidad, no tenemos que pensar en el problema 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 interfiriendo 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. Acceso inapropiado a los recursos

Consideremos el siguiente ejemplo en el que una tarea genera nmeros pares y ofras public void run() { while(true) {

1996 Piensa en Java tareas consumen dichos nmeros. Aqu, el nico trabajo de las tareas consumidoras coasiste en comprobar la validez de los nmeros pares.

En primer lugar, definimos EvenChcckcr, la tarea consumidora, ya que la vamos a reutilizar en todos los ejemplos subsiguientes. Para desacoplar EvenChcckcr de los varios tipos de generadores con los que varaos a estar experimentando, crearemos una clase abstracta denominada IntGenerator, que contiene el mnimo nmero de mtodos necesarios que EvenChecker debe utilizar: dispone de un mtodo next() y el generador puede ser cancelado. Esta clase no implcmcnta la interfaz Generator, porque debe generar un valor int. y los genricos no soportan parmetros primitivos. //: concurrency/IntGenerator.java public abstract class IntGenerator { private volatile boolean cancelad = false; public abstract int next(); // Permitir que cancelarlo: public void cancelO { canceled = true; } public boolean isCanceled() { return canceled; }

} ///:-

public void run() { while(true) {

1997 Piensa en Java IntGenerator tiene un mtodo cancel() para cambiar el estado de un indicador booleano canceled, as como un mtodo isCanceled() 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 esc campo en un estado intermedio durante la realizacin de esas operaciones simples. El indicador canceled tambin es de tipo volatile, con el fin de asegurar la visibilidad. Hablaremos ms en detalle de la atomicidad y la visibilidad posteriormente en el captulo.

Cualquier objeto IntGenerator puede ser probado con la siguiente clase EvenChecker: //: concurrency/EvenChecker.j ava import ja va.util.concurrent.*; public class EvenChecker implements Runnable { private IntGenerator generator; private final int id; public EvenChecker(IntGenerator g, int ident) { generator = g; id = ident;

} public void run() { while({generator.isCancel ed)) { int val = generator.next(); if (val % 2 = 0) { System.out.println(val " not even!"); generator.cancel(); // Cancela codos los objetos public void run() { while(true) {

1998 Piensa en Java EvenChecker

} // Probar cualquier tipo de IntGenerator: public static void test(IntGenerator gp, int count) { System.out.println("Press Control-C to exit"); ExecutorService exec = Executors.newCachedThreadPoolO; for(int i = 0; i < count; i++) exec.execute(new EvenChecker(gp, i)); exec.shutdown();

public void run() { while(true) {

1999 Piensa en Java > // Valor predeterminado de recuento: public static void test(IntGenerator gp) { test(gp, 10);

} ///:-

Observe que en este ejemplo la clase que puede cancelarse no es de tipo Runnablc. En su lugar, todas las tareas EvenChecker que dependen del objeto IntGenerator 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 necesario 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.

public void run() { while(true) {

2000 Piensa en Java El mtodo test() 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 volver. 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 EvenChecker puede invocar cancel( ) sobre su objeto IntGenerator asociado, lo que har que todas las tareas EvenChecker que estn usando IntGenerator terminen de manera grcil. En secciones posteriores, 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 una serie de valores pares: //: concurrency/Even Generator.j ava // Cuando las hebras colisionan. public class EvenGenerator extends IntGenerator { private int currentEvenValue * 0; public int next() { ++currentEvenValue; // Punte de peligro! ++currentEvenValue; retura currentEvenValue; public void run() { while(true) {

2001 Piensa en Java i public static void man(String[] args) { EvenChecker.test(new EvenGenerator());

} } /* Output: (Sample) Press Control-C to exit 89476993 not even! 89476993 not evenl *///:-

Resulta posible que una tarea invoque ncxt( ) despus de que otra tarea haya realizado el primer incremento de currentEvenValue pero no el segundo (en el lugar del cdigo que tiene el comentario Punto de peligro!"). Esto hace que el valor quede en un estado incorrecto. Para probar que esto puede suceder, EvenChecker.test( ) crea un grupo de objetos 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 informacin est en un estado incorrecto. Sin embargo, puede que el problema no se detecte hasta que el objeto EvenGenerator haya completado muchos ciclos, dependiendo de las particularidades de public void run() { while(true) {

2002 Piensa en Java nuestro sistema operativo y de otros detalles de implementacin. Si queremos ver mucho ms rpido cmo falla el programa, podemos incluir una llamada a yleld() entre el primer y el segundo incremento. Esto es parte del problema con las programas multihebra: pueden parecer correctos a pesar que existe un error, siempre y cuando la probabilidad de fallo sea baja.

Es importante comprobar que la propia operacin de incremento requiere mltiples pasos y que la tarea puede ser suspendida 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. 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 suspendida 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 periodos crticos.

public void run() { while(true) {

2003 Piensa en Java 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 desbloquee, 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 sealizan el acceso a los recursos compartidos. Esto quiere decir que slo se permite que una sola tarea acceda al recurso compartido en cada momento. Esto se suele conseguir, normalmente, colocando una clusula alrededor de un fragmento de cdigo, de forma 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 ocupado. 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 public void run() { while(true) {

2004 Piensa en Java 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 bao lo desbloquea y sale de l, la tarea que est ms cerca de la puerta aprovechar para introducirse. 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 plataforma y de la implementacin de la mquina JVM.

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, comprueba 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 E/S o algo ms complejo como una impresora. Para controlar el acceso a un recurso compartido, primero ponemos ese recurso dentro de un objeto. Entonces cualquier mtodo que emplee el recurso puede sincronizarse 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 clase sean privados y, de manera que se acceda a esa memoria nicamente a public void run() { while(true) {

2005 Piensa en Java 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 cualquier mtodo synchronized, dicho objeto se bloquea y no podr llamarse a ningn otro mtodo synchronized de ese objeto hasta que el primero termine y libere el bloqueo. Para los mtodos del ejemplo anterior, si una tarea invoca f() para un objeto, ninguna otra tarea podrinvocar a f() o g( ) para el mismo objeto hastaque f() sehaya completadoy libere el bloqueo. Por tanto, existe un nico bloqueo que escompartido portodos los mtodos synchronized de un objetoconcreto, 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 oUa tarea acceda a un campo directamente, y por tanto se produzcan colisiones. public void run() { while(true) {

2006 Piensa en Java Una tarca 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, ele. La mquina JVM lleva la cuenta del nmero 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 llegue a valer cero, lo que hace que se libere el bloqueo completamente y que pueda ser usado por otras tareas.

Tambin existe un nico bloqueo por clase (como parte del objeto Class de dicha clase), de modo que los mtodos synchronized 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}0 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 sincronizacin: adems, tanto el lector como el escritor deben sincronizarse usando el mismo bloqueo monitor.

public void run() { while(true) {

2007 Piensa en Java 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 deber 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: //: concurrency/SynchronizedSvenGenerator.j ava // Simplificacin de los mutex con la palabra clave synchronized. // {RunByHand} public class Synchronizec&VenGeneraLor extends IntGenerator { private int currentEvenValue = 0; public synchronized int next() { ++currentEvenValue; Thread.yield(); // Provoca el fallo ms rpidamente +4currentSvenValue ; return currentEvenValue;

} public etatic void main (String [] args) ( EvenChecker. public void run() { while(true) { test (new SynchronizedEvenGer.eratcr

2008 Piensa en Java ()) ;

} ///:-

liemos insertado a Thread.yield() entre dos incrementos, para generar la probabilidad de que se produzca un cambio de contexto mientras que currentEvenValue 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 larea que est esperando a adquirir el bloqueo. De esta forma, slo puede haber una tarca en cada momento pasando public void run() { while(true) {

2009 Piensa en Java por el cdigo protegido por el mutex.

Ejercicio 11: (3) Cree 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 incorrectos (de acuerdo con una cierta definicin que usted mismo puede establecer). Aada mtodos para leer los campos y cree mltiples hebras para invocar los distintos mtodos y mostrar que los datos estn visibles en su estado incorrecto. Corrija el problema empleando la palabra clave synchronized. Uso de objetos Lock explcitos

La biblioteca java.util.concurrcnt de Java SE5 tambin contiene un mecanismo explcito de mutex definido en java.util.concurrcnt.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: //: concurrency/MutexEvenGenerator.j ava // Prevencin de las colisiones de hebras mediante mutex. public void run() { while(true) {

2010 Piensa en Java // {RunByHand} impor t j ava. ut i 1.concurrent.locks.*; public class MutexEvenGenerator extends IntGenerator { private int currentSvenValue = 0; private Lock lock = new ReentrantLock(); public int r.ext () { lock.lock(); try { ++currentEvenValue ; Threa.yieldO; // Provoca un fallo ms rpidamente + +currentEvenValue; return currentSvenValue; } finally ( lock.unlock();

} public static void main(String] args) { EvenChecker.test(new MutexEvenGenerator());

public void run() { while(true) {

2011 Piensa en Java }

} ///:**

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 internalizar la estructura sintctica que aqu se muestra: justo despus de la llamada a lock( ), hay que insertar una instruccin try-finally con nnIock( ) en la clusula fnally. 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 pane de otra tarea.

Aunque la clusula try-finally 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 excepcin, pero no tenemos la posibilidad de realizar ninguna tarea de limpieza para poder mantener el sistema en un estado correcto. Con los objetos I.ock explcitos, podemos mantener el estado correcto del sistema empleando la clusula fnaliy.

public void run() { while(true) {

2012 Piensa en Java 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 resolviendo problemas especiales. Por ejemplo, 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 haccr estas cosas, es necesario emplear la biblioteca concurrent: //: concurreney/At temptLocking.j ava // Los bloqueos de la biblioteca concurrent nos permiten // desistir al intentar adquirir un bloqueo, import java.til.concurrent; import j ava.util.concurrent.locks.*; public class AttemptLocking { private ReentrantLock lock = new ReentrantLock(); public void untimed() { boolean captured = lock.tryLockO; try { System.out.println("tryLock(): " + captured); } finally { if(captured) 1ock.unlock();

} public void run() { while(true) {

2013 Piensa en Java public void timed() { boolean captured = false; try { captured = lock.tryLock(2, TitneUnit.SECONDS) ; } catch(InterruptedBxception e) { throw new RuntimeException(e);

} try { System.out.println("tryLock(2, TimeUnit.SECONDS): " + captured); } finally ( if(captured) lock.unlock();

} public static void main(String[] args) { public void run() { while(true) {

2014 Piensa en Java final AttemptLocking al = new AttemptLocking(); al.untimedi); // True -- el bloqueo est disponible al.timed(); // True -- el bloqueo est disponible // Ahora crear una tarea separada para establecer el bloqueo: new Thread() { ( setDaemon(true); ) public void run{) { al.lock.lock(); System.out.println("acauired");

) }.start(); Thread.yield(); // Dar una oportunidad a la segunda tarea al.untimedO; // False -- bloqueo adquirido por la tarea al.timed0; // False -- bloqueo adquirido oor la tarea

} } /* Output: tryLock(): true tryLock(2, TimeUnit.SECONDS): true acquired tryLock(): false tryLock(2, TimeUnit.SECONDS): false *///:public void run() { while(true) {

2015 Piensa en Java Un bloqueo ReentrantLock nos permite intentar adquirir el bloqueo sin xito por lo que si alguien ms ha adquirido el bloqueo, 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 untlined(). En timed( ), se realiza un intento de adquirir el bloqueo que puede fallar despus de 2 segundos (observe el uso de la clase TimeUnit de Java SE5 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 permite el bloqueo synchronized integrado. Esto resulta til para implementar estructuras de sincronizacin especializadas, tales como por ejemplo la de bloqueo mano a mono (tambin conocida como acoplamiento de bloqueos), utilizada 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 operaciones atmicas no necesitan sincronizarse. Una operacin atmica es aquella que no puede ser interrumpida por el planificador 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 atomicidad en lugar de la sincronizacin si somos autnticos expertos en concurrencia o si disponemos de ayuda ds uno de tales expertos. Si considera que es lo suficientemente hbil como para jugar con luego, haga esta prueba:

public void run() { while(true) {

2016 Piensa en Java La Prueba de Goetz62: si eres capaz de escribir una mquina JVM de altas prestaciones para un nico procesador moderno, entonces podrs comenzar a pensar si puedes ahorrarte la sincronizacin.63

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 tentacin 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 memoria absolutamente indivisible (atmica). Sin embargo, la mquina JVM 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 tarcas podran ver resultados incorrectos (esto se denomina en ocasiones desgajamiento de palabra, porque existe la posibilidad de ver el valor despus 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 SE5). Las diferentes mquinas JVM son libres de proporcionar garantas ms fuertes, pero tenga en cuenta que no se debe confiar en las caractersticas que sean especficas de determinadas plataformas. Llamada as por el antes menciouado Brian Goelz, un experto en concurrencia que me ha ayudado a elaborar este captulo, que est parcialmente basado en algunos comentarios que me hizo. 63 Un corolario de esta prueba es: Si alguien sugiere que la gestin de hebras es sencilla, asegrate de que esa persona no tenga bajo su responsabilidades el tomar decisiones importantes acerca del proyecto. Si esa persona est en disposicin de tomar esas decisiones, tienes un problema. public void run() { while(true) {
62

2017 Piensa en Java Por tanto, las operaciones atmicas no son interrumpibles por el mecanismo de gestin de hebras. Los programadores expertos pueden aprovechar esto para escribir cdigo libre de bloqueos, que no necesita sincronizarse. Pero incluso esto es una simplificacin excesiva. F.n 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 qu^ no deberan 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 procesadores 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 procesador local, por ejemplo), de modo que diferentes tareas tendrn una visin distinta del estado de la aplicacin. El mecanismo 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 public void run() { while(true) {

2018 Piensa en Java 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 cambio. Esto es cierto incluso si se utilizan cachs locales: los campos voltiles se escriben inmediatamente en la memoria principal y las lecturas se realizan tambin en la memoria principal.

public void run() { while(true) {

2019 Piensa en Java

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 tarca que lea dicho campo no tendra por qu, necesariamente, ver el nuevo valor. Si hay mltiples tarcas accediendo a un campo, dicho campo debe ser de tipo voltil; 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 definirlo 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 fiinciona 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 (lower) y superior (upper) de una clase Range (rango) que deba obedecer la restriccin lower <= upper.

public void run() { while(true) {

2020 Piensa en Java Normalmente, slo resulta seguro volatile en lugar de synchronized si la clase slo tiene un campo modificabie. De nuevo, nuestra primera opcin debera ser emplear la palabra clave synchronized; 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 normalmente atmicas. Sin embargo, en C++ incluso las siguientes instrucciones podran ser atmicas: i++; //Podra seratmicaen C++ C++ i+= 2; //Podra seratmicaen

Pero en C++, esto depende del compilador y del procesador. No podemos escribir cdigo inter-plataforma en C++ que dependa de la atomicidad, porque C++ no tiene un modelo de memoria coherente, a diferencia de Java (en Java SE5).

En Java, las operaciones anteriores son. sin ninguna duda, no atmicas, como se puede ver analizando las instrucciones JVM generadas por los siguientes mtodos: //: concurrency/ Atomicity.ja va // {Exec: javap -c public void run() { while(true) {

2021 Piensa en Java Atomicity} public class Atomicity { int i; voidfl(){ i+~; } veidf 2 (){ i += 3; }

*///:} /* Output:(Sample) void f 1 ( ) ; Code:

13

void 2{); Code:

13 Esto se est tratando de remediar en el siguiente estndar C+-*- que se va a publicar.

public void run() { while(true) {

2022 Piensa en Java

public void run() { while(true) {

2023 Piensa en Java

Cada instruccin produce una operacin gef y una operacin put\ con instrucciones entremedias. Por tanto, entre el instante de obtener el valor y el instante de almacenar el valer corregido, otra tarea podra modificar el campo, as que las operaciones no son atmicas.

Si aplicamos ciegamente la idea de ia atomicidad, podemos ver que getValue() en el siguiente programa se ajusta a la descripcin: //: concurrency/Atomicity Test.java impert java.til.concurrent. *; puble ciass AtomicityTest iraplements Runnable { private int i = 0; public int getValue) { return i; } private synchronized void evenlncrement () { i++; i+*-; ) public void run() { while(true) evenlncrement{);

public void run() { while(true) {

2024 Piensa en Java } public static void irain (Stnng [] args) { ExecutorService exec = Executors.newCachedThreadPool(); AtomicityTest at = new AtomicityTest(); exec.execute(at); while(true) { int val = at.getValue(); if(val % 2 1= 0) { System.out.println(val); System.exit(0);

} } /* Output: (Sample) 191583767 public void run() { while(true) {

2025 Piensa en Java *///:-

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 esiado intermedio inestable. Adems, puesto que i tambin es de tipo voltil, habr problemas de visibilidad. Tanto getValue() como evenlncrement() deben ser sincronizados. Slo 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 elase que produce nmeros de serie.64 Cada vez que se llama a nextSeriaINumber(), debe devolver un valor unvoco al llamante: //: concurrency/SerialNumberGenerator.java public class SerialNumberGenerator { private static volatile int serialNumber = 0; public static int nextSerialNumber() { return serialNumber+4; / / N o es seguro con las hebras

Ejemplo inspirado en el libro Effective Javant Programming Language Guide de Joshua Bloch (Addison-Wesley, 2001). p. 190. public void run() { while(true) {
64

2026 Piensa en Java } } m-.-

SerialNumberGenerator es una clase de lo ms simple que podramos imaginar, y para cualquiera que proceda de C++ o que tenga alguna otra experiencia previa similar de bajo nivel, cabra esperar que la de incremento fuera una operacin atmica, porque un incremento en C++ a menudo puede implementarse como una instruccin de microprocesador (aunque no en una forma inter-plataforma 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 no es la volatilidad, el problema real es que nextSeriaLNumber() accede a un valor compartido y mutable sin sincronizacin.

El campo serialNumber es voltil porque es posible que cada hebra tenga una pila local y mantenga en ella copias de algunas 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 almacenados 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 hccho de que la de incremento no es una operacin atmica.

public void run() { while(true) {

2027 Piensa en Java Bsicamente, debemos definir 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 SeriaINumberGenerator, 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 valores sobreescritos ser mnima. Los mtodos add() y contains() estn sincronizados para impedir las colisiones entre hebras: //: concurrency/SerialNumberChecker.java // Determinadas operaciones que pueden parecer seguras no lo son // cuando hay hebras presentes. // {Args: 4} import java.util.concurrent.*; // Reutiliza el almacenamiento para no agotar la memoria: class CircularSet { private int[] array; private int Ten; private int index = 0; public CircularSet(int size) ( array = new int[size]; len = size; // Inicializar con un valor no producido por // el generador SerialNumberGenerator: for(int i = 0; i < size; i++) array[i] = *1;

public void run() { while(true) {

2028 Piensa en Java } public synchronized void add(int i) { array[index] = i; // Implementar circularmente el index y reescribir los elementos // antiguos: index = ++index % len; i public synchronized boolean contains(int val) { for(int i = 0; i < len; i++) if(array[i] == val) return trae; retum false;

} public class SerialNumberChecker { private static final int SIZE = 10; private static CircularSet seriis = new CircularSet (1000) private static ExecutorService exec = Executors.newCachedThreadPool(); stacic class SerialChecker implements Runnable { public void run() { while(true) public void run() { while(true) {

2029 Piensa en Java {int serial = SeriaINumberGenerator .nextSerialNumber(); if(seriis.contains(s erial)) { System.out.println("Duplcate: System.exit(0); + serial);

} serials.add(serial);

public class AtomicIntegerTest implements Runnable {

2030 Piensa en Java } public static void main(String[] args) throws Excepcin { or(int i = 0; i < SIZE; i++) exec.execute{new SerialChecker()); // Detenerse despus de n segundos si hay un argumento: if(args.length >0) { TimeUnit.SECONDS.sleep(new Integer(args[0])); System.out .println("No duplicates detected*); System.exit(0);

} } /* Output: (Satnple) Duplicate: 8468656 *///:-

SerialNumberChecker contiene un conjunto esttico CircularSet que almacena todos los nmeros de serie que se han producido y una clase SerialChecker anidada que garantiza public class AtomicIntegerTest implements Runnable {

2031 Piensa en Java que los nmeros de serie sean unvocos. Creando mltiples tareas para contender con el acceso a los nmeros de serie, descubriremos que las tareas llegan a obtener un nmero de serie duplicado, si dejamos que el programa se ejecute el tiempo suficiente. Para resolver el problema, aada la palabra clave synchronized 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, mientras que ste se encuentre en un estado intermedio inestable. Realizar suposiciones acerca de este tema resulta muy peligroso. Lo mejor que puede hacerse es aplicar la Regla de Brian de la sincronizacin.

Ejercicio 12: (3) Corrija AtomicityTestjava utilizando la palabra clave synchronized. Puede demostrar que ahora es

correcto?

public class AtomicIntegerTest implements Runnable {

2032 Piensa en Java Ejercicio 13: (1) Corrija SerialNuinberChecker.java utilizando la palabra clave synchronized. Puede demostrar que

ahora es correcto? Clases atmicas

Java SE5 introduce clases de variables atmicas especiales como Aomiclnteger. AtomicLong, AtomicReference, etc., que proporcionan una operacin condicional de actualizacin atmica de la forma: booiean compareAndSet(expectedValue, updateValue);

Este ejemplo de mtodo comparara y actualizara en una operacin atmica una determinada variable, proporcionndose como argumentos el valor esperado y el valor de actualizacin. Este tipo de mtodo se utiliza para realizar una optimizacin avanzada, aprovechando la atomicidad del nivel de la mquina disponible en algunos procesadores moderaos, por lo que generalmente no tenemos por qu preocupamos de utilizarlos. En ocasiones, pueden resultar tiles para los programas normales, pero, de nuevo, slo cuando se est intentando realizar una optimizacin. Por ejemplo, podemos escribir de nuevo AtomicityTestjava para utilizar Atomiclnteger: //: cor.currency/AtomicIntegerTes t. java import j ava.til.concurrent.*; import public class AtomicIntegerTest implements Runnable {

2033 Piensa en Java java.til.concurrent.atomic.* ; import java.util.*; private AcomicTnteger i = new Atomiclnteger(0); public int getValue() { return i.get(); } private void evenlncrement() { i.addAndGet(2); } public void runO { while(true) evenlncrement();

} public static void main (String [] args) { new Timer () .schedule (new TimerTaskO { public void run() { System.err.println"Aborting"); System.exit(0);

} }f 5000); // Terminar despus de 5 segundos ExecutorService exec = Executors.newCachedThreadPool() ; AtomicIntegerTest ait = new AtomicIntegerTest(); exec.execute(ait); while(true) { public class AtomicIntegerTest implements Runnable {

2034 Piensa en Java int val = ait.getValue() ; if(val % 2 ! = 0) { System.out.println(val)? System.exit(0);

} ///:-

public class AtomicIntegerTest implements Runnable {

2035 Piensa en Java Aqu hemos eliminado la palabra clave synchronized utilizando en su lugar Atomiclnteger. Puesto que el programa no falla, hemos aadido un objeto Timer para abortar automticamente la ejecucin despus de 5 segundos.

He aqu MutcxEvenGenerator.java reescrito para utilizar Atomiclntcger: //: concurrency/AtomicBvenGenerator.j ava // i.as clases atmicas son tiles en ocasiones en los programas normales. // {RunByHand} import j ava.uti1.concurrent.atomic.*; public class AtomicEvenGenerator extends IntGenerator ( private Atomiclnteger currentEvenValue = new Atomiclnteger(0); public int next() { return currentEvenValue.addAndGet(2);

} public static vod main(String[] args) { EvenChecker.test(new AtomicEvenGenerator());

} public class AtomicIntegerTest implements Runnable {

2036 Piensa en Java } ///:~

De nuevo, todas las dems formas de sincronizacin se han eliminado empleando Atomiclnteger.

Es necesario recalcar que las clases Atomic fueron diseadas para construir 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 podamos garantizar que no van a surgir otros posibles problemas. Generalmente, es ms seguro utilizar los bloqueos (bien la palabra clave synchronized o bien objetos Lock explcitos).

Ejercicio 14: (4) Demuestre que java.util.Timer se puede escalar para utilizar nmeros de gran magnitud creando un

programa que genere muchos objetos Timer que realicen alguna tarea simple cuando se produzca el fin de temporizacin. Secciones crticas public class AtomicIntegerTest implements Runnable {

2037 Piensa en Java 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 lizando la palabra clave synchronized. Aqu, synchronized se utiliza para especificar el objeto cuyo bloquee se est empleando para sincronizar el cdigo incluido en la sincronizacin: synchronized{syncObj ect) // En cada momento, slo una tarea puede // acceder a este cdigo {

Esto se denomina tambin bloqueo sincronizado; lo 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 tareas accedan a un 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 public class AtomicIntegerTest implements Runnable {

2038 Piensa en Java multihebra si se la controla y protege mediante otra clase: //: concurrency/Critica]Section.java // Sincronizacin completos. Tambin // ilustra mediante la de bloque de en una lugar clase de no mtodos protegida

proteccin

// otra clase protegida. package concurrency; import j ava.til.concurrent.*; import java.til.concurrent.atonde.*; import java.til.*; class Pair { // No protegida frente a hebras private int x, y; public Pair(int x, int y) { this.x * X; this.y = y;

} public Pair() { this(0, 0); } public int getXO { retum x; } public int getY() { retum y; } public void incrementX() { x+ +; } public void incrementYO { y++; } public String toStringO { retum "x: " + x + ", y: " y;

public class AtomicIntegerTest implements Runnable {

2039 Piensa en Java } public class PairValuesNotEqualExce pLion extenas RuntimeException { public PairValuesNotEqualExceptionO { super("pair vales not equal: " + Pair.this);

} // Invariante arbitrario -- ambas variables deben ser iguales: public void checkStatef) { if(x 1* y) throw new PairValuesNotEqualExceptionO;

public class AtomicIntegerTest implements Runnable {

2040 Piensa en Java } // Proteger un objeto Pair dentro de una clase protegida frente a hebras: abstract class PairManager { Atomiclnteger checkCounter new Atomiclnteger(0); protected Pair p = new Pair(); private List<Pair> storage = Collections.synchronizedList(new ArrayList<Pair>()); public synchronized Pair getPairO {// Hacer una copia para que el original est seguro: return new Pair(p.getx() , p.getYO);

} // Asumimos que sta es una operacin de larga duracin protected void store(Pair p) { storage.add(p); cry { TimeUnit.MILLISECONDS.sleep(50) ; } catch(InterruptedExceotion ignore) {}

} public abstract void incremento ; // Sincronizar el mtodo completo: class PairManagerl extends PairManager { public synchronized void incremento { p.incrementX(); p.incrementY 0; store(getPair()); public class AtomicIntegerTest implements Runnable {

2041 Piensa en Java }

} // Utilizar una seccin crtica: class PairManager2 extends PairManager { public void incremento { Pair temp; synchronized(this) { p.incrementX(); p.incrementY(); temp = getPair();

} store(temp);

} public class AtomicIntegerTest implements Runnable {

2042 Piensa en Java class PairManipulator implements Runnable { private PairManager pm; public PairManipulator(PairManager pm) { this.pra = pm;

} public void run() { while(true) pm.increment();

} public String toStringO { return "Pair: nM + pm.getPair() + " checkCounter <= " + pm.checkCounter .get 0 ;

public class AtomicIntegerTest implements Runnable {

2043 Piensa en Java } class PairChecker implements Runnable { private PairManager pm; public PairChecker(PairManager pm) { this.om = pm;

) public void run() { while(true) { pm.checkCounter.incrementAndGet(); pm.getPair().checkState() ;}

public class CriticalSection { // Probar las dos tcnicas: static void te9tApproaches(PairManager pmanl, PairManager pman2) { ExecutorService exec = Executors.newCachedThreadPool(); PairManipulator pml = new PairManipulator(pman l), pm2 = new PairManipulator(pman 2); PairChecker pcheckl = new PairChecker(pmanl), pcheck2 = new public class AtomicIntegerTest implements Runnable {

2044 Piensa en Java PairChecker(pman2); exec.execute(pml); exec. execute (pm2) ,exec.execute(pcheckl); exec.execute(pcheck2); try { TimeUnit.MILLIS3C0NDS.sleep(500); } catch(InterrupledException e) { System.out .println("Sleep interrupted'*) ;

} System, out .println ("pml: n + pml + H\npm2: " + pm2) ,System.exit(0);

} public static void main(String[] args) { PairManager pmanl = new PairManager! (), pman2 * new PairManager2 () ; testApproach es(pmanl, pman2);

public class AtomicIntegerTest implements Runnable {

2045 Piensa en Java } } /* Output: (Sample) pml: Pair: x: checkCounter pm2: Pair: x: checkCounter *///:15, y: 15 * 272565 16, y: 16 = 3956974

Como se indica. Pair no es seguro de caa a las hebras, poique su invariante (o poique su invariante es arbitrario) requiere que arabas variables mantengan los mismos valores. Adems, como hemos visto anteriormente en el captulo, las operaciones de incremento no son seguras respecto a las hebras, y como ninguno de los mtodos est sincronizado, no podemos confiar en que un objeto Pair permanezca 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 elase PairManager, que almacena un objeto Pair y controla todo el acceso al mismo. Observe que los nicos mtodos pblicos son getPair(), que est sincronizado y el mtodo abstracto increment( ) La sincronizacin de incrcmcDt( ) 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 abstractos definidos en las clases derivadas se denomina public class AtomicIntegerTest implements Runnable {

2046 Piensa en Java Mtodo de plantillas en la jerga de los Patrones de diseo}5 Los patrones de diseo nos permiten encapsular los cambios que nuestro cdigo pueda sufrir; aqu, la parte que cambia es el mtodo increnient(). En PairManagerl todo el mtodo increment() est sincronizado, mientras que en PairManager2 slo se sincroniza parte del mtodo incremente) utilizando un bloque synchronized. Observe que la palabra clave synchronized no forma parte de la signatura del mtodo y que, por tanto, puede ser aadida al sustituirlo en una clase derivada.

El mtodo store() aade un objeto Pair a un contenedor Array List 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 synchronized en PairManager2.

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 prueba, PairChecker incrementa checkCounter cada vez que tiene xito. En ntain(), se crean dos objetos PairManipulator 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 genera) ver que PairManager 1.increment() no permite a PairChecker unos accesos tan frecuentes como a PairManager2.incrcmcnt( ), que tiene el bloque synchronized y goza, por tanto, de un mayor tiempo con los bloqueos liberados. sta es, normalmente, la razn public class AtomicIntegerTest implements Runnable {

2047 Piensa en Java para utilizar un bloque synchronized en lugar de la sincronizacin del mtodo completo: para permitir que las otras tarcas puedan acceder ms frecuentemente (siempre y cuando sea seguro hacerlo).

Tambin se pueden usar objetos Lock explcitos para crear secciones crticas: //: concurrency/ExplicitCriticalSection.java // Uso de objetos Lock explcitos para crear secciones crticas. package concurrency; import java.til.concurrent.locks.*; // Sincronizar el mtodo completo: class ExplicitPairManager! extends PairManager { private Lock lock = new ReentrantLock(}; public synchronized void incremento { lock.lock(); try { p. in cr em en tx () ,* p. in cr em en tY () ; st or e (g et Pa ir 0 >; public class AtomicIntegerTest implements Runnable {

2048 Piensa en Java } fi na ll y ( lo ck . un lo ck ()

} // Usar una seccin crtica: class ExplicitPairManager2 extends PairManager { private Lock lock = new ReentrantLock(); public void increment() { P public class AtomicIntegerTest implements Runnable {

2049 Piensa en Java a i r t e m p ; l o c k . l o c k ( ) ; t r y { p. in cr em en tx () ; p. in cr em en tY () ; temp a getPairO; } finally { lock.unlock() ;

public class AtomicIntegerTest implements Runnable {

2050 Piensa en Java } store(temp);

} public class ExplicitCriticalSection { public static void main(String[] args) throws Exception { PairManager pmanl = pman2 = newExplicitPairManagerl newExplicitPairManager2 0 , O ;

CriticalSection.testApproaches(omanl, pman2);

} } /* Output: (Sample) pml: Pair: x: 174035 pm2: Pair: x: 26C8588 15,y: 16,y: 15 16 checkCounter checkCounter = =

public class AtomicIntegerTest implements Runnable {

2051 Piensa en Java *///:-

Este programa reutiliza la mayor pane de CrlticalSection.java y crea nuevos tipos de PairManager que utilizan objetos Lock explcitos. ExplicitPairMauaer2 muestra la creacin de una seccin crtica 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 apropiado para este propsito es el objeto actual para el que est siendo invocado el mtodo: synchronized(this), que es la tcnica usada en PairManager2. De esta forma, 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, sincronizarse 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 relevantes 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: //: concurrency/SyncObje ct.java // public class AtomicIntegerTest implements Runnable {

2052 Piensa en Java Sincronizacin con otro objeto, import static net.mindview.til.Pr int.*; class DualSynch { prvate Object syncObject = new ObjectO; public synchronized void f() { for(int i = 0; i < 5; i++) { print("f(}"); Thread.yield();

} public void g() { synchronized(syncObject) { for(int i = 0; i < 5; 1++) { print {"g ()") ; i Thread.yield();

public class AtomicIntegerTest implements Runnable {

2053 Piensa en Java }

} public claco SyncObject { public static void main(String[] args) { final DualSynch ds = new DualSynchO; new Thread() { public void run() { ds.f () ;

} } ,start () ; ds.gO ;

public class AtomicIntegerTest implements Runnable {

2054 Piensa en Java } } /* Output:(Sample) g < > f { ) g O f 0 g ( ) f o g ( ) f o g ( ) f o

V//:public class AtomicIntegerTest implements Runnable {

2055 Piensa en Java DualSync.f( ) se sincroniza con this (sincronizando el mtodo completo), y g() tiene un bloque synchronized que se sincroniza con syncObject. As, las dos sincronizaciones son independientes. Esto se ilustra 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, asi 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: explcitos.

(1)Modifique elEjercicio

15

para

usar objetos Lock

Almacenamiento local de las hebras

public class AtomicIntegerTest implements Runnable {

2056 Piensa en Java Una segunda forma de evitar que las tareas colisionen o accedan a recursos compartidos consiste en eliminar Ja comparticin de variables. El almacenamiento local de las hebras es un mecanismo que crea automticamente un espacio de almacenamiento distinto para la misma variable, para cada hebra diferente que est utilizando un objeto. As, si tenemos cinco hebras utilizando un objeto con una variable x, el almacenamiento local de las hebras genera cinco espacios diferentes de almacenamiento para x. Bsicamente, este mecanismo nos permite asociar un cierto estado con cada hebra.

La creacin y gestin del almacenamiento local de la hebra son realizados por la clase java.lang.ThreadLoca). como puede verse aqu: //: concurrency/ThreaaLocalVar iableHolder.j ava // Asignacin automtica de su propio espacio // de almacenamiento a cada hebra, import java.til.concurrent.*; impert java.til.*; class Accessor iraplements Runnable { private final int id; public Accessor (int ion) { id = idn; } public void run() { while(1Thread.currentThread().islnterrupted()) { ThreadLocalVariableIolder. increment () ; System.out.printl n(this); Thread.yieldO ;

public class AtomicIntegerTest implements Runnable {

2057 Piensa en Java } public String tcStringO { return + id + ": ,r + ThreadLocalVariableHolder.get();

} public class ThreadLocalVariableHolder { private static ThreadLocal<Integer> valu = new ThreadLocaI<Integer>() { private Random rand = new Random(47); prctected synchronized Integer initialValue() { return rand.nextlnt(10000) ;

} public class AtomicIntegerTest implements Runnable {

2058 Piensa en Java }; public static void incremento { valu.set(valu.getO + 1);

) public static int get O { return value.getO; } public static void main(String[j args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool() ; or(int i = 0; i < 5; i++) exec.execute(new Accessor(i)); TimeUnit.SECONDS.sleep(3); un tiempo exec.shutdownNow(); terminarn // Ejecutar durante

// Todos los objetos Accessors

} } /* Output: (Sample) # 0 : 9 2 5 9 # public class AtomicIntegerTest implements Runnable {

2059 Piensa en Java 1 : 5 5 6 # 2 : 6 6 9 4 # 3 : 1 8 6 2 # 4 : 9 6 2 # 0 : 9 2 6 0 # 1 : 5 5 7 # 2 : public class AtomicIntegerTest implements Runnable {

2060 Piensa en Java 6 6 9 5 # 3 : 1 8 6 3 I f 4 : 9 6 3

*///:-

Los objetos ThreadLocal normalmente se almacenan como campos estticos. Cuando creamos el objeto ThreadLocal, slo podemos acceder al contenido del objeto con los mtodos get() y sel(). El mtodo ge<() devuelve una copia del objeto asociado con dicha hebra, mientras que sct() inserta su argumento en el objeto almacenado para dicha hebra, devolviendo el objeto anterior que estuviera almacenado. Los mtodos incremcnl() y gct() ilustran este mecanismo en ThreadLocal- VariableHolder. Observe que increment( ) y get() no estn sincronizados, porque ThreadLocal garantiza que no se produzca ninguna condicin de carrera.

public class AtomicIntegerTest implements Runnable {

2061 Piensa en Java Cuando ejecute este programa, podr ver que a cada hebra individual se le asigna su propio espacio de almacenamiento, ya que cada una de ellas mantiene su propio valor de recuento, an cuando slo existe un objeto ThreadLocalV'ariableHolder. Terminacin de tareas

En algunos de los ejemplos anteriores, los mtodos canccl() e isCanceIed() se colocan en una clase que es visible para todas las tareas. Las tareas comprueban isCancc!cd() para determinar cundo deben finalizar. Esta solucin resulta bastante razonable. Sin embargo, en algunas situaciones, la tarea debe terminarse de manera ms abrupta. En esta seccin, veremos 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 adicional de la comparticin de recursos. El jardn ornamental

En esta simulacin, el comit director del jardn quiere saber cuntas personas entran el jardn cada dia a travs de las distintas 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. //: concurrency/Ornam public class AtomicIntegerTest implements Runnable {

2062 Piensa en Java entalGarden.java irnport java.til.concurr ent.*; import java.til.*; import static net.mindview.til.Print.*; class Count { private int count = 0; private Random rand = new Random(47); // Eliminar la palabra clave synchronized para ver cmo falla el recuento: public synchronized int incremento { int temp = count ; if(rand.nextRooleanO) // Seguir el control la mitad del tiempo Thread.yield(); return (count = ++temp);

) public synchronized int value() { return count; }

} class Entrance implements Runnable { private static Count count = new Count(); private static List<Entrance> entrances = new ArrayList<Entrance>(); private int number = 0; // No necesita sincronizacin para leer: private final int id; public class AtomicIntegerTest implements Runnable {

2063 Piensa en Java private static volatile boolean canceled = false; // Operacin atmica sobre un campo voltil: public static void cancelO { canceled = true; } public Bntranee(int id) { this.id = id; // Mantener esta tarea en una lista. Tambin impide // que se aplique la depuracin de memoria a las tareas muertas: entrances.add(this);

} public void run() { while(!canceled) { synchronized(this) { + -number;

} print(this + n Total: " + count.increment()) ; try { TimeUnit.MILLISECONDS.sleep(100); } catch(InterruptedException e) { nrint("sleep interrupted");

public class AtomicIntegerTest implements Runnable {

2064 Piensa en Java }

} print ("Stopping " + this);

} public synchronized int getValue() { return number; } public String toStringO { return "Sntranr.s H + id + ": u -4- gpr.Va 1 AIP () ;

} public static int getTotalCount() { return count.value();

} public static int sumEntrances() { int sum = 0; public class AtomicIntegerTest implements Runnable {

2065 Piensa en Java 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 = 0; i < 5; i+ + ) exec.execute(new Entrance(i)); // Ejecutar durante un tiempo, luego detenerse y recopilar los datos: TimeUnit.SECONDS.sleep(3); Entrance.cancel(); exec.shutdown() ;if (!exec.awaitTermination (250, TiraeUn.it .MILLISECONDS)) print ("Some tasks were not terminated!11); print("Total: " + Entrance.getTotalCount()); print("Sura of Entrances: " + Entrance.sumEntranees());

public class AtomicIntegerTest implements Runnable {

2066 Piensa en Java

Un nico objeto Count mantiene la cuenta maestra de los visitantes del jardn y se almacena como campo esttico en la clase Entrance. Counr.incrcmcnt( ) y Count. value( ) estn sincronizados para controlar el acceso al campo count. El mtodo increment( ) utiliza un objeto Random para ejecutar el mtodo yield( ) aproximadamente la mitad de las veces, entre la operacin de extraer count para almacenarlo en temp y la de incrementar y almacenar de nuevo teiup en count. Si desactivamos mediante un comentario la palabra clave synchronized en ncrement(), el programa deja de funcionar porque habr mltiples tareas acccdicndo a count 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 local number que contiene el nmero 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. Entrancc.run() simplemente incrementa number y el objeto count y pasa a dormir durante 100 milisegundos.

Puesto que Entrance.canceled es un indicador booleano voltil, que slo se lee y se configura (y que nunca es ledo en combinacin con otros campos), sera posible no public class AtomicIntegerTest implements Runnable {

2067 Piensa en Java sincronizar el acceso al mismo. Pero, siempre que tenga alguna duda acerca de si sincronizar algo o no, lo mejor es utilizar synchronized.

Este programa hace un esfuerzo especia] para finalizar 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 multihebra; por otro lado, pretendemos con ello ilustrar el valor de interrupt(), del que hablaremos en breve.

Despus de 3 segundos, maln() enva el mensaje esttico cancel() a Entrance, de modo que llama a shutdown() para el objeto exec, y luego invoca a a\vaitTerniination() sobre exec. KxecutorService.awaitTermination() espera a que cada tarea se complete y si todo se completa antes de que finalice la temporizacin devuelve truc, 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 Li$t<Entrance> denominado entrances. Por lanto. sumEntrances( ) seguir funcionando con objetos Entrance vlidos.

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 count. Mientras utilicemos el mutex para sincronizar el acceso a Count. las cosas sucedern correctamente. Recuerde que Count.incrcmcnt( ) exagera la probabilidad de fallos, utilizando temp y yield(). En los public class AtomicIntegerTest implements Runnable {

2068 Piensa en Java problemas reales de los programas multihebra, la posibilidad de fallo puede ser estadsticamente pequea, asi 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 lo 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 incluye 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 oportunidad de salir del mismo, comprobando el indicador cancelled. Sin embargo, s!eep( ) 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 tarca bloqueada. Estados de las hebras

Una hebra puede estar en uno de cuatro estados:

public class AtomicIntegerTest implements Runnable {

2069 Piensa en Java


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 ejecutable 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 asi lo decide. Por tanto, la hebra no est ni muerta ni bloqueada.

3.

Bloqueada: la hebra puede ejecutarse pero hay algo que lo 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 nc puede recibir 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 public class AtomicIntegerTest implements Runnable {

2070 Piensa en Java Una tarea puede llegar a estar bloqueada por las siguientes 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 nolifyAll() (o los mensajes equivalentes signai() o signalAll() para las herramientas de la biblioteca de Java SE5 java.utl.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 porque ya ha sido adquirido por otra tarea.

public class AtomicIntegerTest implements Runnable {

2071 Piensa en Java En los programas antiguos, puede que tambin vea que se usan los mtodos suspend() y resume( ) para bloquear y desbloquear las hebras, pero estos mtodos son desaconsejados en los programas Java modernos (porque son proclives a los intcrbloqueos), as que uo los examinaremos en el libro. Tambin se desaconseja el mtodo stop(), porque no libera los bloqueos que la hebra haya adquirido, y si los objetos estn en un estado incoherente (daados), otras tareas podran consultarlos y modificarlos en dicho estado. Los problemas resultantes pueden ser muy sutiles y difciles de detectar.

El problema que ahora debemos examinar es el siguiente: en ocasiones, queremos terminal- 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 comprobar un valor de estado y decidir terminar por cuenta propia, tenemos 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 punto donde se compruebe un indicador de cancelacin, o algn otro lugar en el que el programador 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 interrupcin65 (esto significa que estamos caminando sobre el filo que separa el uso adecuado e inadecuado de las excepciones, porque quiere decir que a menudo se las emplea para control del flujo del ejecucin). Para volver a un estado aceptable 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 Sin embargo, las excepciones nunca se generan asincronamente. Por tanto, no bay 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 try-finaUy al utilizar objetos mutex (en lugar de la palabra clave syncbronlzed), esos mutex sern liberados automticamente si se genera una excepcin. public class AtomicIntegerTest implements Runnable {
65

2072 Piensa en Java adecuadamente las cosas.

Para poder terminar 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 interrupcin InterruptedException si ya est bloqueada o si se trata de realizar una operacin de bloqueo. El indicador de estado interrumpido ser reinicializado cuando se genere la excepcin o si la tarea llama a Thread.iiiterrupted(). Como puede ver, Thrcad.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 concurrcnt parece evitar la manipulacin directa de objetos Thread y trata en su lugar de realizar lodas las tareas 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 normalmente querremos terminar de una sola vez todas las tareas para un 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 llamando 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 Fnture 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.

public class AtomicIntegerTest implements Runnable {

2073 Piensa en Java He aqu un ejemplo que muestra los fundamentos bsicos de utilizacin de interrupt( ) empleando ejecutores: //: concurrency/Interrup tng.j ava // Interrupcin de una hebra bloqueada, import java.util.concurrent .*; import j ava.io.*; import static net.mindview.util.Print.*; class SleepBlocked implemento Runnable { public void run() { try { TimeUnit.SECCNDS.sleep(100); } catch(Interrup tedException e) ( print("Inter rupredExceptio n");

} print("Exiting SleeoBlocked.run());

public class AtomicIntegerTest implements Runnable {

2074 Piensa en Java } class XOBlocked implements Runnable { privare InputStream in; public IOBlocked(InputStream is) { in = is; } public void run() { try { print("Waiting for readi):"); in.read() ; } catch(TOException e) { if(Thread.currentThread().islnterr upted()) { print("Interrupted from blocked I/O"); } else { throw new RuntineException(e) ;

} print("Exiting IOBlocked.run{)");

} public class AtomicIntegerTest implements Runnable {

2075 Piensa en Java } class SynchronizedBlocked implements Runnable { public synchronized void f() { while(true) // Nunca libera el bloqueo Thread.yield();

} public SynchronizedBlocked() { new Thread() { public void run() { (); // Bloqueo adquirido por esta hebra

} }.start();

} public void runO ( print("Trying to call f ()"); f 0 ; print("Exiting SynchronizedBlocked.run() "); public class AtomicIntegerTest implements Runnable {

2076 Piensa en Java )

} public class Interrupting { private static ExecutorService exec = Executors.newCachedThreadPool(); static void test(Runnable r) throws InterruptedException{ Future<?> f = exec.submit(r) ; TimeUni t.MILLISECONDS.sleep(100); print ("Interrupting " -? r. get Class () .getName ()) ; f.cancel(true); // Interrumpe si se est ejecutando print("Interrupt sent to " + r.getClass().getName());

} public static void main(String[] args) throws Exception { test(new SleepBlocked()); test(new IOBlccked(System.in)); test(new SynchronizedBlocked()) ; TimeUnit.SECONDS.sleep(3); print("Aborting with System.exit(0)"); System.exit(0); // ... puesto que las 2 ltimas interrupciones fallaron }} /* Output: (95% match)

Interrupting SleepBlocked InterruptedEx public class AtomicIntegerTest implements Runnable {

2077 Piensa en Java ception Exiting SleepBlocked. run() Interrupt sent to SleepBlocked Waiting for read(): Interrupting IOBiocked Interrupt sent to IOBiocked Trying to call f () Interrupting SynchronizedBlocked Interrupt sent to SynchronizedBlocked Aborting with System.exit(0)

*///:-

Cada tarca representa un tipo diferente de bloqueo. SleepBlock es un ejemplo de bloqueo interrumpible, mientras que IOBiocked y SynchronizedBlocked son bloqueos no interrumpibles.66 El programa demuestra que las operaciones de E/S y de espera sobre un bloqueo synchronized no sean interrumpibles, cosa que tambin puede deducirse 66' Algunas versiones del JDK tambin proporcionaban soporte para la instruccin InterruptedlOException. Sin embargo, este soporte silo estaba parcialmente implementado, y nicamente en algunas plataformas. Si se genera esta excepcin hace que los objetos de H/S sean inutilizables. Resulta poco probable que las futuras versiones sigan proporcionando soporte para esta excepcin. public class AtomicIntegerTest implements Runnable {

2078 Piensa en Java examinando el cdigo: 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 segunda. Sin embargo, para ilustrar SynchronizedBlocked, debemos primero adquirir el bloqueo. Esto se lleva a cabo en el constructor 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 SynchronizcdBlock, porque una misma hebra s que puede adquirir mltiples veces un bloqueo sobre un objeto). Dado que f() nunca vuelve, ese bloqueo nunca es liberado. SvnchronizedBIock.ruu() 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 bloqueo sincronizado o que est tratando de efectuar una operacin de E/S. Esto es un poco desconcertante, especialmente si estamos creando una tarea que realice operaciones de E/S, porque significa que la E/S tiene el potencial de bloquear el programa 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 subyacente que hace que la tarea est bloqueada: public class AtomicIntegerTest implements Runnable {

2079 Piensa en Java //: concurrency/CloseRe source.j ava // Interrupcin de una tarea bloqueada // cerrando el recurso subyacente. // {RunByHand} import j ava.net.*; import j ava.ut i1.concurrent.*; import java.io.*; import static net .raindview.util.Print .* ; public class CloseResource { public static void main(String args) throws Exception { ExecutorServjce exec = Executors.newCachedThreadPool{); ServerSocket server = new ServerSocket(8080) ; InputStream socketlnput = new Socket("localhost", 8080).getInputStream{); exec.execute(new IOBiocked(socketlnput)); exec.execute(new IOBiocked(System.in) ) ; TimeUni t.MILLISECONDS.s leep(100); print("Shutting down all threads); exec.shutdownNcw (); TimeUnit.SKCONDS *3leep(1); print{"Closing " + socketlnput.getClass{).getName ()); socketInput.close{); // Libera la hebra bloqueada TimeUnit.SECONDS.sleep(1); print("Closing " + System.in.getClass().getName()) ; System.in.close(); // Libera la hebra blocueada public class AtomicIntegerTest implements Runnable {

2080 Piensa en Java } } /* Output:(85% match) Waiting for read(): Waiting for readO : Shutting down all threads Closing java.net.SocketInp utStream Interrupted from blocked I/O Exiting IOBlocked.run() Closing java.io.Buffere dlnputStream Exiting IOBlocked.run()

*///:-

Despus de invocar shutdownNow(), los retardos utilizados antes de llamar a close() para los dos flujos de datos de entrada permiten resaltar que las tareas se desbloquean una vez que el recurso subyacente se ha cenado. Resulta interesante observar que la interrupcin aparece cuando estamos cerrando un objeto Socket pero no al cerrar System.in.

public class AtomicIntegerTest implements Runnable {

2081 Piensa en Java 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: //: concurrency/NIOInterruption.java // Interrupcin de un canal NIO bloqueado. import j ava.net.*; import java.nio.*; import java.nio.channels.*; import j ava.util.concurrent.*; import java.io.*; import static net.mindview.util.Print.*; class NIC3locked implements Runnable { private final SocketChannel sc; public NIOBlocked(SocketChannel sc) { this.se = sc; } public void run{) { try { print ("Waiting for reaa() in " this); sc.read(Byte3uffer. allocate(1)); } catch(ClosedBylnte rruptException e) { print("ClosedByl nterruptException" ); } catch(Asynchronous CloseException e) { print("Asynchron ousCloseException" ); } catch(lOException e) { throw new RuntimeException(e);

public class AtomicIntegerTest implements Runnable {

2082 Piensa en Java } print("Exiting NIOBlocked.run() " + this);

) public class NIOInterrupLion { public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool() ; ServerSocket server = new ServerSocket(8080) ; InetSocketAddress isa = new InetSocketAddress("localhost", 8080); SocketChannel scl = SocketChannel.open(isa); SocketChannel sc2 - SocketChannel.open(j.sa) ; Future<?> t = exec,submit(new NIOBlocked(sel)); exec.execute(new NIOBlocked(sc2)); exec.shutdown(}; TimeUn.it. SECONDS. sleep (1) ; // Producir una interrupcin mediante cancel: f.cancel(true); TimeUnit.SECONDS.sleep(1) ; public class AtomicIntegerTest implements Runnable {

2083 Piensa en Java // Liberar el bloqueo cerrando un canal: sc2.close();

} } /* Output: (Sample) Waiting for read() in NI0Blocked@7a84e4 Waiting for read() in NIOBlocked@l5c7850 ClosedByInterruptEx cept ion Exiting NIOBlocked.run() NIOBlocked@15c7850 AsynchronousCloseExcep t ion Exiting NIOBlocked.run() NIOBlocked@7a84e4 *///:.-

Como se muestra, tambin podemos cerrar el canal subyacente para liberar el bloqueo, aunque esto slo debera ser necesario en raras ocasiones. Observe que utilizando execute() para iniciar ambas tareas e invocar c.shutdownNow() permite terminar fcilmente cualquier cosa. T>a captura del objeto Future en el ejemplo anterior slo era necesaria para enviar la interrupcin a una hebra y no a la otra.67

67,K Ervin Varga me ayud en ta investigaciones relacionadas con esta seccin. public class AtomicIntegerTest implements Runnable {

2084 Piensa en Java 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: (l) Modifique CachedThreadPool.java para que todas las tareas reciban una interrupcin (con inte

rrupt )) antes de completarse. Tareas bloqueadas por un mutex

public class AtomicIntegerTest implements Runnable {

2085 Piensa en Java Como vimos en Inter rupting.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 muestra cmo puede una misma tarca adquirir mltiples veces el mismo mutex: //: concurrency/MultiLock.j ava // Una hebra puede volver a adquirir el mismo bloqueo, import static net.mindview.util.Print.*; public class MuitiLock { public synchronized void fl(int count) { if(count-- > 0) { print ("fl() calling f2() with count " count); f2(count);

} public synchronized void f2(int count) { if(count-- >0 ) { print("f2() calling fl() with count + count); f1(count);

public class AtomicIntegerTest implements Runnable {

2086 Piensa en Java }

} public static void main(String[] args) throws Exception { final MuitiLock multiLock = new MultiLockO; new Thread() { public void run() { multiLock.fl(10); } }.start(); } } /* Output:

En main(), se crea un objeto Thread para invocar fl( ); luego fl() y f2() se llaman el uno al otro hasta que el valor de count pasa a ser cero. Puesto que la tarea ya ha adquirido el bloqueo del objeto multiLock dentro de la primera llamada a fl( >, esa misma tarea lo estar volviendo a adquirir en la llamada a f2( ), 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.

Como hemos observado anteriormente al hablar de la E/S inintcmmipible. cada vez que una tarea pueda bloquearse de tal forma que no pueda ser interrumpida, existir la posibilidad de que el programa se quede bloqueado. Una de las caractersticas aadidas 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: //: concurreney/Interrupting2.j ava public class AtomicIntegerTest implements Runnable {

2087 Piensa en Java // Interrupcin de una tarea bloqueado con un bloqueo ReentrantLock. import java.util.concurrent import java.til.concurrent.locks.*; import static net.mindview.util.Print.*; class BlockedMutex { private Lock lock = new ReentrantLock{); public BlockedMutex() { // Adquirirlo directamente, para demostrar la interrupcin // de la tarea bloqueada er. un bloqueo KeentrantLock: lock.lock()7

> public void f() { try { // Esto no estar nunca disponible para una segunda tarea lock.locklnterruptibly() ; // Llamada especial print("lock acquired in f()"); } catch(InterruptedException e) { Drint ("Interrupted from lock accuisition in f{)");

public class AtomicIntegerTest implements Runnable {

2088 Piensa en Java }

} class Blocked2 implements Runnable ( BlockedMutex blocked ~ new BlockedMutex(); public void run() { print("Waiting for f() in BlockedMutex"}; blocked.f(); print("Broken out of blocked cali");

} public class Interrupting2 { public static void raain (String [] args) throws Exception { Thread t = new Thread(new Blocked2()); t.start(); Timenit.SECONDS.sleep(1) ; System.out.println"Issu public class AtomicIntegerTest implements Runnable {

2089 Piensa en Java ing t.interrupt()"); t.interrupt();

> } / * Output: Waiting for f) in BiockedMutex Issuing t.interrupt() Interrupted from lock acquisition in f() Broken out of blocked cali *///:-

La clase BiockedMutex 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 BiockedMutex), 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. nterrup(( ) permite salir de una llamada que est bloqueada por un mutex.M Comprobacin de la existencia de una interrupcin

Observe que cuando invocamos interrupt( ) para una hebra, el nico instante en que Observe que, aunque resulta poco probable, la llamada a t.interrupt() podra llegar a suceder antes que la llamada a blocked.f( ). public class AtomicIntegerTest implements Runnable {
M

2090 Piensa en Java 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 mtodos sincronizados bloqueados o la E/S inintcrrumpibles, 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 ejecute? Si slo podemos salir generando una excepcin en una llamada bloqueante, no siempre seremos capaces de abandonar el bucle run(). Por tanto, si invocamos nterrupt() para detener una tarea, la tarea necesita una segunda forma de salir en caso de que el bucle run( ) no est realizando ninguna llamada bloqueante.

Esta oportunidad se presenta gracias al estado interrumpido, que es fijado por la llamada a nter rupt( ). Comprobamos si la tarea est en estado interrumpido llamando a interrupted(). Esto no slo nos dice si se ha llamado a interi upt( ), 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 Thread.interrupted(). Si queremos comprobar de nuevo si hemos sido interrumpidos, podemos almacenar el resultado al invocar Thread.intcrrupted().

El siguiente ejemplo muestra la sintaxis tpica que se usara en el mtodo run( ) para gestionar ambas posibilidades (bloqueada y no bloqueada) cuando est activado el estado interrumpido: //: concurrency/Interruptingldiom.java // Sintaxis general para interrumpir una tarea. // (Args: 1100) import j ava.util.concurrent.*; import static net.mindview.til.Print; public class AtomicIntegerTest implements Runnable {

2091 Piensa en Java class NeedsCleanup { private final int id; public NeedsCleanup(int ident) { id = ident; print("NeedsCleanup + id);

} public void cleanupO { print ("Cleaning up " + id);

} class Blocked3 implements Runnable { private volatile double d = 0.0; public void run() { try { while(lThread.interrupted()) { // puntol NeedsCleanup nl = new NeedsCleanup(1); // Iniciar try-finally inmediatamente despus de la definicin // de nl, para garantizar una limpieza public class AtomicIntegerTest implements Runnable {

2092 Piensa en Java apropiada de nl: try { print{"Sleeping"); Timenit.SECONDS.sleep(1) ; // Punto2 NeedsCleanup n2 - new NeedsCleanup(2); // Garantizar una limpieza apropiada de n2: try { print ("Calculating") ; // Una operacin no bloqueante de larga duracin: for(int i 1; i < 2500000; i++) d = d + (Math.PI + Math.E) / d; print("Finished time-consuming operation"); ) finally { n2 .cleanup();

} } finally { nl.cleanup();

} public class AtomicIntegerTest implements Runnable {

2093 Piensa en Java print ("Exiting via whileO test"); } catch(InterruptedException e) { Drint("Exiting via InterruptedException"); }'

} public class Interruptingldiom { public static void main(String[] args) throws Exception { if(args.length != 1) { print("usage: java Interruptingldiom delay-in-mS"); System.exit(1);

> Thread t = new Thread(new Blocked3()); t.start(); TimeUnit.MILLISECONDS.sleep(new Integer(args [01)); t.interrupt();

} public class AtomicIntegerTest implements Runnable {

2094 Piensa en Java } /* 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 *///: -La clase NeedsCleanup 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 NeedsCleanup creados en Blocked3.run() deben ir seguidos inmediatamente de clusulas try-flnally para garantizar que siempre se invoque el mtodo clcanup( ).

Debe proporcionar al programa un argumento de lnea de comandos que es el retardo en milisegundos antes de que se invoque intcrrupt(). Utilizando diferentes retardos, podemos salir de BJocked3.run() en diferentes puntos del bucle: en la llamada sleep() bloqueante y en el clculo matemtico no bloqueante. Como vemos, si se invoca interrupt( ) despus del comentario punto! (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 punto2 (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 bloqueante. En ese caso, slo se limpian los objetos NeedsCleanup que hayan sido creados hasta el punto en que se genera la excepcin, y tenemos la oportunidad de crear cualquier otra tarea de limpieza dentro de la clusula catch.

public class AtomicIntegerTest implements Runnable {

2095 Piensa en Java 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 seguida por clusulas tryflnally 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 destructores en Java, depende de que el programador de clientes describa las clusulas try-flnally apropiadas. Cooperacin entre tareas

Como hemos visto, cuando se utilizan hebras para ejecutar ms de una tarea simultneamente, podemos evitar que unas tareas interfieran con los recursos de otras utilizando un bloqueo (mutex) para sincronizar el comportamiento de las dos tareas. En otras palabras, si dos tareas est interfiriendo en lo que respecta a un recurso compartido (normalmente la memoria), utilizamos un mutex para permitir que slo una tarca 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 si, de modo que mltiples tareas puedan trabajar juntas para resolver un cierto problema. Ahora, la cuestin no es qu interferencias se producen entre unas tarcas y otras, sino cmo trabajar al unisono, ya que partes de un cierto problema debern ser resueltas antes de que se puedan resolver otras partes. Esto se parece bastante a la planificacin de proyectos: primero 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 fontanera 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 completen antes de poder continuar.

public class AtomicIntegerTest implements Runnable {

2096 Piensa en Java La cuestin clave cuando hay una serie de tareas cooperando es la negociacin que se produce entre dichas tareas. Para llevar 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 lia 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 biblioteca de concurrencia de Java SE5 tambin proporciona objetos Condition con mtodos a\vait( ) y signal(). Veremos los problemas que pueden surgir, junto con sus correspondientes soluciones. wait() y notifyAII()

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 notifyAU( ) (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 forma de sincronizar las actividades entre las tareas.

Es importante comprender que sleep() no libera el bloqueo del objeto cuando se lo invoca, pero tampoco lo hace yeld( ). Por otro lado, cuando una tarca entra en una llamada a wait() dentro de un mtodo, se suspende la ejecucin de esa hebra y se libera el bloqueo sobre esc objeto. Puesto que wait( ) libera el bloqueo, quiere decir que ese bloqueo podr ser adquirido por otra tarea, por lo que durante una espera con wait( ) podrn invocarse otros mtodos sincronizados en el (ahora desbloqueado) objeto. Esto resulta esencial, porque esos otros mtodos son normalmente los que provocan el cambio public class AtomicIntegerTest implements Runnable {

2097 Piensa en Java que hacen que sea interesante que se vuelva a despertar la tarea suspendida. Asi, cuando llamamos a wait( ), estamos diciendo: he hecho todo lo que puedo por ahora, as que voy a esperar aqu, pero quiero permitir que otras operaciones sincronizadas puedan tener lugar, si es que puedenn1*.

Existen dos formas de wait(). Una versin toma un argumento en milisegundos que tiene el mismo significado que slcep(): efecta una pausa durante este perodo de tiempo. Pero, a diferencia de sleep( ), con vvait(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 notifyAH(), adems de permitir que la temporizacin finalice.

La segunda forma de wait() ms comn no toma ningn argumento. Este tipo de wait() continuar indefinidamente hasta que la hebra reciba un mensaje notify() o notfyAll( ). public class AtomicIntegerTest implements Runnable {

2098 Piensa en Java Una aspecto bastante distintivo de wait(), notify() y notifyAl]() 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 iorma parte de todos los objetos. Como resultado, podemos incluir una llamada a wait() dentro de cualquier mtodo sincronizado, independientemente de si dicha clase ampla a Thread o implementa Ruiuiable. De hecho, el nico lugar en que se puede llamar a wait(), notifv( ) o notifyAIl() es dentro de un mtodo o bloque synchronized, (sleep( ) puede invocarse dentro de mtodos no sincronizados ya que no manipula el bloqueo). Si invocamos 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 IllegaLVIouitor- StateExeeption con el poco intuitivo mensaje de current thread not owner (la hebra actual no es la propietaria). Este mensaje quiere decir que la tarea que est invocando \vait( ), notify( ) o notifyAH() 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 notifyAU() a un objeto x, deberemos hacerlo dentro de un bloque sincronizado que adquiere el bloqueo para x: syn chr oni zed (x) { x .no tif yAl l() ;

} public class AtomicIntegerTest implements Runnable {

2099 Piensa en Java Examinemos un ejemplo simple. WaxOMatic.java tiene dos procesos: uno para aplicar cera a un objeto coche (representado 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 tarca de aplicacin de cera hasta que la tarea de pulido haya finalizado, antes de poner otra capa de cera. Tanto WaxOn como WaxOff utilizan el objeto Car, que emplea wait( ) y notifyAlI() para suspender y reiniciar las tareas mientras stas estn esperando a que una condicin cambie: //: concurrer.cy/waxoma tic/WaxOMatic. java // Cooperacin bsica entre tareas, package concurrency. waxoTnat ic ; import java.til.concurren t.*; import static net.mindview.util.P rint.* ; class Car { private boolean waxOn = false; public synchronized void waxedO { waxOn = true; // Listo para pulido notifyAll() ; ) public synchronized void buffedO ( waxOn ~ false; // Listo para ocra capa de cer anotifyAllO ;

} public synchronized void waitForWaxing() throws InterruptedException { while(waxOn == false) wait(); public class AtomicIntegerTest implements Runnable {

2100 Piensa en Java public synchronized void waitForBuffing() throws InterruptedException { while(waxOn == true) wait();

) class WaxOn implements Runnable { private Car car; public WaxOn(Car c) { car = c; } public void run() { try { while(! Thread.interrupted()) { printnb("Wax On ! "); TimeUnit.MILLISECONDS.sleep(200) ; car.waxed(); car.waitPorBuffing();

} } catch(InterruptedException e) { orint("Exiting via interrupt); public class AtomicIntegerTest implements Runnable {

2101 Piensa en Java } print ( "Ending Wax On task'1);

} class WaxOff implements Runnable { private Car car; public WaxOff(Car c) { car = c; } public void run() { try { while(1Thread.interrupted()) { car.waitForWaxing(); printnb("Wax Off! "); TimeUnit.MILLISECONDS.sleep(200); car.buffed();

) } catch(InterruptedException e) { print("Exiting via interrupt");

} print (n Ending Wax Off task1); public class AtomicIntegerTest implements Runnable {

2102 Piensa en Java } public class WaxOMatic { public static void main(String[3 args) throws Exception { Car car = new car(); ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new WaxOff(car)); exec.execute(new WaxOn(car)); TimeUnit.SECONDS.sleep(5); // Ejecutar durante cierto tiempo... exec.shutdownNow(); // Interrumpir todas las tarea s} /* Output: (95% match) Wax On! Wax Off! Of f 3 Wax On! Wax Off! Wax Onl Onl Wax Offl Wax On! Wax Off! Off! Wax On! Wax Off! Wax On! interrupt Ending Wax On task Exitin g via interr upt Ending Wax Off task *///:Wax On! Wax Off! Wax Wax Off! Wax On! Wax Wax On! Wax Off! Wax WaxOff! Wax On! On! Off! On! Wax Wax Wax via

Exiting

Aqu, Car tiene un nico campo booieano waxOn, que indica el estado del proceso de aplicacin-pulido.

public class AtomicIntegerTest implements Runnable {

2103 Piensa en Java En wailForWaxing(), se comprueba el indicador waxOn y, si es false, se suspende la tarea llamante invocando wait( ). Es importante que esto tenga lugar dentro un mtodo sincronizado, en el que la tarea haya adquirido el bloqueo. Cuando invocamos 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 la tarca suspendida pueda continuar), dicho bloqueo debe estar disponible para que lo adquiera alguna otra tarea. En este ejemplo, 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 notifyAU(), que despierta a la tarca que haba sido suspendida en la llamada a wait(). Para que la tarca 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.68

WaxOn.run() representa el primer paso dentro de un proceso de aplicacin de la cera al coche, as que lleva a cabo su operacin: una llamada a slecp() 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 huffcd() para el coche, cambiando el estado y llamando a notfyAll( ). WaxOff.run(), por otro lado, entra inmediatamente en waitFor\Vaxing() 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 shutdownftow() para un objeto ExecutorService, ste invoca a interrupt() para todas las tareas que est controlando.

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 notifyAll( ) (o sus equivalentes para los nuevos objetos Condltiou). La hebra simplemente se despierta aparentemente por si misma. Estos despertares espreos existen porque la implementacin de hebras POSIX, o sus equivalentes, no es siempre tan sencilla como debera ser en algunas plataformas. Permitir estas despertares espreos hace que la tarca de construir una biblioteca como pthreads sea ms fcil en esas plataformas. public class AtomicIntegerTest implements Runnable {
68

2104 Piensa en Java El ejemplo anterior resalta el hecho de que hay que rodear una llamada a >vait() con un bucle while que compruebe la condicin 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 primera 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 inters cambiara.

En el momento en que esta tarca se despierte de su llamada a wait(). es posible que alguna otra tarea haya cambiado las cosas de modo que esta tarea sea incapaz de realizar su operacin en este momento, o no le interesa realizarla. De nuevo, debera 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, necesitamos comprobar si se nos ha despertado por la razn correcta y, en caso contrario, volver a invocar wait().

public class AtomicIntegerTest implements Runnable {

2105 Piensa en Java Por tanto, resulta esencial que comprobemos nuestra condicin de inters concreta y que volvamos a wait() si dicha condicin 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 Runnable. Su mtodo run( ) debera invocar notifyAll() para la

primera tarea despus de que haya pasado un cierto nmero de segundos, de modo que la primera tarea pueda mostrar un mensaje. Pruebe las dos clases utilizando un objeto Kxecutor.

Ejercicio 22: (4) Cree un ejemplo de espera activa. Una tarea debe dormir durante un cierto tiempo y luego asignar el public class AtomicIntegerTest implements Runnable {

2106 Piensa en Java 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 asignarle de nuevo el valor false e informar del cambio a travs de la consola. Observe cunto tiempo desperdicia el programa dentro de la espera 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 notifv( )/wait() o notifvAJl( )/wait(), resulta posible perder una sefial. Suponga que TI es una hebra que notifica a T2, y que las dos hebras se implementan utilizando el siguiente enfoque (incorrecto): TI: syr.chroni zed (sharedMonitor) { <condicin de configuracin para T2> sharedMonitor.noti fy();

} T 2: while(someCondition) { // Punto 1 synchronize d(sharedM onitor) { sharedMon i public class AtomicIntegerTest implements Runnable {

2107 Piensa en Java tor.wait( );

La <condicin de configuracin para T2> es una accin destinada a impedir que T2 invoque wait(), si es que no lo ha hecho ya.

Suponga que T2 evala someCondition y encuentra que esa condicin es verdadera. En Punto 1, el planificador de hebras podra conmutar a TI. 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 ciegamente en wait(). El mensaje uotiiy() se perder y T2 esperar indefinidamente a recibir una seal que ya haba sido enviada, lo que producir un interbloqueo. public class AtomicIntegerTest implements Runnable {

2108 Piensa en Java La solucin consiste en impedir la condicin de carrera que afecta a la variable someCondition. He aqu la tcnica correcta para T2: synchronized(sharedMonitor) { while(someCondition) sharedMoni tor.wait();

Ahora, si se ejecuta primero TI, cuando el control vuelve a T2 ste podr ver que la condicin se ha modificado, y no entrar en wait( ). A la inversa, si se ejecuta primero T2, entrar en wait() y ser posteriormente despertado por TI. De este modo, no puede perderse ninguna seal. notfy() y notifyAII()

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 iioUfyAU( ) 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 uotify() en lugar de notifyAJl().

public class AtomicIntegerTest implements Runnable {

2109 Piensa en Java Utilizar notify() en lugar de notifyAil( ) 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() debemos estar seguros de que se despertar la tarea correcta. Adems, todas la tareas debern estar esperando por la misma condicin si queremos utilizar notify(), porque si hubiera tareas que estuvieran esperando por condiciones diferentes, no sabramos si se despenar la tarea correcta. Si empleamos notfy( ), slo una tarea deber aprovecharse cuando se modifique la condicin. Finalmente, estas restricciones deben cumplirse para todas las subclases posibles. Si no se puede cumplir alguna de estas reglas, es necesario emplear notifyAll() en lugar de notify().

Una de las afirmaciones confusas que a menudo se hacen a la hora de explicar el mecanismo de herramientas de Java es que nolifvAll() 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: //: concurrency/NotifyVsNotifyAll.java import java.util.concurrent import java.util.*; class Blocker { synchronized void waitingCall() { try { while(iThread.i nterrupted( )) { wait (); System.out.print(Thread.currentThread() + " ") ;

public class AtomicIntegerTest implements Runnable {

2110 Piensa en Java } } catch(interruptedException e) { // OK salir de esta forma

} synchronized void prod() { notify(); } synchronized void prodAll() { nctifyAllO; }

} class Task implements Runnable { static Blocker blocker = new Blocker (); public void run() { blocker.waitingCall(); }

public class AtomicIntegerTest implements Runnable {

2111 Piensa en Java } class Task2 implements Runnable { // Un objeto Blocker separado: static Blocker blocker = new Blocker(); public void run() { blocker.waitingCall(); }

} public class NotifyVsNotifyAll { public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool(} ; for(int i = 0; i < 5; i++) exec.execute (new TaskO); exec.execute(new Task2()); Timer timer = new Timer(); timer.scheduieAtFixedRate(new TimerTask() boolean prod * true; public void run() { if(prod) { System.out.print("\xmotifyO ); Task.blocker.prod(); prod = false; } else { System.out.print("\nnocifyAll() "); Task.blocker.prodAl1(); prod = true; {

public class AtomicIntegerTest implements Runnable {

2112 Piensa en Java }

} }, 400, 400); // Ejecutar cada 4 segundos Timenit.SECONDS.sleep (5); // Ejecutar durante un tiempo... timer.cancel{); System.out.printIn("\nTimer canceled); Timenit.MILLISECONDS.sleep(500); System.out.print("Task2.blocker.prodAll() "); Task2.blocker.prodAll(); TimeUnit.MILLISECONDS.sleep(500); System.out.printIn("\nShutting down"); exec. shutdowr.Now () ; // Interrumpir todas las tareas

} } /* Output: (Sample) notify() Thread[pool-1-thread-1,5,main] notifyAllO Thread [pool-1-thread-1, 5, main] Thread !pool-l- thread-5,5,main] Thread[pool-1thread-4,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1thread-2,5,main] notifyO Thread [pool -1-thread-1,5, main] notifyAllO Thread [pool-1-thread-1,5, main] ThreadIpool-l- thread-2,5,main] Thread[pool-lthread-3,5,main] public class AtomicIntegerTest implements Runnable {

2113 Piensa en Java Thread[pool-1-thread-4,5,main] Thread[pool-1thread-5,5,main] notify() Thread[pool-1-thread1,5,main] notifyAllO Thread [pool-1-thread-1, 5, main] ThreadIpool-1- thread-5,5,main] Thread[pool-1thread-4,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1thread-2,5,main] notifyO Thread [pool-1-thread-1, 5, main] notifyAllO Thread [pool-1-thread-1, 5, main] Thread[pool-1- thread-2,5,main] Thread[pool-lthread-3,5,main] Thread[pool-1-thread-4, 5,main] Thread[pool-1thread-5,5,main] notify() Thread[pool-1-thread1,5,main] notifyAllO Thread[pool-1-thread-1,5,main] ThreadIpool-1- thread-5,5,main] Thread[pool-1thread-4,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1thread-2,5,main] notify() Thread[pool-1-thread1,5,main] notifyAllO Thread[pool-1-thread-1,5,main] ThreadIpool-1- thread-2,5,main] Thread[pool-1thread-3,5,main] Thread[pool-1- thread-4,5,main] Thread[pool-1-thread-5,5,main] Timer canceled Task2.blocker.prodAll() Thread[pool-1-thread-6,5,main] S h u tt i n g d o w n * // /: Task y Task2 tienen, cada uno de ellos, su propio objeto Biockcr, de modo que cada objeto Task se bloquea sobre Task.blocker, y cada objeto Task2 se bloquea sobre public class AtomicIntegerTest implements Runnable {

2114 Piensa en Java Task2.blocker. En main(), se configura un objeto java.util.Timer para ejecutar su mtodo run() cada 4/10 segundos y ese mtodo run() llama alternativamente a los mtodos notify() y notifyAll() 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.bIockcr, ninguna de las llamadas notfy( ) o notfyAU() sobre Task.bIocker hace que el objeto Task2 se despierte. De forme similar, al final de niain(), se invoca cancel() para timer y, aun cuando se cancela el temporizados las primeras cinco tareas siguen ejecutndose y siguen bloqueadas en sus llamadas a Task.bIocker.waitingCaIl(). La salida correspondiente a la llamada Task2.blocker.prodAU() 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 quiere decir que tienen su propio bloqueo, de manera que cuando invocan notify() o notifyAlI(), resulta lgico que slo estn invocando dichos mtodos para ese bloqueo, y que slo despierten a las tareas que estn esperando por ese bloqueo concreto. Blocker.watingCall() 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 interrupted(), porque existen dos forma

public class AtomicIntegerTest implements Runnable {

21 Concurrencia 2115 sdistintas de salir del bucle. Si decidimos posteriormente aadir ms cdigo al bucle, correramos el riesgo de introducir un error si no se cubren ambas formas de salir del bucle.

Ejercicio 23: (7) Demuestre que WaxOMatic.java funciona adecuadamente cuando se emplea notify( ) en lugar de

notify All(). 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 camarero representa al consumidor. Ambas tarcas 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: //: concurreney/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 = orderNum; } public String toStringO { return "Meal " + orderNum; }

} class WaitPerson }

21 Concurrencia 2116 implements Runnable { private Restaurant restaurant; public WaitPerson(Restaurant r) { restaurant = r; } public void run() { try { while(1Thread.i nterrupted() ) { synchroniz ed(this) { while(restaurant.mea3 == null) wait(); Jf ... para que el cocinero prepare un plato.

} print("Waitperson got " restaurant.meal); synchronized(restaurant.ch ef) { restaurant.meal = null; restaurant.chef.notifyAll(); // Listo para otro

} } catch(Interrupt edException e) { orint ("WaitPerson interruptedM1') ;

} }

21 Concurrencia 2117 } class Chef implements Runnable { private Restaurant restaurant; private int count = 0; public Chef(Restaurant r) { restaurant = r; } public void run() { try { while(! Thread.inter rupted()) { synchroniz ed(this) { while(restaurant.meal l= null) wait(); // ... para crue se lleven el plato if(++count = 10) { print("Out of food, closing"); restaurant.exec.s hutdownNcw();

} printnb("Order up 1 "); synchronized(rest aurant.waitPerson ) { restaurant.meal = new Meal(count); restaurant.waitPe rson.notifyAll(); i TimeUnit.MILLISECONDS.sleep(100);

} } catch(Interrupt eaException e) { print("Chef interrupted"); }

21 Concurrencia 2118 }

) public class Restaurant { Meal meal; ExecutorService exec = Executors.newCacheaThreadPool() ; WaitPerson waitPerson = new WaitPerson(this); Chef chef = new Chef(this); public Restaurant() { exec.execute(chef); exec.execute(waitPerson);

} public static void main(String[] args) { new Restaurant();

} } /* Output:

Out of food, closing WaitPerson interrupted El objeto Restauran! es el punto focal tanto para el Order up! Chef camarero (WaitPerson) como para el cocinero interrupted *///:(Chef). Ambos deben saber para qu objeto Restaurant estn trabajando, porque ambos deben colocar o tomar los platos en el mostrador del mismo restaurante, restaurant.meal. En run(), el objeto W'aitPerson 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 }

21 Concurrencia 2119 WaitPerson: la propia tarea WaitPerson. Por esta razn, seria tericamente posible invocar notify( ) en lugar de notifyAil(). Sin embargo, en situaciones 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 not'yAll( ), 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 whi!e( ) 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 despertamos, ese pedido tendr que estar disponible, verdad? Como hemos indicado anteriormente, el problema es que en un aaplicacin 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() (empleando, por supuesto, la adecuada sincronizacin y preparando el programa para que no exista la posibilidad de que se pierdan seales): whle(noS eCumpleC ondicin ) 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 notfyAll()), 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 notifyAll( ) tiene que capturar primero 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 invocar notifyAll( ) sobre un mismo objeto.

21 Concurrencia 2120 Ambos mtodos run() estn diseados para poder efectuar una terminacin ordenada encerrando el mtodo run() completo dentro del bloque try. La clusula catch se cierra justo antes de la llave de cierre del mtodo run(), por lo que si la tarea recibe una excepcin IntcrruptedException, terminar inmediatamente despus de capturar la excepcin.

En Chef, observe que despus de invocar shutdownNow( ), podramos simplemente volver (con return) de run(), y eso 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 interrupt) a todas las tareas que hayan iniciado el objeto ExecutorServce. 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 una operacin bloqueante (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 slecp( ). 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 esc 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 posteriormente en el captulo.

Ejercicio 24: (1) Resuelva un problema de un nico productor y un nico consumidor utilizando wait() y notifyAll().

El productor no debe desbordar el buffer del receptor, lo que podra ocurrir si el productor fuera ms rpido que el consumidor. Si el consumidor es ms rpido que el productor, entonces no deber leer los mismos 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 Restaurant.java, vuelva del mtodo run() despus de invocar shutdownNow()

21 Concurrencia 2121 y observe la diferencia de comportamiento.

Ejercicio 26: (8)Aadauna 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 explcitos

Existen herramientas adicionales explcitas dentro de la biblioteca java.util.concurrent de Java SE5 que puede utilizarse para reescribir WaxOMatic.java. La elase 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 externo que pudiera implicar que una tarea puede continuar con su procesamiento, enviamos una notificacin a la tarea invocando el mtodo signal( ), para despertar a una sola tarea, o signaiAlI( ), para despenar a todas las tareas que se hayan suspendido a s mismas para esperar por ese objeto Condition (al igual que sucede con notifyAII( ), signalAll() es la tcnica ms segura). He aqu un programa WaxOMatic.java reescrito para incluir un objeto Condition que se utiliza para suspender una tarea dentro de waitForWaxing() o waitForBuffing( )

2122 Piensa en Java ://: concurrency/waxomatic2/WaxOMatic2.java // Utilizacin de objetos Lock y Condition, package concurrency.waxomatic2; import j ava.util.concurrent.* ; import j ava.uti1.concurrent.locks.* ; import static net.mindview.util.Print.*; class Car { private Lock lock = new ReentrantLock(); private Condition condition <= lock.newConditionO ; private boolean waxOn = false; public void waxed() { lock.lock() ; try { waxCn = true; // Listo para pulir condition.signalAll(); } finally { lock.unlock();

} public void buffed() { lock.lock(); try { waxOn = false; // Listo para otra capa de cera condition.signa 1A11 () ; } finally { lock.unlock{); !

} public void waitForWaxingO throws InterrupteaException { lock.lock(); try { while(waxOn == false) condit ion.awai t ( ) ; } finally { lock.unlock();

2123 Piensa en Java public void waitForBuffing() throws InterruptedSxception( lock.lock (); try { while(waxOn -= true ) condition.await(); } finally { lock.unlock();

} class WaxOn implements Runnable { private Car car; public WaxOn(Car c) { car = c; } public void run() { try { while(!Thread.interrupted()) { printnb("Wax On: ) ; TimeUni t.MILLISECONDS.sieep(200); car.waxed() ;car.waitForBuffing();

} catch(Interrupt edException e) { print("Exitin g via interrupt");

} print("Ending Wax On task");

2124 Piensa en Java } class WaxOff implements Runnable { private Car car; public WaxOff(Car c) { car = c; } public void run () { try { while(i Thread.interrupt ed()) { car.waitForWax ingO ; printnbC'Wax Off! ") ; TimeUnit.MILLISE CONDS.sleep(200) ; car.buffed();

} catch(Interrupt edException e) { print("Exitin g via interrupt");

} print ("Ending Wax Off task1);

) public class WaxOMatic2 { public static void main(String[] args) throws Exception { Car car = new Car(); ExecutorService exec = Executors.newCachedThreadPool() ; exec.execute(new WaxOf f(car)); exec.execute(new WaxOn(car)); TimeUnit.SECONDS.sleep(5);

2125 Piensa en Java exec.shutdownNow();

} } /* Output: (90% match) Wax On! Wax. Off* Off! Wax On! Wax Off! Wax On! Wax Wax Off! Wax On) Wax Off! Wax Wax On I Wax Off 1 Wax On! interrupt Ending Wax Off task Exiting via interrupt Ending Wax On task

WaxOn* Wax Off! Wax Off! Wax On! WaxOff! On! Wax Off! WaxOn! WaxOff! Wax On!

On! Wax Wax

Wax On! Off! via

Exiting

*///:-

Bn 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 boolean.

Cada llamada a Iock() debe ir seguida inmediatamente de una clusula try-flnaUy para garantizar que el desbloqueo se produzca en todos los casos. Al igual que sucede con las versiones integradas, una tarea debe poseer el bloqueo antes de poder invocar a await(), signaK ) o signalAU().

Observe que esta solucin es ms compleja que la anterior 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 .Productores-consumidores y colas

2126 Piensa en Java Los mtodos wait() y notifyAll() resuelven el problema de la cooperacin entre tareas a bastante bajo nivel, efectuando una negociacin para cada iteracin. F.n 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 permite que una tarea inserte o elimine un elemento cada vez. Este tipo de funcionalidad la proporciona la interfaz Java.utH.concurrent.BlockingQueue, que tiene varias implcmentaciones 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 tarca 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 notifyAil().

He aqu una prueba simple que scrializa la ejecucin de objetos LiftOf*. El consumidor es LiftOffRunner, que extrae cada objeto LiftOff 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). //: concurrency/TestBloc kingQueues.java // {RunByHand} import j ava.util.concurrent. *; import j ava.io.*; import static net.mindview.util.Print.*; class LiftOffRunner implements Runnable { private BlockingQueuecLiftOff> rockets; public LiftOffRunner(BlockingQueuecLi ftoff> queue) { rockets = queue;

} public void add(Liftoff lc) { try { rockets.put(lo);

2127 Piensa en Java ) catch(Interrupted Exception e) { print("Interrup ted during put0 ") ;

} public void run() { try { while(!Thread.interrupted()) { Liftoff rocket = rockets.takeO ; rocket.runO ; // Utilizar esta hebra

} } catch(Interrupted Exception e) { print("Waking from take()");

) print("Exiting LiftOffRunner0);

} public class TestBlockingQueues { static void getkeyO { try { // Compensar las diferencias entre Windows/Linux er. cuanto // a 3a

2128 Piensa en Java longitud del resultado producido por la tecla Intro: new BufferedReader( new InputStreamReader(System.in)).readLine(); } catch(java.io.IOException e) { throw new RuntimeExceotion(e)

i 21 Concurrencia 2129 ;static void getkey(String message) { print(message); getkey();

} static void test(String msg, BlockingQueuecLiftOff> queue) { print(msg); LiftOffRunner runner * new LiftOffRunner(queue); Thread t = new Thread(r*anner) ; t.start(); for(int i =0; i < 5; i++) runner.add(new LiftOff(5)) ; getkey ("Press 'Enter' (,? + msg + ")"); t .interrupt () ; print("Finished n + msg + " test");

} pubiic static void main(String[] args) { test ('LinkedBlockingQueue", // Tamao ilimitado new LinkedBlockingQueuecLiftOff>()); test ("ArrayBIockingQueue", // Tamao fijo new ArrayBlockingQueue<LiftOff>(3)); test("SynchronousQueue", // Tamao igual a 1 new SynchronousQueue<Lif tOff>()) ;

) } ///-

Las tarcas son introducidas en la cola BlockingQueue por niain( ) y son extradas de BlockingQueue por el objeto LiftOffRunner. Observe que LiftOffRunncr puede ignorar los problemas de sincronizacin porque stos son resueltos por la cola BlockingQueue.

Ejercicio 28: (3) Modifique TestBIockingQueues.java aadiendo una nueva tarea que introduzca objetos I.iftOfT en la }

i 21 Concurrencia 2130 cola 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 mermelada sobre la tostada ya untada con mantequilla. Podemos ir haciendo pasar la tostada por las distintas colas BlockingQueue que sirven de comunicacin entre los procesos: //: concurrency/ToastOM atic.java // Una tostadora basada en colas, imporn j ava.til.concurrent .*; import java.til.*; import static net.mindview.til.Print.* ; class Toast { public enum Status { DRY, 3TTERBD, JAMMED } private Status status = Status.DRY; prvate final int id; public Toast (int idn) { id = idn; } public void butter() { status = Status.BUTTERBD; } public void jam{) { status = Status.JAMMED; } public Status getStatusO { retum status; } public int getldO { return id; } public String toStringO { retum "Toast " + id +" + status; >class ToastQueue extends LinkedBlockingQueue<Toast> {} class Toaster Implements Runnable ( private ToastQueue toastQueue; private int count = 0; private Random rand = new Random(47); public Toaster(ToastQueue to) { toastQueue = tq; } public void run() { try { while(!Thread.interrupted()) { TimeUnit.MILLISECONDS.sleep( 100 + rand.nextInt(500)); // Kacer tostada Toast t = new Toast (count++) ; print(t); // Insertar en la cola toastQueue.put(t); }

i 21 Concurrencia 2131 ) } catch(InterruptedException e) { print("Toaster interrupted");

} print("Toaster off");

} // Untar de mantequilla la tostada: class Butterer implements Runnable { private ToastQueue dryQueue, butteredQueue; public Butterer(ToastQueue dry, ToastQueue buttered) { dryQueue * dry; butteredQueue = buttered;

) public void run() { try { while (!Thread.interrupted ()) { // Se bloquea hasta que haya otra tostada disponible Toast t = dryQueue. take () ; t.butter(); print(t) ; butteredQueue.put(t);

} } catch(InterruptedException e) { print("Butterer interrupted");

> print ("Butterer off);

} }

i 21 Concurrencia 2132 // Poner mermelada sobre la tostada untada de mantequilla: class Jammer implements Runnable { private ToastQueue butteredQueue, finishedQueue; public Jammer(ToastQueue buttered, ToastQueue finished) { butteredQueue = buttered; finishedQueue = finished;

} public void run() { try { while(1 Thread.interruptedO ) { // Se bloquea hasta que haya otra tostada disponibl eToast t = butteredQueue.take(); t. jam(); print (t) finishedQueue.put(t);

} } catch(InterruptedException e) { print (" Jaratr.er interruoted1) ;

} print("Jammer off");

} // Consumir la tostada: class Eater implements Runnable { private ToastQueue finishedQueue; private int counter = 0; public Eater(ToastQueue finished) { finishedQueue }

i 21 Concurrencia 2133 = finished;

) public void run() { try { while{IThread.interrupted()) { // Se bloquea hasta que haya otra tostada disponible: Toast t = finishedQueue.take() ; // Verificar que la tostada se ha preparado correctamente // y que todas las tostadas llevan mermelada: if(t.getld() Icounters || t.getStatus() 1= Toast.Status.JAMMED) { print(">> Error: " + t) ; System.exit(1); } else print ("Chomp 1 11 + t) ;

} } catch(InterruptedException e) { print ("Eater interrupted11);

) print("Eater off");

} public class ToastOMatic { public static void main(String[) args) throws Exception { ToastQueue dryQueue = new ToastQueue(), butteredQueue = new }

i 21 Concurrencia 2134 ToastQueue(), finishedQueue = new ToastQueue(); ExecutorService exec = Executors.newCachedThreaaPool(); exec.execute(new Toaster(dryQueue)); exec.execute(new Butterer(dryQueuef butteredQueue)); exec.execute(new Jammer(butteredQueue, finishedQueue)); exec.execute(new Eater(finishedQueue)); Timenit.SECONDS.sleep(5); exec.shutdownNow();

} /* (Ejecutar para ver la salida) *///:-

Toast es un excelente ejemplo de la ventaja de emplear valores enum. Observe que no hay ninguna sincronizacin explcita (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 Toast slo es manipulado por una nica tarea cada vez. Puesto que las colas son bloqueantes, los procesos se suspenden y se reanudan automticamente.

Podemos ver que BlockingQueue puede simplificar el problema enormemente. El acoplamiento entre las clases que existira con instrucciones wat( ) y notifyAll( ) explcitas se elimina, porque cada clase se comunica slo con sus colas BlockingQueue.

Ejercicio 29: (8)ModifiqueToastOMadc.javaparafabricar cacahuete y mermelada, uti

bocadillos de mantequilla de

lizando dos lneas de fabricacin separadas (una para el pan con }

i 21 Concurrencia 2135 mantequilla, otra para el pan con mermelada 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 intertareas en la forma de canalizaciones (pipes). Estas canalizaciones existen 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 productorconsumidor, siendo la canalizacin una solucin prediseada. La canalizacin es bsicamente 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: //: concurrency/PipelO.java // Utilizacin de canalizaciones para la E/S inter-tareas import java.til.concurrent.*; import java.io.*; import java.til.*; import static net.raindview.til.Print.*; class Sender implements Runnable { private Random rand = new Random(47); private PipedWriter out = new PipedWriter() ; public PipedWriter getPipedWriter() { return out; } public void rur. () { try { while(true) for(char c = 'A'; c <= 'z'; c+-) { out.write(c); TimeUnit.MILLISECONDS.sleep(rand.nextInt(500));

> ) catch(IOException e) { print(e + n Sender write exception"); } catch(InterruptedKxception e) { print(e + " Sender sleep interrupted"); }

i 21 Concurrencia 2136 )

} class Receiver implements Runnable { private PipedReader in; public Receiver(Sender sender) throws IOException { in = r.ew PipedReader (sender.getPipedWriter ()) ;

} public void run() { try { while(true) { //Se bloquea hasta que haya caracteres: printnb ("Read: 11 + (char) in.readO 4 H, ");

} } cacch(IOException e) { print(e + " Receiver read exception");

} public class PipedlO { public static void main(String[] args) throws Exception { Sender sender = new Sender(); }

i 21 Concurrencia 2137 Receiver receiver = new Receiver(sender); SxecutorService exec = Executors.newCachedThreadPool(); exec.execute(sender); exec.execute(receiver); TimeUnit.SECONDS.sleep(4); exec.shutdownNow();

} } /* Output:(65% match) Read: A, Read: B, Read: C, Read: D, Read: E, Read: F, Read: G, Read: H, Read: If Read: J, Read: K, Read: L, Read: M, java.lang.IncerruptedException: sleep interrupted Sender sleep interrupted java.io.InterruptedlOException Receiver read exception *///:-

Sender y Receiver representan tareas que necesitan comunicarse entre s. Sender crea un objeto escritor PipedWriter, que es un objeto autnomo, pero dentro de Receiver la creacin del objeto lector PipedReader debe asociarse con un objeto PipedWriter dentro del constructor. Sender pone datos sobre el objeto Wrlter 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 existen ms datos.

Observe que los objetos sender y receiver se inician en main( ). despus de que los objetos hayan sido completamente construidos. Si no comenzamos con objetos completamente construidos, la canalizacin puede presentar un comportamiento incoherente en las distintas plataformas (observe que las colas de tipo BlockingQueue 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 shutdownNow(): el lector PipedReader es interrumpible, mientras que si cambiramos, por ejemplo, la llamada in.read() por System.in.read(), la interrupcin interrupt( ) no conseguira salir de la Llamada a read( ). }

i 21 Concurrencia 2138 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 tarcas 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 intei bloqueo (deadlock).2]

Si tratamos de ejecutar un programa y se produce directamente un interbloqueo, podemos intentar localizar inmediatamente 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, asi que el error estar latente en el programa hasta que se presente de manera inesperada cuando un cliente lo ejecute (en una forma que casi siempre ser muy difcil 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 fsofos, inventado por Edsger Dijkstra, es una ilustracin clsica del interbloqueo. La descripcin bsica especifica cinco filsofos (aunque el ejemplo mostrado aqu permitira cualquier nmero de ellos). Estos 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 problema, los utensilios eran tenedores, y se requieren dos tenedores para tomar espagueti de una ftientc 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 comprar cinco palillos (o ms generalmente, el mismo nmero de palillos que de filsofos). Esos palillos estn espaciados alrededor de la mesa }

i 21 Concurrencia 2139 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 filsofo deber esperar hasta que los palillos necesarios estn disponibles. //: concurrency/Chopstick. java // Palillos para la cena de los filsofos. public class Chcpstick { private boolean taken = false; public synchronized void Lake() throws Interruptedxceprion { while(taken) v/ait () ; taken = true;

} public synchronized void dropO { 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 Philosopher, otro filsofo podr esperar (wiiit()) hasta que el objeto Chopstick quede disponible cuando su propietario actual invoque drop() (soltar palillo).

i 21 Concurrencia 2140 Cuando una tarea Philosopher invoca take(), esa tarea espera que el indicador taken (ocupado) valga i'alse (es decir, hasta que el objeto Philosopher 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 Chopstick. Cuando este objeto Philosopher haya terminado de usar el objeto Chopstick, invocar drop( ) para cambiar el indicador y llamar a notifyAll() para notificar a todos los dems objetos Philosopher que puedan estar esperando a utilizar el objeto Chopstick. //: concurrency/Philosophe r.java // Un filsofo comensal import java.til.concurrent.* ; import java.til.*; import static net.mindview.til.Print; public class Philosopher implements Runnable { private Chopstick left; private Chopstick right; private final int id; private final int ponderFactor; private Random rand = new Random(47) ; private void pause() throws InterruptedException { if(ponderFactor == 0) return; TimeUnit.MILLISECCNDS.sleep( rand.nextInt(ponderFactor * 250));

} public Philosopher(Chopstick left, Chopstick right, int idenL, int ponder) { this.left = left; this.right = right

i 21 Concurrencia 2141 ;id = ident; ponderFactor = ponder; public void run() { try { while(IThread.interrupted{)) { print(this -* + "thinking); pause(); // El filsofo tiene hambre print (this *"" + "grabbing right"); right.rake(); print (this M " + "grabbing left"); lefttake(); print (this -* " + "eating); pause(); right.drop(); left.drop();

} } catch(InterruptedException e) { orint(this + " " * "exiting via interrupt");

} public String toStringO { return "Philosopher " + id; }

} ///:-

En Philosopher.run(), cada objeto Philosopher simplemente piensa y come de manera continua. El mtodo pause() efecta una llamada a sleeps() 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 derecho, 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: //: concurrency/DeadlockingDiningPhilosophers.java // Ilustra cmo puede haber interbloqueos ocultos en un programa. // {Args: 0 5 timeout} import j

i 21 Concurrencia 2142 ava.util.concurrent.*; public class DeadlockingDiningPhilosophers { public static void main (String [] args) throws Exception { int ponder = 5; if(args.length > 0) ponder = Integer.parselnt(args[0]); int size = 5; if(args.length > 1) size = Integer.parselnt(args1]); ExecutorService exec = Executors.newCacnedThreadPool(); ChopsticklJ sticks = new Chopstick[sizej; for(int i = 0; i < size; i++) sticks[i] = new Chopstick(); for(int i = 0; i < size; i++) exec.execute(new Philosopher{ sticks[1], sticks[(i+1) % size], i, ponder)); if(args.length == 3 && args[2].equals("timeout")) TimeUnit.SECONDS.s leep(5); else { System.out.println("Press 'Enter' to quit"); System.in.read();

} exec.shutdownNow();

} } /* (Ejecutar para ver la salida) *///:-Podr observar que si los objetos Philosopher pasan demasiado poco tiempo pensando, todos ellos competirn por los objetos 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 Philosopher dedica a pensar. Si tenemos muchos objetos Philosopher o estos pasan una gran cantidad de tiempo pensando. puede que nunca lleguemos a ver un problema de interbloqueo, aunque este 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 sticks. A cada constructor Philosopher se le proporciona una referencia a sendos objetos Chopstick izquierdo y derecho. Cada objeto Philosopher excepto el ltimo se inicializa situando dicho objeto Philosopher entre el siguiente par de objetos Chopstick. Al ltimo objeto

i 21 Concurrencia 2143 Philosopher se le asigna el objeto Chopstick simado en la posicin cero como palillo derecho. con lo que se completa la mesa redonda. Esto se debe a que el ltimo filsofo est situado justo al lado del primero y ambos comparten ese palillo nmero cero. Ahora, ser posible que todos los objetos Philosopher traten de comer, quedando todos ellos a la espera de que el objeto Philosopher 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 quedamos convencidos de que el programa no sufre interbloqueos (utilizando un valor de ponder distinto de cero o un gran nmero de objetos Philosopher), 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 cuatro 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 propiedad de otra tarea. Es decir, para que el interbloqueo se produzca, un objeto Philosopher deber povseer 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 producirse como un suceso normal. En otras palabras, nuestros filsofos son muy educados y no arrebatan los palillos a los otros filsofos.

4.

Puede producirse 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 tareas est esperando por un recurso posedo por la primera tarea, lo que hacc que se produzca una cadena circular de bloqueo. En DeadlockingDiningPhilosophers.java, esta espera circular se produce porque cada filsofo trata de obtener primero el palillo derecho y luego el izquierdo.

i 21 Concurrencia 2144 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 consiste en impedir que se d la cuarta condicin. Esta condicin sucede porque cada filsofo trata de tomar los correspondientes 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 izquierdo, provocando la aparicin de la condicin de espera circular. Sin embargo, si inicial izamos el ltimo filsofo para que trate de obtener primero el palillo izquierdo y luego el derecho, ese filsofo nunca impedir que el filsofo situado inmediatamente a su derecha tome los dos palillos que necesita. En este caso, se impide la espera circular. Esta 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): //: concurrency/FixedDiningPhilosopher s.java // La cena de los filsofos sin interbloqueo. // {Args: 5 5 timeout} import java.util.concurrent ;public static void main(String[] args) throws Exception { int ponder = 5; if(args.length > 0) ponder = Integer.parselnt(args[0]) ; int size = 5; if(args.length > 1) si2e = Integer.parselnt(args[1]); ExecutorService exec = Executors.newCacheThreadPool(); Chopstick[] sticks = new Chopstick[size]; for(int i = 0; i < size; i++) sticks[i] = new Chopstick(); for(int i = 0; i < size; i++) if(i < (size-1)) exec.execute(new Philosopher( sticks [ij, sticks[i+1]/ i, ponder)); else exec.execute(new Philosopher( sticks[0], sticks[i], i, ponder)); if(args.length == 3 && args[2].equals("timeout)) TimeUni t.SECONDS.sleep(5); else { System.out.printIn("Press 'Enter' to quit"); System.in.read();

) exec.shutdownNow();

} } /* (Ejecutar para ver la salida) *///:-

i 21 Concurrencia 2145 Garantizando que el ltimo filsofo tome y deje el palillo izquierdo antes que el derecho, eliminamos el interbloqueo y el programa funcionar sin problemas.

No hay ningn soporte del lenguaje para ayudamos a prevenir el interbloqueo; es un problema que deberemos resolver nosotros mismos realizando 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 DeadlockingDiningPhilosophers.java de modo que, cuando un filsofo haya terminado de

emplear sus palillos, los deje en lina 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 SE5 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 utilice a la hora de escribirlos).

Puesto que estos componentes permiten resolver varios problemas, no existe una forma clara de organizarlos, as que tratar 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 conjunto de operaciones que estn siendo realizadas por otras tareas.

i 21 Concurrencia 2146 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

2147 Piensa en Java objeto para reducir el valor de recuento, presumiblemente cuando esas tareas finalicen su trabajo. CountDownLatch est diseado para utilizarlo una sola vez, el valor de recuento no puede reinicializarse. Si necesitamos una versin donde pueda reinicial izar el valor de recuento, podemos emplear en su lugar CydlcBarrier.

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 CountDownLatch 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 await() sobre el objeto encargado del recuento para esperar a que el problema est completamente resuelto. He aqu un esqueleto de ejemplo que ilustra esta tcnica: //: concurrency/CountDownLatchDemo.j ava import j ava.util.concurrent.*; import j ava.ut i1.*; import static net .mindview.-util. Print .*; // Realiza cierta parte de una tarea: class TaskPortiori implements Runnable { private static int counter = 0; private final int id = counter**; private static Random rand = new Random(47); private final CountDownLatch latch; TaskPortion(CountDownLatch latch) { this.latch = latch;

> public void runO { try { doWork{); latch.countDown(); ) catch(InterruptedException ex) { // Foma aceptable de salir

} public String toStringO {

2148 Piensa en Java public void doWorkO throws interruptedException { TimeUnit.MILLISECONDS.sleep(rand.n extlnt(2000)); print(this + "completed");

} public String LoStringO { return String.format("%l$-3d ", id);

) // Espera sobre CountDownLatch: class WaitingTask implements Runnable { private static int counter = 0; private final int id = counter**; private final CountDownLatch latch; WaitingTask(CountDownLatch latch) { this.latch = latch;

} public void run() { try { latch.await(); print("Latch barrier passed for " + this); } catch(InterrupteaSxception ex) { print (this " interrupted");

retum String.format("WaitingTask %l$-3d ", id) ;

} public String toStringO {

2149 Piensa en Java } public class CountDownLatchDemo { static final int SIZE = 100; public static void main (String [] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool(); // Todos deben compartir un nico objeto CountDownLatch: CountDownLatch latch = new CountDownLatch(SIZE) ; for(int i = 0; i < 10; i++) exec.execute(new WaitingTask(latch)) forint i = 0; i < SIZE; i++) exec.execute(new TaskPortion(latch)); print("Launched all taska"); exec.shutdown(); // Salir cuando todas las tareas se hayan completado

} } /* (Ejecutar para ver la salida) *///:-

TaskPortion duerme durante un perodo aleatorio para simular la terminacin 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)UtiliceunobjetoCountDownLatchpara resultados de las

resolver el problema de correlacin de los

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 invocando Random.nextlnt() al mismo tiempo. Es esto seguro?

public String toStringO {

2150 Piensa en Java Si existe un problema, puede resolverse en este caso asignando dos TaskPortion en su propio objeto Random, es decir, eliminando el especificado static. Pero esa misma cuestin seguir siendo pertinente, con carcter general, para todos los mtodos 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.nextlnt() s que es seguro respecto a las hebras, pero es necesario descubrir en cada caso si un cierto mtodo lo es, efectuando una bsqueda 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 tarcas para realizar un cierto trabajo en paralelo, y luego esperar a que todas hayan finalizado, antes de continuar con el siguiente paso (algo parecido a join(), podramos decir). Esta clase alinea todas las tareas paralelas en la barrera circular, ce modo que podamos continuar hacia adelante al unsono. Se trata de una solucin muy similar a CountDownLatch, excepto porque CountDownLatch representa un suceso que slo se produce una vez, mientras que CyclicBarrier 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 69 era una simulacin: un juego de carreras de caballos escrito en BASIC y denominado (debido a las limitaciones de los nombres de archivos) HOSRAC.BAS. He aqu la versin orientada a objetos y con hebras de dicho programa, utilizando una clase CyclicBarrier: //: concurrency/HorseRace.j ava // Utilizacin de CyclicBarrier.import j ava.util.concurrent.*; import java.util.*; import static net.mindview.util.Print.*; class Horse implements Runnable { private static int counter = 0; private final int id = counter**; private int strides = 0; private static Random rand = new Random(47); private static Cyclic3arrier barrier; public Horse (Cycl icBarrier b) { barrier *= b; } public synchronized int getStrides() { return strides; } public void run() { try ( while(1Thread.interrupted()) { synchronized(this) { strides += rand.nextInt(3); // Produce 0, 1 o 2 Cuando estaba en la universidad; en la laboratorio disponamos de un teletipo SR-33 con un nodo de acoplamiento de acstico de 110 baudios mediante el que se acceda a una computadora HP-1000. public String toStringO {
69

2151 Piensa en Java } barrier.await();

} catch(InterruptedException e) { // Una forma legtima de salir } catch(BrokenBarrierException e) ( // sta queremos probarla throw new RuntimeException(e);

) public String toStringO { return "Horse " * id * " } public String tracks() { StringBuiider s = new StringBuilder(); for(int i = 0; i < getStrides () ; i++) s.append("*"); s.append(id); return s.toString();

} public class HorseRace { Hatic final iuL FINISH_LINE = 75; private List<Horse> horses = new ArrayList<Horse>()? private ExecutorService exec =* Executors.newCachedThreadPool(); private CyclicBarrier barrier; public HorseRace(int nHorses, final int pause) { barrier = new CyclicBarrier(nKorses, new Runnable() { public void run() { StringBuilder s = new StringBuilder() ; for (int i *= 0; i < FINISH_LINE; i++) s.append(); //La valla en el hipdromo print(s); for(Horse horse : horses) print(horse.tracks()); for(Horse horse : horses) if(horse.getStrides() >= FINISH_LINE) { print(horse * won!"); exec.shutdownNcw(); return; public String toStringO {

2152 Piensa en Java } try { TimeUnit .MILLISECONDS. sleep (pause) ;} catch(Interrupted3xception e) { print ("barrier-action aleep interruptedn11)?

}>; forint i = 0; i < nKorses; i++) { Horse horse = new Korse(barrier); horses.add(horse); exec.execute(horse);

} pub.ic static void main(String[] args) { int nHorses = 7; int pause = 200; if(args.length >0) { // Argumento opcional int n = new Integer(args[0]) ; nHorses = n > 0 ? n : nHorses;

) if(args.length >1) { // Argumento opcional int p = new Integer(args[lj); pause = p > -1 ? p : pause;

public String toStringO {

2153 Piensa en Java } new HorseRace(nHorses, pause);

} } /* (Ejecutar para ver la salida) *///:-

Un objeto CyclicBarrier puede proporcionar una accin de barrera, que es un objeto Runnable que se ejecuta automticamente cuando el contador alcanza el valor cero; sta es otra distincin entre CyclicBarrier y CountdownLatch. Aqu, la accin de barrera se define 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 esperar 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 estarautomticamente siguiente ronda.

lista

para

la

Para obtener el efecto de una animacin muy simple, reduzca el tamao de la consola para slo se muestren os caballos. DelayQueue

que

Se trata de una cola BlockingQueue de objetos sin limitacin de tamao que implementa la interfaz Delayed. Un objeto slo puede ser extrado de la cola una vez que su retardo haya transcurrido. La cola est ordenada, de modo que el objeto situado 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 nuil (debido a esto, no pueden insertarse elementos nuil en la cola).

He aqu un ejemplo donde los objetos Delayed son tareas y el consumidor DelavedTaskCousumer extrae la tarea ms urgente (aquella cuyo retardo haya caducado hace ms tiempo) de la cola y la public String toStringO {

2154 Piensa en Java ejecuta. Observe que DelayQneue es, por tanto, una variante de una cola con prioridad. //: concurrency/DelayQueueDemo.java import java.til.concurrent.*; import java.util.*; import static java.util.concurrent.TimeUnit.*; import static net.mindview.til.Print.*; class DelayedTask impiements Runnable, Delayed { prvate static int counter = C; private final int id = counter++ ;private final int delta; private final long trigger; protected static List<DelayedTask> sequence = new ArrayList<DelayedTask>(); public DeiayedTask(int delaylnMilliseconds) { delta = delaylnMilliseconds; trigger = System. nanoTimeO + NANOSECONDS.convert(delta, MILLISECONDS); sequence.add(this);

} public long getDelay(TimeUnit unit) { return unit.convert( trigger - System.nanoTime(), NANOSECONDS);

} public int compareTo(Delayed arg) { DeiayedTask that = (DeiayedTask)arg; if (trigger < that.trigger) return -1; if(trigger > that.trigger) return 1; return 0;

} public void run() { printnb(this + " 11) ; } public String toStringO { return String.format("[%1$-4a] ", delta) + Task " + id;

} public String summary() { return "(" + id + "+ delta M)n; public String toStringO {

2155 Piensa en Java } public static class EndSentinel extends DeiayedTask { private ExecutorService exec; public EndSentinel(int delay, ExecutorService e) { super(delay); exec = e;

} public void run() { for(DeiayedTask pt : sequence) { printrJb (pt. summary () + " M) ;

} print(); print(this + " Calling shutdownNow()"); exec.shutdownNow{);

} class DelayedTaskConsumer implements Runnable { private DelayQueue<DelayedTask> q; public DelayedTaskConsumer(DelayQueue<DelayedTask> q) { this.q = q;

} public void run() { try { while(Thread.interrupted()) q.take-0 .run(); // Ejecutar tarea con la hebra actual } catch(InterruptedException e) { // Forma aceptable de salir

} public String toStringO {

2156 Piensa en Java Dr int (''Finished DelayedTaskConsumer") ;

public String toStringO {

21 Concurrencia 2157 )public class DelayQueueDemo { public static void main(String[] args) { Random rand ** new Random (47); ExecutorService exec = Executors.newCachedThreadPool() ; DelayQueue<Delayed'iask> queue - new DelayQueue<DelayedTask> () ; // Rellenar con tareas que tengan retardos aleatorios: for(int i = 0; i < 20; i+ + ) queue.put(new DelayedTask(rand.nextInt(5000))); // Establecer el punto de detencin queue.add(new DelayedTask.EndSentinel(5000, exec)); exec.execuLe(new DelayedTaskConsumer(queue)); ) } /* Output:

(0:4258)(1:555) (2:1693) (3:1861) (4:961) (5:429) (6:4868) (7:200)(8:4522) (9:1207) (10:3288) DelayedTask contiene una lista (11:128) (12:3551) List<DelaycdTask> (13:4589)(14:1809) (15:2278)(16:998) (17:4861) (18:520) denominada scquence (19:4258)(20:5000) que preserva el orden en [5000] Task 20 Calling shutdownNow() que fueron creadas las Finished DelayedTaskConsumer *///:tareas, para poder comprobar que efectivamente se est realizando una ordenacin.

La interfaz Delayed tiene un mtodo, getDe(ay(), que dice cunto queda para que caduque el retardo o cunto tiempo hace que ha caducado. Este mtodo nos tuerza 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 convenir el valor de delta indicando en qu unidades est y en qu unidades lo queremos como en el ejemplo siguiente: NAN0S3C0NBS.convert(delta, MILLISECONDS);

En getDelay( ), pasamos las unidades deseadas como argumento uiiit, y las usamos para convertir el intervalo de tiempo transcurrido desde el instante de disparo a las unidades solicitadas por el llamante, sin siquiera tener por qu conocer qu unidades son esas (ste es un ejemplo simple del patrn de diseo Estrategia, segn el cual parte del algoritmo se pasa como argumento).

21 Concurrencia 2158 Para la ordenacin, la interfaz Delayed tambin hereda la interfaz Comparable, as que habr que implementar compareTo() para que realice una comparacin razonable. toString() y summary( ) se encargan del formateo de la salida y la clase anidada EndSentinel proporciona una 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 tarca 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 Delayed Task.

Podemos ver, analizando la salida, que el orden en que se crean las tareas no tiene ningn efecto sobre el orden de ejecucin, 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 prioridad PrioritizedTask se le proporciona un nmero de prioridad para lijar el orden ://: concurrency/PriorityBlockingQueueDemo.j ava import java.uti1.concurrent.*; import java.util*? import static net.mindview.util.Print.*; class PrioritizedTask implements Runnable, Comparable<PrioritizedTask> { private Random rand = new Random(47); private static int counter = 0? private final int id = counter**; private final int priority; protected static List<PrioritizedTask> sequence = new ArrayList<PrioritizeaTask>(); public PrioritizedTask(int priority) { this.priority = priority; sequence.add(this);

public int compareTo(PrioritizedTask arg) { return priority < arg.priority ? 1 : (priority > arg.priority ? -1 : 0);

21 Concurrencia 2159 } public void run() { try { TimeUnit.MILLISECONDS.sleep(rand.nextInt(250)); } catch(InterruptedException e) { // Forma aceptable de salir

} print(this);

} pxiblic String toString() { return String.format([%1$-3d]", priority) + " Task w + .id;

} public String summary() { return "(" + id + ":" + priority f n),r;

} public static class EndSentinel extends PrioritizedTask { private ExecutorService exp.r; public EndSentinel(ExecutorService e) { super(-1); //La menor prioridad en este programa exec - e;

} public void run() { int count = 0; for(PrioritizedTask pt : sequence) ( printnb(pt.summary ()); if(*+count % 5 == 0) print();

} print () ; print(this f " Calling shutdownNow()"); exec. shutdownNov/ () ;

21 Concurrencia 2160 }

} class PrioritizedTaakPrcducer implements Runnable { private Random rand = new Random(47); private Queue<Runr.able> queue; private ExecutorService exec; pub]ic PrioritizedTaskProducer( Queue<Runnable> q, ExecutorService e) { queue = q; exec = e; // Utilizado para EndSentinel

} public void run() { // Cola no limitada,- nunca se bloquea. // Rellenarla rpidamente con prioridades aleatorias: forint i 0; i < 20; i++) { queue.addtnew PrioritizedTask(rand.nextlnt(10))); Thread.yield();

} // Introducir tareas de prioridad mxima: try { forint i =* 0; i < 10; i++) { TimeUnit.MILLISECONDS.Sleep(250); queue.add(new PrioritizedTask(10));

} // Af.adir tareas, primero las de menor prioridad: forint i = 0; i < 10; T+) queue.add(new PrioritizedTask(i)); // Un centinela para detener todas ias tareas: queue.aad(new PrioritizedTask.EndSentinel(exec)); } catch(InterruptedException e) { // Forma aceptable de salir

21 Concurrencia 2161 } orint("Finished PrioritizedTaskProducer");

} class PrioritizedTaskConsumer implements Runnable { private PriorityBlockingQueue<Runnable> q; public PrioritizedTaskConsumer( PriorityBlockingQueue<Runnable> q) { this.q = q;

} public void run() { try { while(!Thread.interrupted()) If Utilizar la hebra actual para ejecutar la tarea q.take().run(); ) catch(InterruptedException e) { ff Forma aceptable de salir

} print("Finished PrioritizedTaskConsumer");

} public class PriorityBlockingQueueDemo { public static void main(String args) throws Exception { Random rand = new Random(47); ExecutorService exec = Executors.newCachedThreadPool(); PriorityBlockingQueue<Runnable> queue = new PriorityBlockingQueue<Runnable>(); exec.execute(new PrioritizedTaskProducer(queue, exec)); exec.execute(new PrioritizedTaskConsumer(queue));

21 Concurrencia 2162 > } f* (Ejecutar para ver la salida) *///: -AI 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 PrioritizedTaskProduccr 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 sincronizacin, observe que no hace falta ninguna sincronizacin explcita: 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 encendan y apagaban diversos elementos o se los ajustaba de alguna manera. Este puede verse como un tipo de problema de concurrencia, siendo cada suceso deseado en el invernadero una tarea que se ejecuta en un instante predefinido. La clase ScheduledThreadPoolExecutor 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 tcnica utilizada en el Captulo 10, Clases internas, para observar cmo se simplifican las cosas cuando podemos emplear una herramienta predefinida como ScheduledThreadPoolExecutor: //: concurrency/GreenhouseScheduler.j ava // Reescritura de innerclasses/GreenhouseController.java // para usar la clase ScheduledThreadPoolExecutor. // {Args: 5000} i mport j ava.ut il.concurrent.*; import java.util.*; public class GreenhouseScheduler { private volatile boolean light = false; private volatile boolean water false; private String thermostat = "Day"; public synchronized String getThermostat() { return

21 Concurrencia 2163 thermostat;

} public synchronized void setThernostat(String value) { chermostat = value;

} ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(10); public void schedule(Runnable event, long delay) { scheduler.schedule(event,delay,TimeUnit.MILLISECONDS);

} public void repeat(Runnable event, long initialDelay, long period) { scheduler. sc'neduleAtFixedRate ( event, initialDelay, period, TimeUnit.MILLISECONDS);

} class LightOn implements Runnable { public void runt) { // Incluir aqu el cdigo de control del hardware // para encender fsicamente la iluminacin. System.out.printin("Turning on lights"); light = true;

21 Concurrencia 2164 class LightOff implements Runnable { public void run()

{// Incluir aqu el cdigo de control del hardware // para apagar fsicamente la iluminacin. System.out.println("Turning off lights"); light = false;

} class WaterOn implements Runnable { public void run() { // Incluir aqu el cdigo de control del hardware. System.out.println("Turning greenhouse water on"); water = true;

) class WaterOff implements Runnable { public void run() { // Incluir aqu el cdigo de control del hardware. System.out.println("Turning greenhouse water off"); water = false;

} class ThermostatNight implements Runnable { public void run() { // Incluir aqj el cdigo de control del hardware. System.out.println("Thermostat to night setting"); setThermostat("Night");

} class ThermostatDay implements Runnable { public void run() { // Incluir aqu el cdigo de control del hardware. System.out.println("Thermostat to day setting"); setThermostat("Day");

) class Bell implements Runnable { Dublic void run() { System.out.println("Bing!"); }

} class Terminate implements Runnable { public void run() { System.out.println("Terminating"); scheduler.shutdownNow(); // Hay que iniciar una tarea separada para hacer este trabaj //ya que el planificador ha sido terminado: new Thread() { public void run() { for (DataFoint d : data) System.out.println(d);

} }.start();

} // Nueva caracterstica: recopilacin de datos static class DataPoint { final Calendar time; final float temperature; final float humidity; public DataPoint(Calendar d, float temp, float hum) { time = d; temperature = temp

;humidity = hum; public Scring toStringO { return time.getTime() + String.format( " temperature: %l$.lf humidity: %2$.2f", temperature, humidity);

} private Calendar lastTime = Calendar.getInstance0; { // Ajustar la hora con medias horas lastTime.set(Calendar.MINUTE, 30); lastTime.set(Calendar.SECOND, 00); j private float lastTemp = 65. Of; private int tempDirection = +1; private float lastHumidity = 50.Of; private int humidityDirection = +1; private Random rand = new Random(47); List<DataPoint> data = Collections.synchronizedList ( new ArrayList<Data?oint>()); class CollectData implements Runnable { public void run() { System.out.printIn("Collecting data); synchronized(GreenhouseScheduler.this) ( // Simular que el intervalo es mayor de lo que es: lastTime.set(Calendar.MINUTE, lastTime .get(Calendar.MINUTE) + 30); // Una oportunidad entre 5 de invertir la direccin: if(rand.nextlnt(5) == 4) tempDirection = -tempDirection; // Almacenar valor anterior: lastTemp = lastTemp + tempDirection * (l.Of + rand.nextFloat()); if(rand.nextInt(5) == 4) humidityDirection = -humidityDirection; lastHumidity = lastHumidity + humidityDirection * rand.nextFloat();

// Ec preciso clonar Calendar, ya quo on caso contrario todos // los puntos de datos almacenaran referencias al mismo valor // lastTime. Para un objeto bsico como Calendar, clone() es OK. data.add(new DataPoint((Calendar)lastTime.clone0, lastTemp, lastHumidity));

} public static void main(String[] args) { GreenhouseScheduler gh = new GreenhouseScheduler(); gh.schedule(gh.new TerminateO, 5000); // La anterior clase "Restart" no es necesaria: gh.repeat(gh.new Bell(), 0, 1000); gh.repeat(gh.new ThermostatNight() , 0, 2000); gh.repeat (gh.new LiahtOnO, 0, 200); gh.repeat (gh.new LightOffO, 0, 400); gh.repeat (gh.new WaterOnO, 0, 600); gh. repeat (gh.new WaterOffO, 0, 800); gh.repeat(gh.new ThermostatDay(), 0, 1400); gh.reoeat(gh.new CollectData(), 500, 500);

} } /* (Ejecutar para ver la salida) *///: -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 volatile como de synclironized 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.Coilections en el momento de crear la lista.

Ejercicio 33: (7) Modifique GreenhouseSchcduler.java para que utilice un objeto DelayQueue en lugar de

Scheduled Executor. 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 permite que n tareas accedan al recurso simultneamente. Tambin podemos pensar en un semforo como algo que entrega permisos para usar un recurso, aunque en realidad no se utiliza ningn objeto permiso.

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 usuario haya terminado con ellos. Esta funcionalidad puede encapsularse en una clase genrica: //: concurrency/Pool.j ava // Utilizacin de un semforo dentro de conjunto compartido, // para restringir ex nmero de careas que pueden usar un recurso, import java.til.concurrent; import java.til.*; public class Pooi<T> { prvate int size; private List<T> items new ArrayList<T>() ; private volatile booleanf] checkedOut; private Semaphore avaiiable; public Pool(CIass<T> classObject, int size) { this.size = size; checkedOut = new boolean[size]; avaiiable = new Semaphore(size, true); // Cargar conjunto con objetos que puedan extraerse: for(int i * 0; i < size; ++i) try { // Presupone un constructor predeterminado; items.add(classObject. newInstance()); } catch(Exception e) { throw new RuntimeExcepticn(e);

) public T checkOut0 throws InterruptedException { avaiiable.acquire 0; retum getltem() ;

} public void checkIn{T x) { if(reieaseltem(x)) avaiiable.release();

} private synchronized T getltem() { for(int i = 0; i < size; + +i) if (!checkedOut[i]) { checkedOut[iJ = true

2171 Piensa en Java ;return tems.get(i);

) return nuil; I I El semforo impide llegar acru

} private synchronized boolean releaseItem(T itein) { int ndex = tems.ndexOf(tem) ; if (ndex == -1) return false; // No est lista if(checkedOut[ndex] ) { checkedOut[ndex ] = false; return true;

en

la

} return false; // No ha sido extrado

} ///:-

En csia forma simplificada, el constructor utiliza newlnstance() para cargar de objetos el conjunto compartido. Si necesitamos un nuevo objeto, invocamos checkOut(), y cuando hemos finalizado con un objeto, lo devolvemos con checkln().

La matriz boolcana checkedOut lleva la cuenta de los objetos que han sido extrados y est gestionada por los mtodos getltem() y releaseltein(). Estos, a su vez, estn protegidos por el semforo available, de modo que, en checkOut(), avai- lablc bloquea el progreso de la llamada si no hay permisos de semforo disponibles (lo que quiere decir que no hay ms }

2172 Piensa en Java objetos en el conjunto compartido). En checkln(), si el objeto devolviendo es vlido, se permiso que se est devuelve un

al semforo.

Para construir un ejemplo, podemos usar Fat, un tipo de objeto que resulta costoso de crear, porque su constructor tarda bastante tiempo en ejecutarse: //: concurreney/Fat.j ava // Objetos que son costosos de crear. public class Fat { private volatile double d; // Impedir optimizacin private static int counter = 0; private final int id counter++; public Fat() { // Operacin costosa e interrumpible: for(int i = 1; i < 10000; i++) { d += (Math.PI -i- Math.E) / (double)i; i

) public void operation) { System.out.println(this); } public String toStringO { retum "Fat id: " + id; } }III'-

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: //: concurrency/Semaphore Demo.j ava // Prueba de la clase Pool irnport j ava.ut }

2173 Piensa en Java il.concurrent.*; irnport java.til.*; irnport static net.mindview.til.Print.*; // Una tarea para extraer un recurso de un conjunto compartido: class CheckoutTask<T> implements Runnable { private static int counter = 0; private final int id = counter-n-; private Pool<T> pool; public CheckoutTask(Pool<T> pool) { this.pool *= pool ;public void run() { cry { T item = pool.checkout(); print(this + "checked out " + item); TimeUnic.SECONDS.sleep(1) ; print (this + "checkir.g in " + item); pool.checkin(item); ) catch(InterruptedException e) { // Forma aceptable de terminar

public String toStringO { return "CheckoutTask n + id + " "/

} public class SemaphcreDe roc { final static int SIZE = 25; public static void main(String[] args) throws Exception { final Pool<Fat> pool = new Pool<Fat>(Fat.class, SIZE); ExecutorServi ce exec }

2174 Piensa en Java Executors.newCachedThreadPool( ); for (int i * 0; i < SIZE; i++) exec.execute(new CheckoutTask<Fat>(pool)); print ("All CheckoutTasks created1); lii3t<Fat> list = new ArrayList<Fat> () ; for(int i * 0; i < SIZE; i++) { Fat f = pool.checkout(); printnb(i ": main() thread checked out "); f.operation(); list.add(f);

} Future<?> blocked = exec.submit(new Runnable() { public void run() { try { // El semforo impide extracciones adicionales, // por lo que se bloquea la llamada: pool.checkout(); } catch(InterruptedException e) { print("checkout() Interrupted");

}>; TimeUnit.SECONDS.sleep(2); blocked.cancel(true) j // Salir de la llamada bloqueada print("Checking in objects in " + list); for(Fat f : list) pool.checkin(f) ; for(Fat f : list) pool.checkin(f); // Segunda devolucin ignorada exec.shutdown();

2175 Piensa en Java } } /* (Ejecutar para ver la salida) *///:-

En iuain( ). se crea un objeto Pool para almacenar los objetos Fat y un conjunto de tareas CheckoutTask comienza a gestionar el conjunto compartido. Entonces, la hebra main( ) comienza a extraer objetos Fat sin devolverlos. Una vez que ha extrado todos los objetos del conjunto compartido, el semforo no permitir 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.

Este ejemplo depende de que el cliente de Pool sea riguroso y devuelva voluntariamente los elementos, lo cual es la solucin ms simple, siempre que funcione. Si no podemos confiar siempre en esto, Thinking in Patterns (en www. MindView.net) contiene anlisis adicionales de formas que pueden emplearse para gestionar los objetos extrados de conjuntos 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 tarca 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 Fat. 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 contenedores List<T> habrn sido intercambiados: //: concurreney/ExchangerDe mo.j ava import j ava.util.concurrent.*; import java.util.*; }

2176 Piensa en Java import net.mindview.Util.*; class ExchangerProducer<T> implements Runnable { private Generator<T> generator; private Exchanger<List<T>> exchanger; private List<T> holder; ExchangerProducer(Exchanger<List<T>> exchg, Generator<?> gen, List<T> holder) { exchanger * exchg; generator = gen; this.holder - holder;

} public void runO { try { wh-i 1 ( !Thread . inf.Arrupfcpd () ) { for (int i = 0; i < ExchangerDemo.size; i+ +) holder.add(generator.n ext')) ; // Intercambiar el contenedor vaco por el lleno: holder = exchanger.exchange(holder);

} } catch(InterruptedException e) { // OK terminar de esta forma.

2177 Piensa en Java } class ExchangerConsumer<T> implements Runnable { private Exchanger<List<T>> exchanger; private List<T> holder; private volatile T value,ExchangerConsumer(Exchanger<List< T>> ex, List<T> holder) { exchanger = ex; this.holder = holder;

} public void run() { try { while('Thread.interrupted()) { holder = exchanger.exchange(hold er); for(T x : holder) { value = x; // Extraer valor holder.remove(x); // OK para CopyOr.WriteArrayList

} } catch(InterruptedSxception e) { // OK terminar de esta forma.

} System.out.printIn("Final value: " + value);

2178 Piensa en Java } public class ExchangerDemo { static int size = 10; static int delay 5; // Segundos public static void main(String[] args) throws Exception { if(args.length > 0) size = new Integer(args[0] ) ; if(args.length > 1) delay = new Integer(args[1]); ExecutorService exec = Executors.newCachedThreadPool(); Exchanger<List<Fat>> xc = new Exchanger<List<Fat>>(); List<Fat> producerList = new CopyOnWriteArrayList<Fat>(), consumerList = new CopyOnWriteArrayList<Fat>() ; exec.execute(new ExchangerProducer<Fat>(xc, BasicQenerator.create(Pat .class), producerList)); exec.execute( new ExchangerConsumer<Pat>(xc,consumerList)); TimeUn i t.S ECONDS.sleep(delay); exec.shutdownNow();

} } /* Output: (Sample) Final value: Fat id: 29999 *///:-

En muin( ), se crca un nico objeto Exchanger para que lo empleen ambas tarcas, y dos contenedores CopyOn- WrteArrayList para intercambiar. Esta variante concreta de List permite que se invoque el mtodo reniove() mientras que se est recorriendo la lista sin que se genere una excepcin ConcurrcntxYlodificationException. ExchangerProducer rellena una lista y luego intercambia la lista llena por la vaca que ExchangerConsumer le entrega. Debido a la existencia del objeto Exchanger, el llenado de una lista y el consumo de la otra pueden tener lugar simultneamente. }

2179 Piensa en Java Ejercicio 34: (1)Modifique ExchangerDcmo.java para utilizar su propia clase en lugar de Fat. 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 t'cil de programar. Muchos juegos informticos y animaciones por computadora en las pelculas son simulaciones, y HorseRace.java y Green houscScheduler.ja va. 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 un nmero limitado de servidores. Es posible construir la simulacin para determinar el nmero ideal de servidores.

En este ejemplo, cada cliente del banco requiere una cierta cantidad de tiempo de servicio, que es el nmero de unidades de tiempo que el cajero debe invertir en el cliente para satisfacer sus necesidades. La cantidad de tiempo de servicio ser diferente para cada cliente y se determinar aleatoriamente. Adems, no sabemos cuntos clientes llegarn en cada intervalo, por lo que esto tambin se determinar aleatoriamente. //: concurrency/BankTellerSimula tion.java // Utilizacin de colas y mecanismos multihebra. // {Args: 5} import java.til.concurrent.*; import java.til.*; // Los objetos de slo lectura no requieren sincronizacin: class Cu3tomer { private final int serviceTime; public Customer(int tm) { serviceTime = tm; } public int getServiceTime{) { return serviceTime; } public String }

2180 Piensa en Java toStringO { return "[" + serviceTime + n]";

} // Mostrar a la fila de clientes cmo visualizarse: class CustomerLine extends ArrayBlockingQueue<Customer> { public CustomerLine(int maxLineSize) { super(maxLineSize);

} public String toStringO { if(this.size() == 0) return " [Empty]"; StringBuilder result = new StringBuilder(); for(Customer customer : this) result.append(customer); return result.toScring();

} // Aadir clientes aleatoriamente a una cola: class CustomerGenerator implements Runnable { private CustomerLine customers; private static Random rand = new Random(47); public CustomerGenerator(CustomerLine cq) { customers = cq;

} public void run() ( try { }

2181 Piensa en Java whi ie(!Thread.int errupted()) { TimeUnit.MILLISECONDS.sle ep(rand.nextInt(300)); customers.put(new Customer(rand.nextInt(100 0)));

} } catch(InterruptedException e) { System.out.printIn("CustomerGenerator interrupted");

} System.out.println("CustomerGenerator terminating");

) class Teller implements Runnable, Comparable<Teller> { private static int counter *= 0; private final int id counter**

21 Concurrencia 2182 ;// Clientes servidos durante este intervalo: private int customersServed = 0; private CustomerLine customers; private boolean servingCustomerLine = true; public Teller(CustcmerLine cq> { customers = cq; } public void run() { try { while(!Thread.interruptea()) { Customer customer = customers.take(); TimeUnit.BILLISECONDS.sleep( customer.ge tServiceTime()); synchronized(this) { customersServed++; while(! servingCustomerLine) wait () ;

} } catch(InterrupteaException e) { System.out.printIn(this * "interrupted");

} System.out.printIn(this + "terminating");

} public synchronized void doSomethingElse() { customersServed = 0; servingCustomerLine = false;

} public synchronized void serveCustomerLine() ( assert !servingCustomerLine:"already serving: " + this; servingCustomerLine = true; notifyAllO ;

} public String toStringO { return "Teller " + id + n n; } public String shortString() { return "T" + id; ) }

21 Concurrencia 2183 // Usado por la cola de prioridad: public synchronized int compareTo(Teller other) { return customersServed < other.customersServed ? -1 : (customersServed == other.customersServed ? 0 : 1);

) class TellerManager implements Runnable { private ExecutorService exec; private CustomerLine customers; private PriorityQueue<Teller> workingTellers = new PriorityQueue<Teller>(); private Queue<Teller> tellersDoingOther'ihings = new LinkedList<Teller> (); private int adjustmentPeriod; private static Random rand = new Random(47); public TellerManager(ExecutorService e, CustomerLine customers, int adjustmentPeriod) { exec = e; this.customers = customers; this.adjustmentPeriod = adjustmentPeriod; // Comenzar con un tinico cajero: Teller teller = new Teller(customers); exec.execute(teller); workingTellers.add(teller) ;public void adjustTellerNumber {) ( // Esto es realmente un sistema de control. Ajustando // los nmeros, podemos descubrir problemas de estabilidad // en el mecanismo de control. // Si la fila es demasiado larga, aadir otro cajero: if(customers.size() / workingTellers.size() >2) { // Si los cajeros estn descansando o haciendo // otra tarea, decir a uno que venga: if (tellersDoingOtherThings.sizeO >0) { Teller teller tellersDoingOtherThir.gs.remove O ; teller.serveCustomerLine(); workingTellers.offer(teller); return;

} //en caso contrario, crear (contratar) un nuevo cajero }

21 Concurrencia 2184 Teller teller = new Teller(customers); exec.execute(teller); workingTellers.add(teller); return;

} // Si la fila es lo suficientemente corta, eliminar un cajero: if(workingTellers.size() > 1 && customers.size() / workingTellers.size() < 2) reassignOneTeller(); // Si no hay una fila, slo hace falta un cajero: if(customers.size() == 0) while(workingTellers.size() > 1) reassignOneTeller();

) // Dar a un cajero un trabajo diferente o un descanso: private void reassignOneTellerO { Teller teller = workingTellers.poll(); teller .doSomethingElse () ,* tellersDoingOtherThings.offer(teller);

} public void run() { try { while(!Thread.interrupted()) { TimeUnit.MILLISECONDS.sleep(adjustmentPeriod) ; adjustTellerNumber(); System.out .print (customers + " ( 11); for(Teller teller : workingTellers) System.out.print(teller.shortString() + " "); System.cut.println("}n);

} } catch(InterruptedException e) { System.out.println(this + interrupted");

} }

21 Concurrencia 2185 System, out .println (this + "terminating'1);

} public String toStringO { return "TellerManager "; } public class BankTellerSimulation { static final int MAX_LINE_SIZE = 50; static final int ADJUSTMENT PERIOD = 1000; public static void main(String[] args) throws Exception { ExecutorService exec = Executors.newCachedThreadPool(); // Si la fila es muy larga, los clientes se irn: CustomerLine customers =new CustomerLine(MAX_LINE_SIZE); exec.execute(new CustomerGenerator(customers)); // Bl director aadir o quitar cajeros segn sea necesario: exec .execute (new TellerManager ( exec, customers, ADJUSTMENT_PERIOD)); if(args.length >0) // Optional argument TimeUnit.SECONDS.sleep(n ew Integer(args[01)); else { System.out.printlnCPress ' Enter' to quit") ; System.in.read() ;

} exec.shutdownNow();

} } /* Output: (Sample) [429] [200] [207] Teller interrupted Teller terminating Teller interrupted Teller } 2 2 1 1

{ T0 TI )

21 Concurrencia 2186 terminating TellerManage r interrupted TellerManage r terminating Teller 3 interrupted Teller 3 terminating Teller 0 interrupted Teller 0 terminating CustomerGene rator interrupted CustomerGene rator terminating *///:-

Los objetos Customer son muy simples, conteniendo nicamente im campo final int. Puesto que estos objetos nunca cambian, 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 completado, 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 ArrayBlockingQueue que tiene un mtodo toString( ) que imprime los resultados de la forma deseada.

Con cada objeto CustomerLine se asocia un objeto CustomerGenerator, que introduce clientes en la cola a intervalos aleatorios.

Cada objeto Teller extrae clientes de la cola CustomerLine y los procesa de uno en uno, llevando la cuenta del nmero de clientes a los que lia servido durante ese intervalo concreto. Se le puede decir al cajero que haga alguna otra cosa con doSomethingElse() cuando no haya suficientes clientes o que atienda a la fila con serveCustomerLine() }

21 Concurrencia 2187 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 servidos, de modo que una cola PriorityQueue puede seleccionar automticamente al cajero 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( )t que es un sistema de control para agregar 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 determinar la carga que el grupo de servidores puede gestionar

2188 Piensa en Java .Simulacin de un restaurante

Esta simulacin retoma el ejemplo simple Restaurant.java mostrado anteriormente en el captulo, aadiendo ms componentes de simulacin, como los pedidos (Order) y los platos (Pate), y reutiliza las clases men del Captulo 19, Tipos enumerados.

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 viceversa. Es como si estuviramos entregando un objeto a alguien (no hay ninguna mesa sobre el que ponerlo, por loque slo funciona si la otra persona est tendiendo una mano hacia nosotros y lista para recibir el objeto). En este ejemplo, SynchronousQueue representa el espacio situado delante del comensal, para hacer hincapi en la dea 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: //: concurrency/restaurant2/Restauran tWithQueues.java // {Args: 5} package concurreney.re s tauran12; import enumerated.men.*; import java.til.concurrent.*; iraport j ava. ut i 1. *; import static net.mindview.util.Print.*; // Se entrega al camarero, que a su vez se lo da al: class Order { // (un objeto de transferencia de datos) private static int counter 0; private final int id counter++; private final Customer customer; private final WaitPerson waitPerson; private final Food food; public Order (Customer cust, VaitPerson wp, Food f) { customer = cust; waitPerson = wp; food = f; class Customer implements Runnable {

2189 Piensa en Java } public Food item() { return food; } public Customer getCustomer() { return customer; ) public waitPerson getwaitPerson() { return waitPerson; } public String toStringO { return "Order: " + id + " item: M + food + " for: M + customer " servea by: " + waitPerson;

} // Esto es lo que el chef devuelve: class Pate { private final Order order; private final Food food; public Pate(Order ord, Food f) { order = ord; food = f;

) public Order getOrder() { return order; ) public Food getFoodO { return ood; ) public String toStringO { return fooa.toString(); }private static int counter = 0; private final int id = counter++; private final WaitPerson waitPerson; // Slo se puede recibir un piato cada vez: private SynchronousQueue<Pate> placeSetting = new SynchronousQueue<Plate>(); public Customer(WaitPerson w) ( waitPerson = w; } public void deliver(Plate p) throws InterruptedException { // Slo se bloquea si el cliente est todavia // comiendo el piato anterior: placeSetting.put(p);

) public void run() { class Customer implements Runnable {

2190 Piensa en Java for(Course course : Course.values() ) { Food food = course.randomSelection() ; try { waitPerson.placeOrder{this, food); // Se bloquea hasta que se entregue el piato: print(this + "eating " + placeSetting.take(}) ; } catch(InterruptedException e) { print(this + "waiting for " + course + " interrupted"); break;

} print(this * "finished meal, leaving");

} public String toStringO { return "Customer " + id + n ";

} class WaitPerson implements Runnable { private static int counter = 0; private final int id = counter-*-+; private final Restaurant restaurant; BlockingQueuecPlate: filledOrder? = new Linked5lockingQueue<Plate>(); public WaitPerson(Restaurant rest) { restaurant = rest; } public void placeOrder(Customer cust, Food food) { try { // No debera bloquearse en la prctica, porque es una cola // LinkedBlockingQueue sin lmite de tamao: restaurant.orders.put(new Order(cust, this, food)); } catch(InterruptedException e) { print(this + n placeOrder interrupted");

class Customer implements Runnable {

2191 Piensa en Java }

) public void run() { try { while(iThread.interrupted()) { // Se bloquea hasta que est listo un plato Plate plate = filledOrders.take(); print(this + "received " + plate + " delivering to + plate.getOrderO .getCustamer () ) ; plate.getOrder().getCustomer().deliver(plat e);

) ) catch(InterruptedException e) { print (this " interrupted");

) print(this + " off duty");

} public String toStringO { return "WaitPerson " + id + " ";

} class Chef implements Runnable { private static int counter = 0; private final int id counter++; private final Restaurant restaurant; private static Random rand = new Random(47); public Chef(Restaurant rest) { restaurant = rest; } public void run() { try { while(!Thread.interrupted 0) { // Se bloquea hasta que aparece un pedido: Order order = restaurant.orders.take<) ; class Customer implements Runnable {

2192 Piensa en Java Food requestedltem = order.itemO; // El tiempo para preparar el pedido: TimeUnit.MILLISECONDS.sleep(rand.nextInt(500)); Plate plate = new Plate(order, requestedltem); order .getWaitPersonO . filledOrders .put (plate) ,*

} } catch(InterruptedException e) { print (this + 11 interrupted");

} print(this + " off duty");

) public String toStringO { return "Chef " + id + " w;

} class Restaurant implements Runnable { private List<WaitPerson> waitPersons = new ArrayList<WaitPerson>0; private Lit<Chef> chefa - new ArrayLi3t<Chef>{) / private ExecutorService exec; private static Random rand = new Random(47); BlockingQueue<Order> orders = new LinkedBIockingQueue<Order>(); public Restaurant(ExecutorService e, int nWaitPersons, int nChefs) ( exec = e; for(int i = 0; i < nWaitPersons; i^+) { WaitPerson waitPerson new WaitPerson (this) ; waitPersons.add(waitPerson); exec.execute(waitPerson);

) for(int i = 0; i < nChefs; i++) ( Chef chef = new Chef(this); che f s.add(chef); exec.execute(chef);

class Customer implements Runnable {

2193 Piensa en Java }

} public void run() { try {// Llega un nuevo cliente; asignar un camarero: WaitPerson wp = waitPersons.get( rand.nex tlnt (waitPersons.size())) ; Customer c = new Customer(wp); exec.execute(c); TimeUnit.MILLISECONDS.sleep(100);

} } catch(InterruptedException e) { print("Restaurant interrupted");

} print("Restaurant closing");

! public class RestaurantWithQueues { public static void main(String[] args) throws Exception { ExecutorService exec = Bxecutors.newCachedThreadPool(); Restaurant restaurant = new Restaurant(exec. 5, 2); exec.execute(restaurant); if{args.length > 0) // Argumento opcional TimeUnit.SECONDS.sleep(new Integer(args10])); else { printC'Press 'Enter' to quit"); System.in.read(); class Customer implements Runnable {

2194 Piensa en Java } exec.shutdownNow();

} ) /* Output: (Sample) WaitPerson 0 received SPRING_ROLLS delivering to Customer 1 Customer 1 eating SPRING_ROLLS WaitPerson 3 received SPRING_ROLLS delivering to Customer 0 Customer 0 eating SPRING_ROLLS WaitPerson 0 received BURRITO delivering to Customer 1 Customer 1 eating BURRITO WaitPerson 3 received SPRING_ROLLS delivering to Customer 2 Customer 2 eating SPRING_ROLLS WaitPerson 1 received SOUP delivering to Customer 3 Customer 3 eating SOUP WaitPerson 3 received VINDALOO delivering to Customer 0 Customer 0 eating VINDALOO WaitPerson 0 received FRUIT delivering to Customer 1 *///:-

Un aspecto muy importante de este ejemplo es la gestin de la complejidad utilizando colas para la comunicacin entTe tareas. Esta tcnica simplifica enormemente el proceso de la programacin 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 gestiona el objeto, tratndola como un mensaje, en lugar de recibir directamente mensajes. Si seguimos esta tcnica siempre que podamos, tendremos una mayor posibilidad de construir sistemas concurrentes robustos. Ejercicio 36: (10) Modifique RestaurantWithQueues.java para que haya un objeto OrderTicket (nota de pedido) por

cada mesa. Cambie order por ordcrTicket, y aada la clase Table (mesa), con mltiples clientes (Customer) por mesa. class Customer implements Runnable {

2195 Piensa en Java Distribucin de trabajo He aqu un ejemplo de simulacin simple donde se anan muchos de los conceptos vistos en el captulo. Considere una hipottica lnea de montaje robotizada para automviles. Cada objeto automvil (Car) ser construido en varias etapas, comenzando por la fabricacin del chasis y siguiendo por el montaje del motor, de la transmisin y de las ruedas

class Customer implements Runnable {

2196 Piensa en Java .//: concurrency/CarBuilder.j ava // Un ejemplo complejo de tareas que funcionan conjuntamente. ircport j ava .util, concurrent. * ; import java.util.*; import static net.mindview.util.Print.*; class Car { private final int id; private boolean engine ~ false, driveTrain = false, wheels = false; public Car(int idn) { id = idn; } // Objeto Car vacio: public Car() { id * -1; } public synchronized int getldO { return id; } public synchronized void addEngineO ( engine = true; } public synchronized void addDriveTrainO { driveTrain = true;

} public synchronized void addWheelsO { wheels = true; ) public synchronized String toStringO { return "Car " + id + " [" + " engine: " + engine + " driveTrain: " + driveTrain + " wheels: " -f wheels + " ] ";

> class CarQueue extends LinkedBlockingQueue<Car> (} class ChassisBuilder implements Runnable { private CarQueue carQueue; private int counter = 0; public ChassisBuilder(CarQueue cq) { carQueue = cq; } public void run() { try { while(!Thread.interrupted 0) { Timeuni t.MILLISECONDS.sleep(500); // Hacer chasis: Car c = new Car(counter**); print("ChassisBuilder created " + c); // Insertar en la cola carQueue.put(c);

} }

2197 Piensa en Java } catch(InterruptedException e) { orint (11 Interrupted: ChassisBuilder);

} print("ChassisBuilder off");

} class Assembler implements Runnable { private CarQueue chassisQueue, finishingQueue; private Car car; private CyclicBarrier barrier = new CyclicBarrier(4) ; private RobotPool robotPool; public Assembler(CarQueue cq, CarQueue fq, RobotPool rp) { chassisQueue = cq; finishingQueue = fq; robotPool = rp ;public Car cari) { return car; } public CyclicBarrier barrier() { return barrier; } 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); robctPool.hire(WheelRobot.class, this); barrier.await{); // Until the robots finish // Insertar coche en la cola de acabado (finishingQueue) // para trabajos adicionales finishingQueue.put(car);

} } catch(InterruptedException e) { print("Exiting Assembler via interrupt"); } catch(BrokenBarrierException e) ( // Queremos que nos informen de esta }

2198 Piensa en Java excepcin throw new RuntimeException(e);

} print ("Assembler off'1);

) class Reporter implements Runnable { private CarQueue carQueue; public Reporter(CarQueue cq) { carQueue = cq; } public void run() { try { while(!Thread.interrupted()) { print(carQueue.take());

} } catch(InterruptedException e) { print("Exiting Reporter via interrupt");

) print("Reporter off");

} abstract class Robot implements Runnable { private RobotPool pool; public Robot(RobotPool p) { pool p; } protected Assembler assembler; public Robot assignAssembler(Assembler assembler) { this.assembler = assembler; return this;

} private boolean engage = false; public

2199 Piensa en Java synchronized void engage() { engage = true; notifyAll();

} // La parte de run() que es diferente para cada robot: abstract protected void performService(); public void run() { try { powerDownO; // Esperar hasta que haga falta while(!Thread.interrupted()) { performService{); assembler.barrier().await{); // Sincronizar // Remos finalizado con este trabajo... DOwerDown();

) } catch(InterruptedException e) { print("Exiting " + this + " via interrupt0); } catch(BrokenBarrierException e) { // Queremos que nos informen de esta excepcin throw new RuntimeException(e);

} print(this + " off");

} private synchronized void powerDown() throws InterruptedException { engage = false; assembler = null; // Desconectar del ensamblador (Assembler) // Volver a ponernos er. la cola de disponibles: pool.release(this); while(engage == false) // Desconectar alimentacin wait();

} Dublic String toStringO { return getClass().getName(); }

2200 Piensa en Java } class EngineRobot extends Robot { public EngineRobot(RobotPool pool) ( super(pool); } protected void performService() { print(this + " installing engine"); assembler.car().addEngine();

} class DriveTrainRobot extends Robot { public DriveTrainRobot(RobotPool pool) { super(pool); } protected void performService() { print(this + " installing DriveTrain"); assembler.car().addDriveTrain();

} class WheelRobot extends Robot { public WheelRobot(RobotPool pool) { super(pool); } protected void performService() { print(this + " installing Wheels"); assembler.car().addWheels();

} class RobotPool { // Impide silecosamente que existan entrada idnticas: private Set<Robot> pool = new HashSet<Robot>(); public synchronized void add(Robot r) ( pool.add(r); notifyAll();

} public synchronized void hire(Class<? extends Robot> robotType, Assembler a )throws InterruptedException { for(Robot r : pool) if(r.getClass(). equals(robotType }

2201 Piensa en Java )) { pool.remove(r) ; r.assignAssemble r(a); r.engage(); // Encenderlo para realizar la tarea retum;

} wait(); // Ninguno disponible hire(robotType, d); // Intentar de nuevo recursivamente

} public synchronized void release(Robot r) { add(r); }

} public class CarBuilder { public static void main (String [] args) throws Exception { CarQueue chassisQueue = nev/ CarQueue(), finishingQueue = new CarQueue()? ExecutorService exec = Executors.newCachedThreadPool(); RobotPool robotPool = new RobotPool(); exec.execute(new EngineRobot(robotPool)); exec.execute(new DriveTrainRobot(robotPool)); exec.execute(new WheelRobot(robotPool)); exec.execute(new Assembler( chassisQueue, finishingQueue, robotPool)); exec.execute(new Reprter(finishingQueue)); // Comenzar a funcionar produciendo un chasis: exec .execute (new ChassisBuilder(chassisQueue)) ,* TimeUnit.SECONDS.sleep(7); exec.shutaownNow(); }

2202 Piensa en Java } } /* (Ejecutar para ver la salida) *///:~

Los objetos Car se transportan de un lugar a otro mediante una cola CarQueue, que es un tipo de LinkedBIoeklngQueue. Un objeto ChassisBuilder crea un chasis de coche y lo coloca en una cola CarQueue. K1 objeto Assembler extrae los objetos Car de una cola CarQueue y adquiere objetos Robot para trabajar con ellos. Una barrera CyclicBarrier permite que Assembler espere basta 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 Reprter, que simplemente imprime los datos del objeto Car para demostrar que las tareas se han completado apropiadamente.

Los objetos Robot se gestionan mediante un conjunto compartido y cuando hace falta realizar un trabajo, se extrae el objeto 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 LinkedBlockingQueuc, no importara si iniciramos la tarea de construccin del chasis en primer lugar). Observe que este programa se ajusta a todas las directrices relativas al tiempo de vida de los objetos presentadas en este captulo, de manera que el proceso de terminacin resulta seguro.

Observar que Car tiene definidos todos sus mtodos como synchronized. En realidad, en este ejemplo, esto es redundante, porque dentro de la fbrica, los objetos Car se desplazan a travs de colas y slo una tarea puede estar trabajando en un cieno coche en un determinado momento. Bsicamente, las colas fuerzan a realizar un acceso sealizado 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 funcionar.

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 }

2203 Piensa en Java que podemos caracterizar est

2204 Piensa en Java eenfoque 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 esia regla no es impedimos 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 aJ 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.concurrent 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 BIockingQueue) y cules otras se usan exclusivamente para mejorar el rendimiento. En esta seccin, vamos a examinar algunos de los problemas y de las clases relativos a las tcnicas de optimizacin 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.

2205 Piensa en Java 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 j ava.ut i1.concurrent.locks.*; abstract class Incrementable { protected long counter = 0; public abstract void increment();

} class SynchronizingTesr. extends Incrementable { public synchronized void incremento { +j-counter; }

} class IiOckingTest extends Incrementable { prvate Lock lock = new ReentrantLock{}; public void increment() { lock.lo ck(); try { ++counter; } finaily { lock.unlockO ;

} public class SimpleMicroBenchmark { static long test(Incrementable incr) { long start = System.nanoTime(); for(long i = 0; < 10000000L; i++)

2206 Piensa en Java incr.increment(>; return System.nanoTimeO - start;

} public static void main (String l] args) { long synchTime = test(new SynchronizingTest{)); long lockTime = test(nev; LockingTest ()); System.out.printf("synchronized: %l$10d\n", synchTime); System.out .printf ("Lock:%l$10d\n", lockTime); System.out.printf("Lock/synchronized = %l$.3fu, (double)lockTime/(double)synchTime);

} } /* Output: (75% match) synchronized: 244919117 Lock: 939098964 Lock/synchronized = 3.834 *///:-

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. 70 Generalmente, este trmino se refiere 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 escribir estos tipos de pruebas.

Existen diversos problemas en el ejemplo anterior. En primer lugar, slo podremos ver la verdadera diferencia de rendimiento 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. 25 Brian Goetz me ayud mucho, explicndome estos problemas. Consulte su artculo en mvwt28.ibni.com/developerworks/libraiy/j-jtpl2214 para conocer ms deralles acerca de las medidas de rendimiento.
70

2207 Piensa en Java 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 difcil 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 cucnta de que los valores no estn siendo utiI7ados nunea). Adems, el clculo debe ser complejo y lo suficientemente 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.nextlnt() en los bucles principales) y utilizando esos valores en un sumatorio: //: concurrency/SynchronizationComparisons.j ava // Comparacin del rendimiento de objetos Lock y Atomic // explcitos y la palabra clave synchronized. import j ava.ut i1.concurrent.* ; imporl java.til.concurrent.atomic.*; import j ava.til.concurrent.locks.*; import java.util.*; import static net.mindview.util.Print.*; abstract class Accumulator { public static long eyeles = 50000L; // Numero de modificadores y lectores durante cada prueba: private static final int N = 4; public static ExecutorService exec =Executors.newFixedThreadPool (N*2) ; private static CyclicBarrier barrier = new CyclicBarrier (N*2 + 1) ; protected volatile int index = C; protected volatile long value = 0; protected long duration = 0; protected String id = "error"; protected final static int SIZE = 100C00; protected static int[j preLoaded = new int[SIZE]; static { // Cargar la matriz con nmeros aleatorios: Random rand = new Random(47); for(int i = 0; i < SIZE; i++) preLoaded[i] = rand.nextInt();

) public abstract void accumulate{); public abstract long read(); private class Modifier implements Runnable { public void run() { for(long i = 0; i < cycles; i++) accumulate(); try { barrier.await(); } catch(Exception e) { throw new RuntimeException(e);

2208 Piensa en Java i

} private class Reader implements Runnable { private volatile long value; public void run() { for (long i = 0; i < cycles; i++) value = readt); try { barrier.await(); } catch(Exception e) { throw new RuntimeException(e);

} public void timedTest() { long start = System.nanoTime(); for (int i = 0; i < N; i++) { exec.execute(new Modifier()); exec.execute(new Reader());

) try ( barrier.await(); } catch(Exception e) { throw new RuntimeException(e);

) duration = System.nanoTime() - start; printf(%-138: %13d\nWM/ id, duration);

2209 Piensa en Java } public static void report(Accumulator accl, Accumulator acc2) { printf(M%-22s: %.2f\n", accl.id + "/" + acc2.id, (double)accl.duration/(double)acc2.duration) ;class BaseLine extends Accumulator { { id = "BaseLine"; } public void accumulate() { value += preLoaded[index+-] ; if(index >- SIZE) index = 0;

} public long read() { return value; }

) class SynchronizedTest extends Accumulator { ( id a "synchronized"; } public synchronized void accumulate() { value +- preLoaded [ir.aex++} ; if(index >= SIZE) index = 0;

} public synchronized long read() { return value;

} class LockTest extends Accumulator { { id = "Lock"; } private Lock lock = new ReentrantLock(); public void accumulate 0 { lock.lock(); try { value += preLoaded[indexH+] ; if(index >= SIZE) index = 0; } finally { lock.unlock()

2210 Piensa en Java } public long read() { lock.lock() ; try { return value; } finally { lock.unlock();

} class AtomicTest extends Accumulator { { id = nAtomic"; } private Atomiclnteger index = new Atomiclnteger(0); private AtomicLong value = new AtomicLong(0); public void accumulate() { // Vaya! Depender de ms de un objeto Atomic a la vez // no funciona.Pero sigue dndonos un indicador de // rendimiento: int i = index.getAndIncrement(); value.getAndAdd(preLoaded tij); if(++i >= SIZE) index.set(0);

} public long read() { return value.get(); }

} public class SynchronizationComparisons { static BaseLine baseLine = new BaseLine(); static SynchronizedTest synch = new SynchronizedTest()

;static LockTest lock = new LockTestO; static AtomicTest atomic = new AtomicTest0 ; static void test() { print ( " = = = == = = = = = = = = = ==rt*as = = = = = = = = n ) j printf("%-12s : %13d\n"f "Cycles", Accumulator.cycles); baseLine.timedTest() ; synch.timedTest(); lock.timedTest() ; atomic.timedTest()? Accumulator.report(synch, baseLine); Accumulator, report (lock, baseLine) ,Accumulator.report{atomic r baseLine); Accumulator.report(synch, lock); Accumulator.report(synch, atomic); Accumulator.report(lock, atomic);

) public static void main(String[] args) { int iterations 5; // Predeterminado if(args.length >0) // Cambiar opcionalmente las iteraciones iterations = new Integer(args[0]) ; // La primera vez rellena el conjunto compartido de hebras: print ("Warmup') ; baseLine.timedTest() ; // Ahora la prueba inicial no incluye el coste de // iniciar las hebras por primera vez. // Generar mltiples puntos de datos: for (int i = 0; i < iterations; i++) { test(); Accumulator.cycles *= 2;

i Accumulator.exec.shutdown();

2212 Piensa en Java

Cycles : 200000 BaseLine : 80176670 synchronized : 5455046661 Lock : 177686829 Atomic : 1017891S4 Cycles : 400000 BaseLine : 160383513 synchronized : 780052493 Lock : 362187652 Atomic : 202030984 Cycles : 800000 BaseLine : 322064955 synchronized : 336155014 Lock : 704615531 Atomic : 393231542 1600000 650004120 52235762925 1419602771 796950171 80.36 2.18 1.23 36.80 65.54 1.78 Cycles BaseLine synchronized Lock Atomic synchronized/BaseLine Lock/BaseLine Atomic/BaseLine synchroni zed/Lock synchron i zed/At omi c Lock/'Atomi c 3200000 1285664519 96336767661 2846988654 1590545726 74 .93 2.21 1.24 33.84 60.57 1.79 Cycles BaseLine

synchroni zed Lock Atomic synchronized/BaseLine Lock/BaseLine Atomic/BaseLine synchronized/Lock synchronized/Atomic Lock/Atomic *///:

-Este programa utiliza el patrn de diseo basado en plantillas71 para poner todo el cdigo comn en la clase base y aislar todo el cdigo variante en las implementaciones de accuniulate( ) y read() en las clases derivadas. En cada una de las clases derivadas SynchronizcdTest. LockTes y AtomicTest, podemos ver cmo accumulate() y read() expresan diferentes formas de implemcntar 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 CydicBarrier 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 principio cuando se alcanza el final de la matriz) y se aade otro nmero generado aleatoriamente a valu. Las mltiples tareas Modifler y Rcadcr 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, bsicamente, si hay ms de un objeto Atomic implicado, probablemente nos tengamos que dar por vencidos y utilizar mutex ms convencionales (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 predeterminado. 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 eficiente que la utilizacin de Lock o Atomic. Pero repentinamente, se cruza un umbral y synchronized parece ser
71

Vase Thinking in Patterns en mvw.MindView.net.

bastante ineficiente, mientras que Lock y Atomic parecen mantener aproximadamente las prestaciones relativas a la pmeba 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 perodos de tiempo ms largos. Algunas optimizaciones 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 tambin 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 primer 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 prctica, las secciones protegidas con mutex pueden tener un tamao mayor que en el ejemplo anterior, por lo que el porcentaje 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 forma de saberlo es (y slo en el momento en que estemos realizando las actividades de rencimiento, no antes) probar las distintas tcnicas y ver el impacto que tienen.

21 Concurrencia 2217

En segundo lugar, est claro, al leer el cdigo contenido en este captulo, que la palabra clave synchronized permite un cdigo mucho ms legible que la sintaxis lock-try/finally-unlock que Lock requiere, y esa es la razn por la que en este captulo hemos utilizado principalmente la palabra clave synchronized. Como hemos dicho en otros lugares del libro, resulta mucho ms normal leer 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 crtica. Como resultado, tiene bastante sentido comenzar utilizando la palabra clave synchronized y cambiar nicamente a objetos J.ock si la optimizacin del rendimiento 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 Atomic slo son tiles en casos muy simples, generalmente cuando slo hay un objeto Atomic que est siendo modificado y cuando dicho objeto sea independiente de todos los dems objetos. Resulta ms seguro comenzar con otras tcnicas ms tradicionales de mutex y slo tratar de cambiar a Atomic posteriormente, si as lo exige la optimizacin del rendimiento. Contenedores libres de bloqueos

Como hemos indicado en el Captulo 11, 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 primitivos como Vector y Ilashtable tenan muchos mtodos synchronized, que hacan que se incurriera en un coste inaceptable cuando no se los estaba utilizando en aplicaciones multihcbra. 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 costc 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 contenedores pueden tener lugar al mismo tiempo que las lecturas, siempre y cuando los lectores slo puedan ver el resultado de las modificaciones completadas. Cada modificacin se realiza en una copia separada de la estructura de datos (o en ocasiones 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 estructura de datos "principal, despus de lo cual los lectores podrn ver la modificacin.

2218 Piensa en Java

En CopvOnWriteArrayList, una escritura har que se cree una copia de toda la matriz subyacente. 1.a 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 lecturas podrn ver la informacin. Una de las ventajas de CopyOnWriteArrayList 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 ocurra en el pasado.

CopyOnVVriteArraySet utiliza CopyOnWriteArrayList para conseguir un comportamiento libre de bloqueos.

ConcurrentHashMap y ConcurrentLinkedQucue emplean tcnicas similares para permitir lecturas y escrituras concurrentes, 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. ConcurrentHashMap no genera la excepcin ConcurrentModificationException. Problemas de rendimiento

Mientras que estemos principalmente leyendo de un contenedor libre de bloqueos, ste ser mucho ms rpido que otro basado en synchronized, 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: //: concurrency/Tester.java // SisCema para probar el rendimiento de los contenedores concurrentes, import java.util.concurrent.*; import net.mindview.util.*;public abstract class Tester<C> { static int testReps = 10; static int testCycles = 1000; static int containerize = 1000; abstract C containerlnitializer() ; abstract void startReadersAndWriters() ; C testContainer; String testld; int nReaders; int nWriters; volatile long readResult = 0; volatile long readTime = 0; volatile long writeTime = 0; CountDownLatch endLatch; static ExecurorService exec = Executors.newCachedThreadPool();

21 Concurrencia 2219

Integer[] writeData; Tester(String testld, int nReaders, int nWriters) { this.testld = testld + " + nReaders + "r " + nWriters + nnw"; this.nReaders = nReaders; this.nWriters nWriters; writeData = Generated.array(Integer.class, new RandomGenerator.Integer(), containersize); for (int i = 0; i < testReps; i++) { runTest(); readTime = 0; writeTime = 0;

} void runTest() { endLatch = new CountDownLatch(nReaders + nWriters); testContainer = containerlnitializer(); startReadersAndWriters(); try { endLatch.await(); } catch(InterrupteaSxception ex) { System.out.println("endLatch interrupted);

System.out.printf("%-27s %14d %14d\n", testld, readTime, writeTime); if (readTime 0 && writeTime != 0) System.out.printf("%-27s %14d\nB, "readTime * writeTime readTime + writeTime);

) abstract class TestTask implements Runnable { abstract void test(); abstract void putResults(); long duration; public void run() { long startTime = System.nanoTime(); test(); duration = System.nanoTime() - startTime; synchronized(Tester.this) { putResults();

) endLatch.countDown(); )public static void initMain (String [] args) { if(args.length > 0) testReps = new Integer(args[0] ); if(args.length > 1)

2220 Piensa en Java

testCycies = new Integer(args[1] ) ; if(args.length > 2) containerSize = new Integer(args[2] ); System.out.printf("%-27s %14s %14s\n", ''Type", "Read time", "Write time");

} //./:El mtodo abstracto containerLnitializcr() devuelve el contenedor inicializado que hay que probar, que se almacena en el campo testContainer. El otro mtodo abstracto startReadersAndWrters(), inicia las tarcas 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 escrituras (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 startReadersAnd\Vriters() 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 test( ), y luego llama a putResults() dentro de un bloque de tipo synchronized para almacenar los resultados. Para usar este sistema (en el que podemos reconocer el patrn de diseo basado en el mtodo de plantillas), debemos heredar de Tester para el tipo de contenedor concreto que queramos probar, y proporcionar las apropiadas clases Reader y Writer: //: concurrency/ListComparisons.java // {Args: 1 10 10) (Prueba rpida ae verificacin durante la construccin) // Comparacin aproximada del rendimiento de listas compatibles con hebras. import j ava.ut i1.concurrent.*; import java.util.*; import net.mindview.util.*; abstract class ListTest extends Tester<List<Integer>> { ListTest(String testld, int nReaders, int nWriters) { super(testld, nReaders, nWriters);

} class Reader extends TestTask { long result: = 0; void test() { for(long i = 0; i < testCycies; i++) for(int index = 0; index <

21 Concurrencia 2221

containerSize; index++) result += testContainer.get(index);

} void putResultsO { readResult += result; readTime +* duration;

} class Writer extends TestTask { void test() { for (long i = 0; i < testCycies; i + + ) for(int index = 0; index < containerSize; index++) testContainer.set(index, writeData[index]);

) void putResultsO { writeTime += duration

i++) 2222 Piensa en Java ;}

} void startReadersAndWriters(> { for (int i = 0; i < nReaders; i++) exec.execute(new Reader()); for(int i = 0; i < nWriters; exec.execute (new WriterO);

} class SynchronizedArrayListTest extends ListTesL { List<Integer> containerlnitializer() { return Collections.synchronizeaList ( new ArrayList<Integer>( new CountinglntegerList(containerSize)>);

) SynchronizedArrayListTest(int nReaders, int nWriters) { super("Synched ArrayList", nReaders, nWriters);

} class CopyOnWriteArrayListTest extends ListTest { List<Integer> containerlnitializer() { return new CopyOnWriteArrayList<Integer >( new CountinglntegerList(containe rSize));

){

i++) 2223 Piensa en Java } CopyOnWriteArrayListTest(int nReaders, int nWriters super(HCopyOnWriteArrayList, nReaders, nWriters)

} public class ListComparisons { public static void main(String[] args) { Tester.initMain(args); new SynchronizedArrayListTest( 10, 0); new SynchronizedArrayListTest( 9, 1); new SynchronizedArrayListTest( 5, 5); new CopyOnWriteArrayListTest(1 0, 0); new CopyOnWriteArrayListTest: (9, 1); new CopyOnWriteArrayListTest(5 , 5); Tester.exec.shutdown();Output : (Samp1e) Read time 232158294700 198947618203 223866231602 117367305062 249543918570 758386889 741305671 877450908 212763075 68180227375 Write time 0 24918613399 lOr Ow 9r lw 5w 10r Ow 9r lw 132176613508

136145237 67967464300

} } /' Type Synched ArrayLisL Synched ArrayList readTime + writeTime Synched ArrayList 5r readTime + writeTime CopyOnWriteArrayList CopyOnWriteArrayList readTime + writeTime = CopyOnWriteArrayList 5r 5w readTime + writeTime = *///:-

i++) 2224 Piensa en Java

En ListTest, las clases Reader y Writer realizan las acciones especficas para un contenedor List<Integer>. En Rcader.putRcsults(), la duracin (duration) se almacena, al igual que el resultado (result), para impedir que los campos sean optimizados por el compilador. startReadersAnd\Vriters() se define a continuacin para crear y ejecutar los objetos lectores y escritores especficos.

i++) 2225 Piensa en Java

Una vez creada la lista ListTest, hay que heredar de ella para sustituir containerInitiaIizer() con el fin de crear e iniciali- zar los contenedores especficos de prueba.

En niain( ), 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 JVM, como la optimizacin y la depuracin de memoria.72 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 independientemente 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 CopyOnWriteArrayList es mucho ms rpido cuando no hay escritores y sigue siendo significativamente ms rpido cuando hay cinco escritores. Parece que podemos utilizar de manera bastante libre CopvOnWritcArrayList; el impacto de escribir en la lista no parece superar al impacto de sincronizar 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.

Para ver una introduccin a la realizacin de pruebas comparativas de rendimienro bajo la influencia de la compilacin dinmica de Java, consulte www128.ibm.coin/developenvorks/Hbrary/j-jipl 2214.
72

i++) 2226 Piensa en Java Puesto que CopyOnWrite A rray Set utiliza CopyOnWritcArrayList, 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 synehroni- zed comparado con un ConcurrentHasliMap: //: concurrency/MapComparisons.java // {Args: 1 10 10} (Prueba rpida de verificacin durante la construccin) // Comparacin aproximada del rendimiento de los mapas // compatibles con hebras. import j ava.til.concurrenl.*; import java.util.*; import net.mindview.til.*; abstract class MapTesl extends Tester<Mapdnteger, Integer>> { MapTest(String tescld, int nReaders, int nWriters) { super(testJd, nReaders, nWriters);

} class Reader extends TestTask { long result = 0; void test() { for(long i = 0; i < testCycles; i++) forint ir.dex = 0; index < containerS ize; ir.dex++) result += testContai ner.get(in dex);

) v o i d p

i++) 2227 Piensa en Java u t R e s u l t s ( ) { r e a d R e s u l t + * > r e s u l t ; r e a d T i m e + = d u r a t i o n ;

i++) 2228 Piensa en Java } class Writer extends TestTask { void test() { fordong i 0; i < testCycles; i++

)for(int index = 0; index < containerSize; index**) cescContainer.put(index, writeData[index]);

} void putResultsO { writeTime += duration;

} void startReadersAndWriters0 { for(int i = 0; i < nReaders; i++) exec.execute(new Reader()); for (int i 0; i < nWriters; i++) exec.execute(new Writer());

) class SynchronizedHashMapTest extends MapTest { Map<Integer,Integer> containerInitializer() { return Collections.synchronizedMap( new HashMapcInteger,Integer>( MapData.map( new CountingGenerator.Integ er(), new CountingGenerator.Integ er(), containerSize)));

SynchronizedHashMapTest(int nReaders, int nWriters) { super("Synched HashMap", nReaders, nWriters);

} class ConcurrentHashMapTest extends MapTest { Mapdnteger, Integer> containerlnitializer 0 { return new ConcurrentHashMapcInteger,Integer>( MapData.map( new CountingGenerator.Integer(), new CountingGenerator.Integer(), containerSize))

ConcurrentHashMapTest(int nReaders, int nWriters) { super("ConcurrentHashMap", nReaders, nWriters);

} public class MapCoTnparisons { public static void main(String[] args) { Tester.initMain(args); new Synchroni zedHashMapTest (10, 0) ,* new SynchronizedHashMapTest(9, 1); new SynchronizedHashMapTest(5, 5); new ConcurrentHashMapTest(10, 0); new ConcurrentHashMapTest(9, 1); new ConcurrentHashMapTest{5. 5); Tester.exec.shutdown();

Write time 0 47697347568 244012003202 Read time 306052025049 428319156207 476016503775 243956877760 487968880962 } /* Output:(Sample) Type Synched HashMap lOr Ow Synched HashMap 9r lw readTime + writeTime = Synched HashMap 5r 5w readTime + writeTime

=readTime + writeTime ConcurrentHashMap 5r readTime + writeTime *///:-

El impacto de aadir escritores a un mapa ConcurrentHashMap es todava menos evidente que para CopyOnWriteArrayList. pero eso es porque ConcurrentHashMap emplea una tcnica distinta que minimiza claramente el impacto de las escrituras. Bloqueo optimista

Aunque los objetos Atomic realizan operaciones atmicas como decrenientAndGe<(), 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 ha 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 significa que alguna otra tarea ha modificado el objeto mientras tanto. Recuerde que normalmente lo que haramos es utilizar un mutex (synchronized o Lock) para impedir que ms de una tarca 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 synchronized 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 tcnica a aquellos problemas que puedan adaptarse a una serie de requisitos. Si fallara comparcAndSet( ), tenemos que decidir 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 clculos muy costosos, por lo que decidimos emplear una mquina multiprocesador con el fin 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 posihle, utilizando 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 tendra la solucin: //: concurrency/FastSimulation.j ava import java.util.concurrent import java.util.concurrent.atomic. *; impert java.til.*; import static net.mindview.til.Print.*; public class FastSimulation { static final int KELEMENTS = 100000; static final int N_GENES = 30; static final int N_3V0LVERS = 50; static finalAtomiclnteger [] [] new Atomiclnteger[N_ELEMENTS3 [NGENES]; static Random rand = new Random(47); static class Evolver implements Runnable ( public void run() { while(!Thread.interrupted()) (

GRID *=

// Seleccionar aleatoriamente un elemento con el que trabajar: int element rand.nextlnt (N_ELEMENTS) ; for(int i = 0; i < N_GENES; i++) { int previous = element - 1; if(previou9 < 0) previous N_ELEMEN7S - 1; int next = element + 1; if(next >= N_ELEXENTS) next = 0; int oidvalue = GRID[element][i].get(); // Realizar algn tipo de clculo de modelado: int newalue = oidvalue f GRID[previousJ [i] .get() + GRID[next] [i] .get() ; newvalue /= 3; // Promediar los tres valores if(!GRID[elementj [i] .compareAndSet(oidvalue, newvalue)) { // Aqu una poltica para tratar los fallos. En este caso, // nos limitaremos a informar del fallo y a ignorarlo; // nuestro modelo se encargar de tratar con l. orint("01d valu changed from " + oidvalue);

}' i

} public static void main(String[] args) throws fxception { ExecutorService exec = Executors.newCachedThreadPool ) ,for(int i = 0; i < N_ELEMENTS; i++) for(int j = 0; j < N_GENES; j++) GRID[i] [j] = new AtomicInteger(rand.nextlnt(1000)); for (int 1 = 0; i < NEVOLVERS; i++) exec.execute(new Evolver()); TimeUnit.SECONDS.sleep(5); exec.shutdownNow();

} /* (Ejecutar para ver la salida} *///:-

Todos ios elementos se insertan en una matriz, en la suposicin de que esto ayudar a incrementar la velocidad (esta suposicin ser comprobada en un ejercicio). Cada objeto Evolver promedia su valor con los valores contenidos antes y despus suyo, y si se detecta un fallo cuando se intenta hacer la actualizacin, simplemente se imprime el valor y se contina. Observe que no aparece ningn inutex en el programa.

Ejercicio 39: (6) Son razonables las suposiciones realizadas en FastSimulation.java? Pruebe a cambiar la matriz, sus

tituyendo los valores Atomiclntegcr por valores int ordinarios y utilizando mutex de tipo Lock. Compare el rendimiento de las dos versiones del programa. ReadWriteLock

Los bloqueos ReadWriteLock 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 permite 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 ReadWriteLock 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 mquina multiprocesador o no. En ltimo termino, la nica forma de saber si podemos obtener alguna ventaja con ReadWriteLock consiste en comprobarlo. lie aqu un ejemplo que muestra nicamente el uso ms bsico de los bloqueos ReadWriteLock://: concurrency/ReaderWriterList.java import j ava.util.concurrent.*; import j ava.ut i1.concurrent.locks.*; import j ava.ut i1.*; import static net.mindview.util.Print.*; public class ReaderWriterList<T> { private ArrayList<?> lockedList; // Realizar una ordenacin equitativa: private ReentrantReadWriteLock lock - new ReentrantReadWriteLock(true); public ReaderWriterList(int size, T initialValue) { lockedList = new ArrayList<T>( Collections.nCopies(size, initialValue));

) public T set(int index, T element) { Lock wlock = lock.writeLockO ; wlock.lock(); try { return lockedList.set(index, element); ) finally { wlock.unlockO ;

} public T get(int index) { Lock rlock = lock.readLcck() ; rlock.lockO ; try { // Mostrar que mltiples lectores pueden // adquirir el bloqueo de lectura: i f(lock.ge tReadLockCount() > 1) print(lock.getReadLockCount()); return lockedList.get(index); } finally { rlock.uniock()/

} public static void main(String[] args) throws Exception { new ReaaerWriterListTest(30, 1);

} class ReaderWriterListTest { ExecutorService exec = Executors.newCachedThreadPool(); private final static int SIZE = 100; private static Random rand = new Random(47) ; private ReaderWriterList<Integer> list = new ReaderWriterList<Integer>(SIZE, 0); private class Writer implements Runnable { public void run() { try ( for (int i - 0; i < 20; i++) { // Prueba de 2 segundos list.set(i, rand.nextInt()); TimeUni t.MILLISECONDS.s1eep(100);

} } catch(InterruptedException e) { // Forma aceptable de salir

} print("Writer finished, shutting down) ;exec.shutdownNow();

>

} prvate class Reader implements Runnable { pubiic void run() { try { whi le{! Thread.interrupted()) { for(int i = 0; i < SIZE; i++) { list.get(i); TimeUnit.MILLISKCONDS.sleep(1};

} } catch(InterruptedException e) { // Forma aceptable de salir

} pubiic ReaderWriterListTest(int readers, int writers) { for (int i 0; i < readers; i++) exec.execute(new Reader(}); for(int i = 0; i <

writers; i++) exec.execute(new Writer());

) ) /* (Ejecutar para ver la salida) *///:-

Una lista ReaderWriterList puede almacenar un nmero lijo 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 escritura 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 afirmativo, muestra dicho nmero para demostrar que puede haber mltiples lectores que adquieran el bloqueo 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 JJDK para ReentrantReadWriteLock, ver que hay varios otros mtodos disponibles, as como cuestiones relativas a la equidad y a las decisiones de poltica. 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 slo si es necesario.

Ejercicio 40: (6) Siguiendo el ReaderW'riterMap utilizando un

ejemplo

de

ReaderWriterList.java,

cree

un

mapa

mapa HashMap. Investigue su rendimiento modificando MapComparisons.java, Cmo se compara con un contenedor HashMap sincronizado y con un contenedor ConcurrentHashMap? Objetos activos

Despus de estudiar este captulo, habr observado que el mecanismo de gestin de hebras en Java parece bastante complejo y difcil 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 tarcas interfieran entre si.

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.

Podra 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 procedimcntal. Quiz exista un modelo diferente de concurrencia que encaje mejor con la programacin orientada a objetos.

Una tcnica alternativa es la basada en los denominados objetos activos o actores.73 La razn de que dichos objetos se denominen activos es que cada objeto mantiene su propia hebra funcional y su propia cola de mensajes, y todas las solicitudes a cada objeto se ponen en cola para procesarlas de una en una. Por tanto, con los objetos activos, sealizamos los mensajes en lugar de los mtodos , lo que significa que no necesitamos protegemos frente a aquellos problemas que surgen 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 objeto, para ejecutarla en algn instante posterior. La clase Future de Java SE5 resulta til para implementar este esquema. He aqu un ejemplo simple que dispone de dos mtodos que ponen en cola las llamadas a mtodo: //: concurrency/ActiveObjectDemo.java // Slo se pueden pasar constantes, immutables, "objetos // desconectados", u otros objetos activos como argumentos // a mtodos asincronos, irsport java.til .concurrent. *; import java.til.*; import static net.mindview.util.Frint.*; public class ActiveObjectDemo { prvate ExecutorService ex * Executors.newSingleThreadExe cutor(); private Random rand = new Rar.dom(47) ; // Insertar un retardo aleatorio para producir el efecto // de un tiempo de clculo: private void pause(int tactor) { try { TimeUnit.MILLISECONDS.sleep( 100 + rand.nextInt(factor)); } catch(InterruptedException e) { print(sleep{) interrupted");
73

Gracias a Alien Holub por dedicarme el tiempo necesano para explicarme este tema.

} public Future<Integer> caiculatelnt(final int x, final int y) { rstum ex.submit(new Callable<Integer>() { public Integer call() { print (starting nn + x + " + 11 + y) ,* pause(500); return x + y;

}>;

} public Future<Float> calculateFlcat(final float x, final float y) { return ex.submit(new Callable<Float>() { public Float call() { print("starting H + x + " 4 " + y) ; pause(2000) ; return x + y;

})>

) public void shutdownO { ex.shutdown() ; } public static void main(Strir.g[3 args) { ActiveObjectDemo di = new ActiveObjectDemo0; // Evita ConcurrentModificationKxception: List<Future<?>> results new CopyOnWriteArrayList<Future<

?>>(); for (float f = 0. Of ; f < l.Of; f += 0.2f) results.add(dl.calculateFloa t(f, f)); for(int i = 0; i < 5; i++) results.add(dl.caiculatelnt (i, i ) ) ; print("All asynch calls made"); while(results.size() > 0) { for(Future<?> f : results) if(f.isDone{) ) { try { print(f.get()); } catch(Exception e) { throw new RuntimeExcepticn(e);

} results.remove(f);

>

} } /* Output: (85% match) All asynch calls made

} dl.shutdown();

6 8

*///:

El ejecutor monohebra producido por la llamada a Executors.newSngleThrcadExecutor() mantiene su propia cola bloqueante no limitada, y tiene una nica hebra que extrae tareas de la cola y las ejecuta hasta completarlas. Todo lo que necesitamos hacer en calculatelnt() y calculatcFloat( ) es enviar con submit() un nuevo objeto Callable 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 el llamante utiliza el objeto Futnre para descubrir cundo se completa la tarea y para extraer el valor de retomo real, listo permite gestionar el caso ms complejo, pero si la llamada no tiene valor de retomo el proceso se simplifica.

En main(). se crea una lista List<Future<? para capturar los objetos Future devueltos por los mensajes caicuIatcFloat() y calculatelnt( ) 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 CopyOnWriteArrayList elimina la necesidad de copiar la lista para evitar la excepcin ConcurrentModilcation- Exccption.

Para impedir un acoplamiento inadvertido entre hebras, los argumentos que se pasen a una llamada a mtodo de objeto activo 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 tarca (resulta difcil imponer esta regla, porque no hay ninguna 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 normales, que no slo disponen de la opcin de proteger sus campos).

Toda la comunicacin entre objetos activos toma la forma de mensajes intercambiados entre esos objetos.
3.

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 retardo 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 produciendo, 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 demasiado 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 prctica, 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,

algunas de las ideas subyacentes a los objetos activos provienen de la Teora de Procesos Secuenciales Comunicantes (CSP, Communicating SequentiaProcesses) de C.A.R. lloare.

Ejercicio 41: (6) Aada una rutina de tratamiento de mensajes a ActivcObjectDemo.java que no tenga ningn valor de

retomo e invquela desde tnain().

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.

Hay que considerar todos los posibles problemas que pueden producirse cuando estas tareas terminan.
2.

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.

I-as tareas pueden interbloqucarsc si no se disean cuidadosamente.

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 Je soluciones

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 (incluyendo la capacidad de distribuir transparentemente las tarcas 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 instrucciones) 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 ejecucin del programa y las variables locales. Un cambio de proceso (el cambio de contexto pesado) debe intercambiar el espacio 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 tarcas individuales que el sistema no es capaz de terminar).

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, porque podran introducirse inadvertidamente en situaciones de posible interbloqueo.

Adems, la aplicacin de hebras es todo un arte. Java est diseado para permitirnos crear tantos objetos como necesitemos para resolver nuestro problema, al menos en teora (la creacin de millones de objetos para un anlisis de elementos finitos en Bioingeniera, por ejemplo, podra no resultar prctica en Java sin utilizar el patrn de diseo Peso mosca). Sin embargo, 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 difcil de detectar y depender a menudo del sistema operativo y de la mquina JVM; puede ser menor de cien o encontrarse en el rango de los miles. Puesto que muy a menudo slo crearemos un puado de hebras para resolver un problema, este lmite no suele ser una restriccin, pero en problemas de diseo 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 esc tipo de programacin como una tecnologa realmente avanzada. Siempre hay algo que puede estallamos en la cara cuando menos lo 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, valore 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 multihebra, si es que est escribiendo muchos de estos programas y su nivel de complicacin es lo suficientemente alto como para justificar esta solucin.

Lecturas adicionales

Lamentablemente, hay mucha informacin errnea acerca de la concurrencia; esto constituye una constatacin de lo confuso 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 anteriormente que haba comprendido perfectamente 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 abordar con una cierta mirada crtica cada nuevo documento que encontremos acerca del tena de concurrencia, para intentar entender 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 Blocli, 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.

Concurrertt Programming in Java, Second Edition, por Doug Lea (Addison-Wesley, 2000). Aunque este libro se basa significativamente en Java SE5, buena parte del trabajo de Doug se utiliz para crear las nuevas bibliotecas java.util.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 entTe una lectura y otra, para poder interiorizar la informacin). 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 Specification, Third Edition (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/jIs. Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico The Thinking in Java AimotatedSotution Cuide, disponible para la venta en www.MindView.net

.Interfaces grficas de usuario

22

Una directriz de diseo fundamental es: 4'Hacer fciles las cosas simples y hacer posibles las cosas difciles.74

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, fcse objetivo no se consigui. En su lugar, la herramienta AWT (Abstract Windowing Toolkit, herramienta abstracta de ventanas) de Java produca una GUI con un aspecto igualmente mediocre en todos los sistemas. Adems, era bastante restrictiva; slo se utilizaban cuatro tipos de fuentes y no se poda acceder a ninguno de los elementos GUI sofisticados que existen en el sistema 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 herramienta AWT original haba sido concebida, diseada e implementada en un solo mes. Se trata, ciertamente, de una productividad maravillosa, y tambin de una leccin instructiva sobre por qu es importante el diseo.

Mi ejemplo favorito es el modelo "Servilleta" de Kcn Amold que hace que las ventanas parezcan dibujadas en una serv illeta \ easc hup.S'napkmUtf stnn- ccfuqie ne.
74

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 creacin de entornos de programacin visuales. Java 2 (JDK 1.2) complet la transformacin desde el modelo AWT antiguo de Java 1.0 sustituyendo esencialmente casi todo con las clases JFC (Java Foundation Classes), cuya parte para diseo de interfaces 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 biblioteca GUI que Sun tiene prevista como destino final para Java.- 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, dcscargable 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 enorme 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.comy 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.sun.com/docsfbooks/iutorial/uiswing.

Existen numerosos (y voluminosos) libros dedicados especficamente a Swing, y puede consultar alguno de ellos si necesita analizar los temas con mayor profundidad o si quiere modificar el comportamiento predeterminado de Swing.

A medida que estudiemos Swing, vera que:

1.

Swing es un modelo de programacin enormemente mejorado, si se compara con muchos otros lenguajes y entornos de desarrollo (no queremos decir que sea perfecto, pero s que representa un gran paso adelante). JavaBeans (del que hablaremos hacia el final del capitulo) es el marco de trabajo para dicha biblioteca.

2.

Los "constructores de interfaces GUI (entornos de programacin visual) forman una parte fundamentiil de cualquier entorno completo de desarrollo en Java. JavaBeans y Swing permiten al constructor de interfaces GUI escribir cdigo automticamente a medida que colocamos los componentes sobre formularios empleando herramientas grficas. Esto permite acelerar el desarrollo enormemente durante la construccin de interfaces GUI, y permite una mayor dosis de experimentacin y. por tanto, la posibilidad de probar ms disertos y. presumiblemente, terminar adoptando mejores diseos.

3.

Puesto que Swing es razonablemente sencillo, an cuando utilicemos 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 GUI presentaban en el pasado, que era que generaban fcilmente cdigo ciertamente ilegible.

Swing contiene todos los componentes que cabria esperar ver en una interfaz de usuario moderna: desde botones con imgenes a rboles y tablas. Se trata de una biblioteca enormemente grande, pero ha sido disertada de tal forma que su complejidad resulta apropiada para la tarea que tengamos entre manos: si esa tarca es simple no es necesario escribir cdigo, pero a medida que tratamos de hacer cosas ms complejas, la complejidad del cdigo se incrementa proporcional mente.

Uno de los aspectos ms atractivos de Swing es lo que podramos denominar ortogonalidad de uso. Esie trmino quiere decir que. una vez que entendemos las ideas generales acerca de la biblioteca, usualmente podemos aplicarlas en todas partes. Debido principalmente a los convenios estndar de denominacin, mientras escribamos ejemplos de este capitulo comprob que normalmente se poda adivinar con precisin qu es lo que cada mtodo haca basndose en su nombre. Ciertamente, se trata de todo un hito en lo que respecta al diserto adecuado de bibliotecas de programacin. Adems, podemos generalmente complementar unos componentes con otros y las cosas funcionarn correctamente

La navegacin mediante el teclado es automtica; podemos ejecutar una aplicacin 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 formulario. Caractersticas tales como las sugerencias de pantalla requieren, normalmente, una nica linea de cdigo.

En aras de la portabilidad, Swing est escrito enteramente en Java.

Swing soporta tambin una funcionalidad bastante radical denominada aspecto y estilo seleccionabas'*, que quiere decir que la apariencia de la interfaz de usuario puede modificarse dinmicamente para ajustarla a las expectativas de los usuarios que estn trabajando bajo diferentes plataformas y sistemas operativos. Resulta incluso posible (aunque algo complicado) inventar nuestros propios aspecto y estilo de interfaz. En la Web pueden encontrarse algunos ejemplos de modelos bsicos de interfaz '

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 final del capitulo, examinaremos dos soluciones alternativas a Swing; el modelo SWT patrocinado por IBM. desarrollado para el editor Eclipse que est disponible de manera gratuita como biblioteca GUI autnoma de cdigo abierto y la herramienta Flex de Macromedia para el desarrollo de interfaces 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 applei, un programa que puede distribuirse a travs de Internet para ejecutarlo dentro de un explorador web (dentro de un entorno de seguridad).

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 applet:5, y descargar e instalar un paquete de 10 MB para peder 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 aplicaciones del lado del cliente nunca consiguieron una masa crtica, y aunque 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 posteriormente en el captulo) pueden ser la forma perfecta de distribuir programas cliente y de actualizar automticamente la mquina de todos los usuarios sin el coste y el esfuerzo habitualmente 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 www. 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 operativo que estemos empleando. El ttulo de la ventana se puede lijar utilizando el constructor JFrame de la forma siguiente: //: gui/HelloSwing.java itsport j avax.swing.*; public class HelloSwing { public static void main (String [] args) { JFrame frame = new JFrame("Helio Swing"); frame.setDefaultCloseOperation(JFrame. EXIT_ON_CLOSE); frame.setSize(300, 100); frame.setVisible(true);

} ///=-

setDefaultCIoseOperation( ) le dice a JFrame lo que debe hacer cuando el usuario ejecute una operacin ce cierre. La constante EXIT_ON_CLOSE le dice que salga del programa. Sin esta llamada, el comportamiento predeterminado consiste 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: //: gui/HelloLabel. java import javax.swing.*; import java.til.concu rrent; public class HelloLabel { public static void main{String[] args) throws Sxception { JFrame frame = new JFrame{"Helio Swing); JLabel label = new JLabel{"A Label); frame.add(label); frame.setDefaultCloseOperacion(JFrame. EXIT ON_CLOSE); frame.setSize(300, 100); frame.setVisible(true); Timenit.SECONOS.sleep{1); label.setText("Hey! This 6 DifferentIpM);

} } m-.-

Despus de un segundo, el texto de JLabel cambia. Aunque esto resulta entretenido y seguro, teniendo en cuenta la trivialidad del programa, no resulta en realidad una buena idea que la hebra main() 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()) deben enviar las tareas para que sean ejecutadas por la hebra de despacho de sucesos de Swing.75 Para hacer esto, entregamos una tarea a SwingUtilities.invokeLater(), 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 anterior, lo que obtendramos es: //: gui/SubmitLabelManipulationTask.java import javax.swing.*; import java.til.concurrent.*; public class SubmitLabelManipulationTask { public static void main(Strir.g[] args) throws Exception { JFratne frame = new JFrame("Helio Swing"); final JLabel label = new JLabel("A Label"); frame.add(label); frame.seDefaultCloseOperation(JFrame. EXIT_ON_CLOSB); frame.setSize(300, 100); frame.setVisible(true) ; Timenit. SECONDS. sleep (1) ; SwingUtilities.invokeLater(new RunnableO { public void runO {
75

Tcnicamente, la hebra de despacho de sucesos proviene de la biblioteca ANVT.

label .setText ("Hey! This is Different!");

});

) } ///:-

Ahora ya no estamos manipulando directamente la etiqueta JLabel. En lugar de ello, enviamos un objeto Runnablc, 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 Kunoable, 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 SwngUtilities.invokeLater()). 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. 76 Por tanto, el problema escrito correctamente tendra el siguiente aspecto: //: gui/SubmitSwir.gProgram.java lista prctica fue aadida en Java SE5, por lo que podr encontrar multitud de programas ms antiguos que no se ajustan a ella. Oso no quiere decir que los autores de esos programas fueran ignorantes. Las prcticas sugeridas parecen estar en constante evolucin.
76

import j avax.swing.*; import j ava.util.concurrent.*; public class SubmitSwingProgram extends JFrame { JLabel label; public SubmitSwingProgram() { super("Helio Swing"); label = new JLabel(A Label"); add(label); setDef aultCloseOperatior. (JFrame. EXIT__ON_CLOSE) ; setSize(300, 100); setVisible(true);

} static SubmitSwingProgram ssp; public static void main(String[] args) throws Exception { SwingUtilities.invokeLater(new RunnableO { }); public void run() ( ssp = new SubmitSwingProgram O ; }

TimeUnit.SBCONDS.sleep(1); SwingUtilities.invokeLater(new RunnableO { public void run{) { sso.label.setText("Hey! This is Different!");

});

} } III-.-

Observe que la llamada a s!eep() m se encuentra dentro del constructor. Si la ponemos ah, 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: llamada a

(l) Modifique HelIoSwing.java para demostrar que la aplicacin no se cerrar sin la setDefaultCloscOperation().

Ejercicio 2: (2) Modifique HelioLabel.java 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 emplearlo en los ejemplos Swing del resto del captulo: //: net/minview/util/SwingConsole.java // Herramienta para ejecutar ejemplos de Swing desde // la consola, tanto applets como marcos JFrame. package net.mindview.til; import javax.swing.*; public class SwingConsole { public static void run(final JFrame f, final int width, final int height) { SwingUtilities.invokeLater(new RunnableO { public void runO { f.setTitle(f.getClass().getSimpleName()); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.

setSize(width, height);

f.setVisible(true);

}>.

} } ///-

Dado que el lector podra querer utilizar esta herramienta en otras situaciones, la hemos incluido en la biblioteca net.mind- view.util. Para utilizarla, la aplicacin debe estar dentro de un marco JFrame (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 SubmitSwingProgram.java para utilizar SwingConsole.

Definicin de un botn

Definir 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: //: gui/Buttonl.j ava // Inclusin de botones en una aplicacin Swing. import j avax.swing.*; import j ava.awt.* j import static net.mindview.util.SwingConsole.*?

public class Buttonl extends JFrame { private JButton bl = new JButton{"Button 1")/ b2 = new JButton("Bucton 2"}; public ButtonlO { setLayout(new FlowLayout()); add(bl); add(b2);

} public static void main(StringU args) { run(new ButtonlO, 200, 100);

} ///:~

Aqui hemos aadido algo nuevo: antes de colocar ningn elemento en el marco .IFrame, definimos un gestor de disposicin de tipo FIowLayout 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 funcionara aqu porque (como veremos posteriormente en el captulo) su comportamiento predeterminado consiste en cubrir cada control completamente con los nuevos controles que se vayan aadiendo. Por el contrario, FIowLayout 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. Aqui es donde debemos intervenir y escribir algo de cdigo para determinar qu es lo que tiene que suceder. La base fundamental de la programacin dirigida por sucesos, que estn muy relacionados con el comportamiento de una 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 implemcntacin (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, s se ha desplazado el ratn por encima del botn, no registraremos nuestro inters en dicho suceso. Se trata de una forma muy sencilla y elegante de gestionar la programacin dirigida por sucesos, 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 JavaBcan (de los que hablaremos ms adelante en el captulo).

Al empezar, vamos a centramos 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 pulsacin de un botn, invocamos el mtodo addActionListener() de JButton. Este mtodo espera un argumento que es un objeto que implemento la interfaz ActionListener. Dicha interfaz contiene un nico mtodo denominado actionPerformcd(). Por tanto, para asociar cdigo con un botn JButton. implemento 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 retrollamad).

Pero, cul debe ser el resultado de presionar ese botn? Nos gustara que algo cambiara en la pantalla, as que introduciremos 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 formas de crear un campo JTextField, la ms simple consiste en informar al constructor sobre cul es la anchura que queremos que tenga ese campo. Una vez colocado el campo JTextField en el formulario, podemos modificar su contenido utilizando el mtodo setText( ) (existen muchos otros mtodos en JTextField, y puede encontrar la informacin correspondiente en la documentacin del JDK disponible en http://java.sun.com). He aqu un ejemplo: //: gui/Button2.java // Respuesta a la pulsacin de un bocn, i mport j avax.swing.*; import j ava.awt.*; import j ava.awr.evenc.*; import ntatic net.mindview.til.SwingConsole.*; public clasn Button2 extends JFrame { prvate JButton bl - new JButton(MnButton 1"), b2 <= new JButton("Button 2 ") ;

private JTextField txt = new JTextField(10); class ButtonListener implements ActionListener { public void actionPerformed(ActionEvenl e) { String ame = ((JButton)e.getSource()).getText(); txt.seLText(ame);

} private ButtonListener bl = new ButtonListener(); public Button2{) { bl.addActionListener(bl); b2.addActionListener(bl); setLayout(new FlowLayout()) ,* add(bl); add(b2); add(txt);

} public args) 150);

static void main(String[] { runnew Button2()/ 200,

} } ///:-

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 diferencia en el programa anterior radica en la creacin de la clase ButtonListener que implementa la interfaz ActionListener antes mencionada. El argumento de actionPerformed( ) es de tipo ActionKvent, que contiene toda la informacin acerca del suceso y del lugar en que se ha producido. En este caso, queramos 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 asociado 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.

2270 Piensa en Java En el constructor, se utiliza addActionListener() para registrar el objeto ButtonListener ante ambos botones.A menudo resulta ms cmodo programar la clase ActionListener como una clase interna annima, especialmente, dado que lo normal es utilizar una nica instancia de cada una de estas clases. Podemos modificar Button2.java para utilizar una clase interna annima de la forma siguiente: //: gui/Buzton2b.java // Utilizacin de clases internas annimas, import j avax.swing.*; import j ava.awt.*; import j ava.awt.event.*; import static net.mindview.util.SwingConsoie.*; public cass Button2b extends JFrair.e ( private JButton bl = new JButton("Button i"), b2 = new JButton{MButtcn 2") ; private JTextField txt = new JTextField(10) ; private ActionListener bl = new ActionListener(} { public void actionPerformed(ActionEvent e) { String ame = ((JButton)e.getSource()).get Text(); txt.setText(ame);

public class TextArea extends JFrame {

2271 Piensa en Java }; public Button2b() { bl.addActionListener(bi); b2.ad&ActionListener(bl); setLayout(new FlowLayout()); add(bl); add(b2) add(txt);

) public static void main(Stringf] args) { run(new Button2b(), 200, 150);

} ///:-

public class TextArea extends JFrame {

2272 Piensa en Java 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)Creeunaaplicacin utilizando la clase SwingConsoie. 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 funcionalidad. Un mtodo particularmente til es append(); con l podemos ir colocando fcilmente la salida de datos del componente JTextArea. Como 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 // Utilizacin del control JTextArea. i mport j avax.swi ng. * ; import java.awt.*; public class TextArea extends JFrame {

2273 Piensa en Java import j ava.awt.event.*; import java.til.*; import net.mindview.util.*; import static net.mindview.til.SwingConsoie.* ;private JButton b = new JButton("Add DataN), c = new JButton("Cle ar Data"); private JTextArea t = new JTextArea(20 , 40); private Map<String,S tring> m = new HashMap<Stri ng,String>() ; public TextAreaO { // Utilizar todos los datos: m.putAll(Cou ntries.capit als0);
b.

addActionListener{new ActionListener() { public void actionPerformed(ActionE vent e) { or(Map.Entry me : m.entrySet()) t.appendme.getKey() + ": + me.getValue()+n\n");

public class TextArea extends JFrame {

2274 Piensa en Java });


c.

} addActionListener(new ActionListener0 { pubiic void actionPerformed(ActionE venr e) { t.setText("");

}).

setLayout(ne w PlowLaycut() ); add(new JScrolIPane( t)); add(b) i add(c);

} public static void main(String[J args) { runlnew TextAreaO, 475, 425);

public class TextArea extends JFrame {

2275 Piensa en Java }

} ///:**

En el constructor, el mapa se rellena con todos los pases y sus capitales. Observe que. para ambos botones, se crca el objeto ActionListener y se aade sin definir una variable intermedia, dado que no necesitamos volver a referimos a dicho objeto durante el programa. El botn Add Data (aadir datos) formatea y aade todos los datos, y el botn Clear Data (borrar datos) utiliza set'lext() para eliminar todo el texto de JTextArea.

Al aadir el control JTextArea al marco JFrame, lo envolvemos en un panel JScrolIPane para controlar el desplazamiento 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 entornos de programacin GUI, he de confesar que me impresionan bastante la simplicidad y el adecuado diseo de componentes tales como JScrolIPane.

public class TextArea extends JFrame {

2276 Piensa en Java Ejercicio 6: (7) Transforme strings/TestRegularExpression.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.

Ejercicio 7: (5) Cree una aplicacin usando SwingConsole, y aada todos los componentes Swing que dispongan de

un mtodo addActionListener( ) (bsquclos en la documentacin del JDK. disponible en http://java. suti.com. Consejo: busque addActionListener() utilizando el ndice). Capture sus sucesos y muestre un mensaje apropiado para cada uno dentro de un campo de texto.

Ejercicio 8: (6) Casi todos los componentes Swing derivan de Component, que dispone de un mtodo setCursor(). public class TextArea extends JFrame {

2277 Piensa en Java 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 GUI que haya utilizado. En primer lugar, todo se fija en el cdigo, no hay ningn recurso que controle la colocacin de los componentes. En segundo lugar, la forma en la que se colocan los componentes en un formulario est controlada no por la posicin absoluta, sino por un gestor de diseo o de disposicin (layout manager), que decide cmo se disponen los componentes, 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 aplicacin, por lo 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, J Window, JDiaiog, JPanel. etc., pueden contener y visualizar objetos Coniponent. En Container, existe 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

public class TextArea extends JFrame {

2278 Piensa en Java A menos que indiquemos otra cosa, un marco JFrame utilizar un BorderLayout como su esquema de disposicin predeterminado. En ausencia de instrucciones adicionales, este gestor toma todo aquello que agreguemos con add() y lo coloca en el centro, estirando el objeto 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:

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 BorderLayout: //: gui/BorderLayout1.java // Ejemplo de BorderLayout. import j avax.swing.*; import j ava.awt.*; import static nec.mindview.util.SwingConsoie.*; public class TextArea extends JFrame {

2279 Piensa en Java public class BorderLayout1 extends JFrame { public BorderLayoutl(> { add(BorderLayout.NORTH, new JButton("North")); add(BorderLayout.SOUTH, new JButton("South)}; add (BorderLayout EAST, new JButton (,1East")) ; add(BorderLayout.WEST, new JButton("West")); add(BorderLayout.CENTER, new JButton("Center"));

} public static void main(String[] args) { run(new BorderLayoutl(), 300, 250);

} ///* Para todas las colocaciones salvo CENTER, el elemento que aadamos se comprime para que quepa en la cantidad de espacio ms pequea posible a lo largo de una dimensin, mientras que se estira para ocupar el mximo espacio sobre la otra dimensin. Sin public class TextArea extends JFrame {

2280 Piensa en Java embargo, CENTER se estira segn ambas dimensiones para ocupar toda la parte central

public class TextArea extends JFrame {

22 Interfaces grficas de usuario 2281 .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 flujo de los componentes.

He aqu un ejemplo donde se selecciona FlowLayout como gestor de disposicin y luego se colocan botones en el formulario. Observar que. con FlowLayout. los componentes se muestran con su tamao natural. Un control JButton. por ejemplo, tendr el tamao de su cadena de caracteres asociada. // : gui /FlowLayout 1. j ava // Ejemplo de FlowLayout. import j avax.swing.*; i mport j ava.awt.*; import static net.mindview.til.SwingConsole.*; public class FlowLayoutl extends JFrame { public FlowLayoutl() { setLayout(new FlowLayoutO ) ; for(int i = 0; i < 20; i++) add(new JButton("Button " + i));

} public static void mair.(String [] args) { run(new FlowLayoutl(), 300, 300);

22 Interfaces grficas de usuario 2282 > } Hh-

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 tendr el tamao de su cadena de caracteres asociada, si tratamos de justificar a la derecha su texto, la visualizacin no se modificar.

Observe que si cambiamos el tamao de la ventana, el gestor de disposicin har que los componentes vuelvan a fluir en consecuencia. GridLayout

GridLayout permite 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 columnas necesarias y dichas filas y columnas se disponen en iguales proporciones. //: gui/GridLayout1.j ava // Ejemplo de GridLayout. import javax.swing.*; import j ava.awt.*; import static net.mindview.til.SwingConsole.*; public class GridLayoutl extends JFrame { public GridLayoutl() { setLayout(new GridLayout{7,3));

22 Interfaces grficas de usuario 2283 for(int i = 0; i < 20; i-t) add(new JButton{"Button " + i));

} public static void main(String[1 args) { runnew GridLayoutl(), 300, 300);

} ) Hh-

En este caso, hay 21 casillas pero slo 20 botones. La ltima casilla se deja vaca porque GridLayout no efecta ningn proceso de equilibrado. 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

22 Interfaces grficas de usuario 2284 difcil de comprender. Est pensado principalmente para la generacin automtica de cdigo por parte de un constructor de interfaces GUI (los constructores de interfaces GUI pueden usar GridBagLayout en lugar de un sistema de posicionamiento absoluto). Si el diseo es tan complicado que piensa que puede necesitar GridBagLayout, es mejor que emplee una herramienta de construccin de interfaces GUI para generar ese diseo. 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 descargarse de http://iava.sun.com. Este componente est apilado sobre GridBagLayout y oculta la mayor parte de su complejidad, as que permite simplificar enormemente el diseo. Posicionamiento absoluto

Tambin resulta posible establecer la posicin absoluta de los componentes grficos:

1.

Asigne el valor nuil al gestor de disposicin del objeto Container: setLayout(nuII).

2.

Invoque setBounds( ) o reshape( ) (dependiendo de la versin del lenguaje) para cada

22 Interfaces grficas de usuario 2285 componente, pasando como parmetro un rectngulo de contorno en coordenadas de pxel. Puede hacer esto en el constructor o en paint(), dependiendo del efecto que quiera conseguir.

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 tambin 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 complejo, 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 componentes en sentido vertical u horizontal, asi como el espaciado entre los componentes. Podr encontrar algunos ejemplos bsicos de BoxLayout en los suplementos en linea (en ingls) del libro, disponibles en www.MindVtcw.net. Cul es la mejor solucin?

Swing es bastante potente: pueden hacerse una gran cantidad de cosas con slo unas lneas de cdigo. Los ejemplos mostrados 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 combinando 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 lenguaje y las bibliotecas para soportar las herramientas de construccin de interfaces

22 Interfaces grficas de usuario 2286 GUI, que han sido creadas con el expreso propsito de facilitar la experiencia de programacin. Mientras que comprendamos el tema de los gestores de disposicin y sepamos cmo tratar los sucesos (tal como se describe a continuacin) no resulta particularmente importante conocer los detalles acerca de cmo disponer los componentes de forma manual; deje 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 representado por una clase diferente. Cuando se dispara un suceso, es recibido por uno o ms escuchas que actan de acuerdo con ese suceso. Por tanto, el origen de un suceso y el lugar donde ese suceso se trata pueden estar separados. Puesto que normalmente 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 disparando el suceso. Este registro se realiza invocando el mtodo addXXXListener() en el componente encargado de disparar 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.

22 Interfaces grficas de usuario 2287 Por tanto, toda la lgica de sucesos estar contenida dentro de la clase escucha. Cuando creamos una elase escucha, la nica restriccin es que esta 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 pueden agregar y eliminar los tipos apropiados de escuchas para cada componente. Observar que tkXXX en cada caso tambin 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 compouentes bsicos que soportan esos sucesos concretos, proporcionando los mtodos addXXXListener() y removeXXXListener(). Recuerde que el modelo de sucesos est diseado para ser ampliable, as que puede que se encuentre con otros tipos de sucesos y de escuchas que no estn incluidos en esta tabla.

22 Interfaces grficas de usuario 2288

Puede ver que cada tipo de componente sopona slo ciertos tipos de sucesos. Resulta bastante tedioso buscar rodos los sucesos 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.

22 Interfaces grficas de usuario 2289 En el Captulo 14, Informacin de tipos, se present el mecanismo de reflexin y dicha funcionalidad se emple para buscar los mtodos de una clase concreta, bien la lista completa de todos o un subconjunto de los nombres que se correspondan 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 clases 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 lo que crea que est buscando, consulte la documentacin del JK.

He aqu la versin GUI ms til de ShowMethods.java, especializada para buscar los mtodos addListener de los componentes Swing:

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2290 No hay ningn suceso MouseMntionEvent, an cuando parezca que debera haberlo. Los ciics y el movimiento de ratn estn combinados en el suceso Mousetvent, por lo que esta segunda aparicin de MouseEvent en la tabla no es un error.// Muestra los metodos "addXXXListener" de cualquier clase Swing. import j avax.swing.*; import j ava.awt.*; import j ava.awt.event.*; import java.lang.reflect.*; import java.util.regex.*; import static net.mindview.util.SwingCcnsole.*; public class ShowAddListeners extends JFrame { private JTextFiela name = new JTextField(25); private JTextArea results = new JTextArea(40, 65); private static Pattern addListener = Pattern.compile("(aad\\w+?Listener\\(. *?\\))"); private static Pattern qualifier * Pattern.compile(n\\w+\\."); class NameL implements ActionListener { public void actionPerformed(ActionKvent e) { String nm = name.getText 0 . trimO ; if(nm.length() = = 0 ) { results .setText ("No match"); return;

} CIass<?> kind; try { kind = Class.forName("javax.swing." + nm) ; } catch(ClassNotFoundException ex) { results.setText("No match"); return;

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2291 } Method[] methods = kind.getMethods(); results.setText(n"); for(Method m : methods) { Matcher matcher = addListener.matcher(m.toString()); if(matcher.find()) results.append(qualifier.matcher( matcher .group (1)) .replaceAll ("") *\n ') ;

) public ShowAddListeners() { NameL nameListener = new NameL 0 ; name.addActionListener(nameListener); J Pan el top = new JPanel (); top.add{new JLabel("Swing class name (press

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2292 Enter):")); top.add(name); add(BorderLayout.NORTH, top); add(new JScroilPane(results)); // Datos iniciales y prueba: name.setText("JTextArea"); nameListener.actionPerformed( new ActionEvent("" , 0 ,""));

} public static void main(String[] arcs) { run(new ShowAddListeners(), 500, 400);

} ) ///= -Introducimos el nombre de la clase Swing que queramos buscar en el campo JtextField ame. 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 lista 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

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2293 se captura y en 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), CIass.forNamc() tendr xito y getMethods() devolver una matriz de objetos Method.

Aqu se emplean dos expresiones regulares. La primera, addListener, busca la cadena add seguida por cualquier combinacin de caracteres alfabticos y seguida de Listener y de la lista de argumentos entre parntesis. Observe que esta expresin 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 find( ) para el objeto Matcher, devuelve true slo si se detecta una correspondencia, y en este caso podemos seleccionar el primer grupo de correspondencia entre parntesis invocando group(l). Esta cadena de caracteres sigue conteniendo cualificadores, as que para quitarlos se emplea el objeto Pattern de tipo qualifier, al igual que se haca en ShowMethods.java.

Al final del constructor, se coloca un valor inicial en ame y se ejecuta el suceso de accin para realizar una prueba con daros iniciales.

Este programa es una forma 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:

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2294


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 ejemplo, 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, addMouseMotionListcner().

He aqu algunas de las interfaces escucha:

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2295

No se trata de un listado exhaustivo, en pane porque el modelo de sucesos permite crear nuestros propios tipos de sucesos junto 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 trivial. Sin embargo, las interfaces escucha que tienen mltiples mtodos pueden ser ms cmodas de emplear. Por ejemplo, si queremos

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2296 capturar un clic de ratn (que no haya sido ya capturado por nosotros, por ejemplo, mediante un botn), necesitaremos escribir un mtodo para mouseClicked(). Pero como MouseListener es una 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 proporcionan con adaptadores, cuyos nombres podemos ver en la tabla anterior. Cada adaptador proporciona mtodos predeterminados vacos para cada uno de los mtodos de la interfaz. 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 void mouseClicked(MouseEvent e) { // Responder al clic del ratn...

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2297 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(MouseEvenL 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 correctamente, salvo porque el mtodo no ser invocado cuando se haga un clic de ratn. Puede ver el problema? El problema se encuentra en el

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2298 nombre del mtodo: MouseClicked() en lugar de mouseClickcd (). Un simple error en el uso de maysculas 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, asi que no se obtienen los resultados deseados. A pesar de la incomodidad. 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 predefinida en el cdigo anterior.

Ejercicio 9: (5) Partiendo de ShowAddListeners.java, cree un programa con la funcionalidad completa de typeinfo.

Show.Yethods.java. Control de mltiples sucesos

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2299 Para demostrar que estos sucesos se estn realmente disparando, merece la pena crear un programa que controle el comportamiento de un botn JButton, y que no se limite a ver si ha sido pulsado. En este ejemplo tambin nos muestra cmo heredar nuestro propio botn de JButton.7

En el cdigo que se muestra a continuacin, la clase MyButton es una clase interna do TrackEvent. por lo que MvButton 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 una solucin limitada, ya que MyButton slo puede emplearse en conjuncin con TrackEvent. Este tipo de cdigo se denomina en ocasiones cdigo altamente acoplado: //: gui/TrackEvent.java // Mostrar los sucesos a medida que tienen lugar. import j avax.swing.*; import j ava.awt.*; import j ava.awt.event.*; import java.util.*; import static net.mindview.util.SwingConsole.*; public class TrackKvent extends JFrame { prvate HashMap<String,JTextFeld> h = new HashMap<String,JTextPield>0 ; private String [] event = { "focusGained", "focusLostn, "keyPressed", "keyReleased", "keyTyped", "mouseClicked', "mouseEntered", "mouseExited" r "mousePressed", }; mouseReleased11, "ruouseDragged", "mouseMoved"

private MyButton bl = new MyButton(Color.BLUE, "testln), b2 - new

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2300 MyButton(Color.RED, "testS"); class MyButton extends JButton { void report(String field, String msg) { h.aet(field).setText( msg)?

} FocusLister.er fl = new FocusListener (} { public void focusGained(FocusEvent e) { report (" focusGained1', e. paramString {)) ;

} public void focusLost(FocusEvent e) { report ("focusLost'', e .paramString () ) ;

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2301 }- KeyListener kl = new KeyListener() { public void keyPressed(KeySvent e) { report("keyPressed", e.paramString());

} public void keyReleased(KeySvent e) { ' En Java 1.0/1.1 no se/)odia heredar el objeto botn ni ninguua clase til. *ie era uno de los numerosos fallos de diseo fundamentales. report("keyReleased", e.paramString{));

) public void keyTyped(KeyEvent e) { report("keyTyped", e.paramString 0};

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2302 }; MouseListener ml = new MouseListener 0 { public void tuouseClicked (MouseEvent e) { report("mouseCIiek ed, e.paramString()) ;

} public void mouseEnterea(MouseE vent e) ( report("mouseEnte red", e.paramString( } ) ; ) public void mouseExited(MouseEv ent e) { reDort("mouseExit ed", e.paramString()};

} public void mcusePre3sed(MouseE vent e) { report{"mousePres sed", e.oaramString());

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2303 } public void mouseReleased(Mouse Event e) { report("mouseRele ased", e.paramString());

}? MouseMotionListener mml = new MouseMotionListener() {

public void mouseDragged(MouseE vent e) { report ('mouseDragged", e .paramString () ) ;

} public void mouseMoved(MouseEve nt e) { report("mouseMoved " , e .paramString{));

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2304 }

}; public MyButton(Color color, String label) { super(label); 3etB ackg roun d(co lor) ; addF ocus List ener (f1) ; addK eyLi sten er(k l); addM ouse List ener (ml) ; addMouceKotionLictener(mml);

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2305 } public TrackEventO { setLayout (new GridLayout {event.length 1, 2)); for(String evt : event) { JTextField t = new JTextFieldO ; t.setEditable(false )/ add(new JLabel(evt, JLabel.RIGHT)); add(t); h.put(evt, t);

} a a d ( b l ) ; a d d ( b 2 ) ;

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2306 } public static void main(Stringf] args) { run(new TrackEventO, 700, 500);

} ///:

En el constructor de MyButton, el color del botn se fija mediante una llamada a SetBackground(). Todos bs escuchas se instalan con simples llamadas a mtodos.

La clasc 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

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2307 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 uua cadena de caracteres en la matriz event; todo lo dems se hace automticamente.

Cuando se invoca report(). 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 suceso 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 programa.

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

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2308 foco, los caracteres escritos aparezcan en el campo JTextField.

Ejercicio 11: (4)Heredeun nuevo tipo de botn de JButton. Cada vez que pulse este botn, debe cambiar su color a

o tro color elegido aleatoriamente. Consulte ColorBoxes.java (ms adelante en el captulo) para ver cmo generar un valor aleatorio de color.

Ejercicio 12: (4) Monitorice un nuevo tipo de suceso en TrackEvent.java aadiendo el nuevo cdigo de tratamiento de

sucesos. Deber descubrir por si mismo el tipo de suceso que quiera monitorizar. Una seleccin de componentes Swing

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2309 Ahora que comprendemos los gestores de disposicin y el modelo de sucesos, estamos preparados para ver ccmo pueden utilizarse los componentes Swing. Esta seccin es un recorrido no exhaustivo de los componentes Swing y de las caractersticas que probablemente vaya a utilizar la mayor parte de las veccs. Hemos intentado que cada ejemplo sea razonablemente 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 tuente descargable correspondiente a este captulo (>vwv:MmdView. net).

2.

La documentacin del .TDK 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 escribir e instalar una nueva rutina de tratamiento

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2310 para un tipo de suceso concreto. Utilice el programa de bsqueda ShowAddListeners.java, presentado anteriormente en el captulo, como ayuda a la hora de investigar un componente 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 elementos de men heredan de AbstractButton (que en realidad, ya que estn incluidos los elementos de men, debera, probablemente, haberse llamado AbstractSelector o algn otro nombre igualmente genrico). Ms adelante veremos la utilizacin de los elementos de men, pero el siguiente ejemplo ilustra los distintos tipos de botones disponibles: //: gui/But tcns.j ava / / Diversos botones Swing. import javax.swing.*; import j avax.swing.bor der.*; import javax.swing.pi af.basic.*; import j ava.awt.*; import static net.mindview.util.SwingConsole.*; public class Buttons extends JFrame { prvate JButton jb = new JButton("JButton");

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2311 private BasicArrowButton up = new BasicArrowButton(BasicArrowBu tton.NORTH), down = new BasicArrowButton(BasicArrowBu tton.SOUTH), right = new BasicArrowButton(BasicArrowBu tton.EAST), left = new BasicArrowButton(BasicArrowBu tton.WEST); public Buttons() { setLayout(new FlowLayout()) ; add(jb) ; add(new JToggleButton("JToggleButton")); add(new JCheckBox("JCheckBox")); add(new JRadioButton("JRadioButtonn)); JPanel jp = new JPanelO; jp.setBorder(new TitledBorder("Directions")); jp.add(up); jp.add(down); jp.add(left); jp.add(right); add(jp);

} public static void main(String[] args) { run(new Buttons(), 350, 200);

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2312 }

} ///:-

E! ejemplo comienza con el botn BasicArrowButton de javax.swing.plaf.basic. y luego continua con los diversos tipos especficos de botones. Cuando se ejecuta el ejemplo, vemos que el botn conmutador mantiene su ltima posicin, pulsado o no pulsado. Pero las casillas de verificacin y los botones de opcin se comportan de forma idntica, permitiendo simplemente 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 bolones. 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

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2313 reflexin para generar los grupos de diferentes tipos de botones. Esto se ve en makeBPanel(), que crea un grupo de botones en un control JPanel. El segundo argumento de makeBPancl() es una matriz de objetos String. Para cada objeto String, se aade al control JPauel un botn de la clase representada por el primer argumento: //: gui/ButtonGroups.java // Uso del mecanismo de reflexin para crear grupos // de diferentes tipos de botones AbstractButton. import javax.swing. * import javax.swing.border.*; import j ava.awt.*; import java.lang.reflect.*; import static net.mindview.til.SwingConsole.*; public class ButtonGroups extends JFrame ( private static String[] ids = ( "June", wWard", "Beaver", "Wally", "Eadie", "Lutapy"

}; static JPanel makeBPanel( Classc? extends AbstractButton kind, String11 its) { ButtonGroup bg = new ButtonGroup(); JPanel jp = new JPanel() ; String title kind.getName(); title title.substring(title.lastlnd exOf(1) + 1); jp.setBorder(new TitledBorder(title)); for(String id : ids) { AbstractButton ab = new JButton("failedu) try { // Obtener el mtodo constructor

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2314 dinmico // que toma un argumento de tipo String: Constructor ctor = kind.getConstructor(String.class); // Crear un nuevo objeto: ab = (AbstractButton)ctor.newlnstance(id); } catch(Exception ex) { System, err .nrintln ("car.11 create " + kind);

} bg.add(ab) ; jp.add(ab); i return jp;

) public ButtonGroups() { setLayout(new FiowLayout()) ; add(make3Panel(JButton.class, ids)); add(makeBPanel(JToggleButton. class, ids)); add(makeBPanel(JCheckBox.clas s, ids)); add(makeBPanel(JRadioButton.c lass, ids));

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2315 } public static void roain (String j args) { runnew ButtonGroups0, 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 JButton 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 tipos contenidos en la lista de objetos Class que se pasan a getConstructor(). Entonces todo lo que tenemos que hacer es invocar newlnstance(), 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

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2316 programa, veremos que todos los botones excepto JButton exhiben este comportamiento de tipo or exclusivo. Iconos

Podemos utilizar un objeto Icn dentro de un control JLabel o de cualquier control que herede de AbstractButton (incluyendo JButton, JCheckBox, JRadioButton y los diferentes tipos de JMenuItem). Utilizar Icn con JLabel resulta bastante 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 control Imagelcon y pasarle el nombre del ai-chivo. A partir de ah. podr emplear el icono resultante en su programa. //: gui/Paces.java // Comportamiento de los iconos en controles JButton. import j avax.swing.*; import j a va. awt. * import: j ava. awt. event. * ; import static net .mindview.util.SwingConsole.* ,public class Faces extends JFrame { private static Icon[] faces; private JButton jb, jb2 = new JButton("Disable"); private boolean mad = falser- public Faces() { faces e new Icon[]{

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2317 new Imagelcon(getClass().getResou rce C'FaceO.gif")), new Imagelcon(getClass{).getResou rce("Facel.gif")), new Imagelcon(getClass().getResou rce("Face2.gif")), new Imagelcon(getClass().getResou rce("Face3.gif")), new Imagelcon (getClass () -getResource ("Face4 .gif ")),

}; jb = new JButton("JButton", faces[3]); setLayout(new FlowLayout()); jb.addActionListener(new ActionListener!) { public void actionPerformed(ActionEv ent e) { if(mad) ( jb.setl con(fac es| 3] ); mad = false; } else { jb.setl con(fac es[0] ) ; mad true;

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2318 } jb.setVerticalAlig nment(JButton.TOP) ; jb.setKorizcntalAl ignment(JButton.L3 FT);

}); jb.setRoll overEnable d(true); jb.setRoll overlcon(f aces llj); jb.setPres sedlcon(fa ces [2])? jb.setDisa biedlcon(f aces 14)); jb.setTool TipText("Y owl); add(jb); jb2 .addActior.Listener (new ActionListener () public void actionPerformed(Actio nEvent: e) ( if (jb. isEnabledO ) { jb.setEnabled(false ); jb2. setText {

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2319 ("Enable'1) ; } else { jb.setEnabled(true ); jb2.setText("Disab le");

} }).add(jb2) ;

} public static void main(StringI] args) { run(new Faces(), 250, 125);

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2320 } } III:-

Podemos emplear un objeto Icon como argumento para muchos constructores diferentes de componentes Swing, pero tambin podemos usar setlcon( ) para aadir o modificar un objeto Icon. Kste ejemplo tambin muestra cmo un control JButton (o cualquier control AbstractButton) puede fijar los distintos tipos de conos 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. Sugerencias

En el ejemplo anterior hemos aadido una sugerencia a un botn. Casi todas las clases que utilizaremos para crear nuestras interfaces de usuario derivan de JComponent, que contiene un mtodo denominado sctToolTipText(String), que sirve para definir una sugerencia Uool tip). Por tanto, para casi todos los componentes que coloquemos en el formulario, lo nico que hace falta es decir (para un objeto je de cualquier clase derivada de JComponent): je.setToolTipText("Mi sugerencia ");

Cuando el ratn permanezca 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.

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2321 Campos de texto

Este ejemplo muestra lo que se puede hacer con los controles JTextField: / / : gui/TextFields.java // Campos de texto y sucesos Java. import javax.swing.*; import j avax.swing.event.*; import j avax.swing.text.*; import j ava.awt.*; import j ava.awt.event.*; import static net.mindview.til.SwingConsole. *; public class TextFieIds extends JFrame { private JButton bl = new JButton(nGet Text" ) , D 2 = new JButton("Set Text"); private JTextField tl = new JTextFie ld(30) , t2 = new JTextFie ld(30), t3 = new JTextFie ld(30) ; private String s = private UpperCaseDocument ucd = new UpperCaseDocument(); public TextFieldsO

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2322 { tl.setDocument(ucd); ucd.addDocumentListener(new Tl()); bl.addActionLiotcncr(now Bl ()) h 2 .addActionListener(new B2()); tl.addActionListener(new TIA() ) ; setLayout(new FlowLayout())/ add(bl); add(b2); add(tl); add(t2); add(t3);

} class Tl implements DocumentListener { public void changedpdate(DocumentEv ent e) {} public void insert'Jpdate (DocumentEvent e) { t2.setText(tl.aetText( )); t3.setText(Text: n+ tl.getText());

} public void removepdate(Document Event e) ( t2.setText(tl.getTe xt());

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2323 }

) class TIA implements ActionListener { private int count = 0; public void actionPerformed(Action Event e) { t3.setText{"ti Action Event " + count++);

} class B1 implements ActionListener { public void actionPerformed(Action Event e) { if(ti.getSelectedTex t{) == nuil) s a ti.getText() ; else

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2324 s = ti.getSelectedText(}/ ti.BetEditable(true);

) class B2 implements ActionListener { public void actionPerformed(Action Event e) { ucd.setpperCase(fal se); ti.setText("Inserted by Button 2: " + s) ; ucd.setpperCase(true) ; ti.setEditable(false);

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2325 } public static void main(String[] args) { runnew TextFields(), 375, 200);

} class pperCaseDocument extends PlainDocument { private boolean upperCase = true; public void setUpperCase(boolean flag) { upperCase = flag;

) public void insertString(int offset, String str, AttributeSet attSet) throws BadLocationException { if(upperCase) str = str.toUpperCase(); super.insertString(off set, str, attSet);

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2326 } } ///:-

El conol t3 de tipo JTcxtField se incluye para disponer de un lugar en el que informar cada vez que se dispare el escucha 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 ti de tipo JTextField tiene asociados varios escuchas. El escucha TI es un objeto DocumentListener que responde a cualquier cambio que se produzca en el documento'* (el contenido del control JTexfField, en este caso). Esc escucha copia automticamente todo el texto de (1 en ti. Adems, el documento de ti 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 cursor de texto y gestionando toda la operacin de la manera que cabra esperar.

Ejercicio 13: (3) Modifique TcxtFields.java para que los caracteres en 12 retengan su condicin original de mayscu

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2327 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 denominado sho\vBorder() que crea un control JPanel y agrega en cada caso el borde correspondiente. Asimismo, el ejemplo utiliza el mecanismo RTT1 para averiguar el nombre del borde que se est usando (quitando la informacin de nita) y luego inserta dicho nombre en una etiqueta JLabel situada en la zona central del panel: //: gui/Borders.java // Diferentes bordes Swing. import j avax.swing.*; import javax.swing.border.*; import java.awt.*; import static net.mindview.til.SwingConsole.*; public class Borders extends JFrame { static JPanel showBorder(Border b) { JPanel jp = new JPanel (),jp.setLayout(new BorderLayout()); String nm = b.getClass().toString); nm = nm.substring(nm.lastlndexOf('.) + 1) ,* jp.add(new JLabel(nm, JLabel.CEN7ER), BorderLayout.CENTER) ; jp.setBorder(b); retum jp;

public Borders() { setLayout(new GridLayout(2,4)); add (showBorder (new Titled3order ("Title11))) ;

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2328 add(showBorder(new StchedBorder())); add(showBorder(new LineBorder(Color.BLUE))); add(showBorder( new MatteBorder(5,5,30,3 0,Color.GREEN) )) ; add(showBorder( new BevelBorder(BevelBor der.RAISED))); add(showBorder( new SoftBevelBorder(Beve lBorder.LOWERED))); add(showBorder(new CompoundBorder{ new StchedBorder(), new LineBorder(Color.RBD ))));

} public static void main(Stringfl args) { run(new BordersO, 500, 300);

//: gui/ShowAddListeners.java 22 Interfaces grficas de usuario 2329 } ///:-

Tambin podemos crear nuestros propios bordes y colocarlos en botones, etiquetas, ctc., es dccir, en cualquier cosa derivada de JComponent. Un mini-editor

El control JTextPane proporciona un gran soporte de edicin de texto, sin demasiado esfuerzo. El siguiente ejemplo hace un uso muy simple de este componente, ignorando gran pane de su funcionalidad: //: gui/TextPane.j ava / / E l control JTextPane es un pequeo editor. import j avax.swing.*; import java.awt.*; import java.awt .event. import net.mindview.til.*; import static net.mindview.til.SwingConsole; public class TextPane extends JFrame { private JButton b = new JButton("Ada Text"); private JTextPane tp = new JTextPaneO; private static Generator sg = new RandomGenerator.String(7) pub]ic TextPane()

22 Interfaces grficas de usuario 2330 {addActionListener(new ActionListener() { public void actionPerformed(ActionEvenL e) { for(int i c 1; i < 10; i++) tp.setText (tD.get.Text () + sg.next() + M\n">;

> addnew JScrollPane(tp)); add(BordexLayout.SOUTH, b);

} puolic static void main(String[] args) ( run(new TextPane(), 475, 425);

22 Interfaces grficas de usuario 2331 } } ///:-

El botn aade texto generado aleatoriamente. 1.a 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 utilizando setText().

Los elementos se aaden al control JFrame 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 SOUTII, por lo que el componente encajar en dicha regin, 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_

22 Interfaces grficas de usuario 2332 Ejercicio 14: (2)ModifiqueTextPane.javaparausaruncontrolJTextArea JTextPane. Casillas de verificacin en lugar de

Una casilla de verificacin proporciona una forma de efectuar una nica eleccin binaria. Est formada por un pequeo recuadro y una etiqueta. El recuadro almacena normalmente una pequea x minscula (o alguna otra indicacin de que la casilla est activada) o est vaca, dependiendo de si el elemento ha sido seleccionado o no.

Normalmente, 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 forma 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: //: gui/CheckBoxes.j ava // Utilizacin de controles JCheckBox. import j avax.swing.*; i

22 Interfaces grficas de usuario 2333 import j ava.awt.*; import j ava.awt.event.*; import static net.mindview.util.SwingConsole.*; public class CheckBoxes extends JFrame { private JTextArea t = new JTextArea(6, 15); private JCheckBox public CheckBoxesO { cbl.addActionListener(ne w ActionListener() { public void actionPerformed(ActionEv ent e) { trace("1", cbl); cb2.addActionListener(ne w ActionListener() { public void actionPerformedtActionSv ent e) { trace("2", cb2);

})- cb3.addActionListener(new ActionLstener() public void actionPerformed(Actio nEvent e) i (

22 Interfaces grficas de usuario 2334 { trace("3", cb3) ;

} )>; setLayout(new FlowLayout()); add(new JScrollPane(t)); add(cbl); add(cb2); add(cb3);

! private void trace (String b, JC'neckBox cb) { if(cb.isSeiected()) t.appendCBox " + b + M Set\nM); else t. append (H Box + b + " Cleared\n") ;

} public Btatic void tiiain (String [j args) { run(new CheckBoxes(), 200,

22 Interfaces grficas de usuario 2335 300);

} j m-.-

El mtodo trace() enva el nombre de la casilla JCheckBox seleccionada, junto con su estado actual, al control JTextArea utilizando el mtodo appeud(), 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 i

22 Interfaces grficas de usuario 2336 El concepto de botones de opcin (o de radio) en la programacin de interfaces GUI proviene de las radios da 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 eleccin 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 true (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 ActionLstener: //: gui/RadioButtons.java // Utilizacin de controles JRadioButton. import javax.swing.; import java.awt.*; import j ava.awt.event.*; import static net.mindview.util.SwingConscle.*; public class RadioButtons extends JFrame { private JTextField t - new JTextField(15); private ButtonGroup g = new ButtonGroup(); private JRadioButton rbl = new JRadioButton("onen i

22 Interfaces grficas de usuario 2337 , false), rb2 = new JRadioButton("two", false), rb3 = new JRadioButton("three1, false); private ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e) { t .setText ('Radio button " + ((JRadioButton)e.getSource()).aetText());

} }. public RadioButtons() { rbl.addActionListener(al); rb2.addActionListener(ai); rb3.addActionListener(al); g.add(rbl); g.add(rb2); g.add(rb3); t. setEditable(false) setLayout(new FlowLayout()); add(t) ,* add(rbl); add(rb2); add(rb3);

22 Interfaces grficas de usuario 2338 } public static void main(String[] args) { run(new RadioButtons(), 200, 125);

} } ///:-

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 elemento de entre un grupo de posibilidades. Sin embargo, es una forma ms compacta de conseguir este efecto y una forma 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).

22 Interfaces grficas de usuario 2339 De manera predeterminada, el recuadro JComboBox no se parece al cuadro combinado de Windows, que permite seleccionar un elemento de una lista o escribir nuestra propia seleccin. Para conseguir este comportamiento debemos invocar el mtodo setbditable(). 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 recuadro cuando se pulsa un botn. //: gui/CoraboBoxes.java // Utilizacin de listas desplegables. import j avax.swing.*; import j ava.awt.*; import java.awt.event.*; import static net.mindview.util.SwingConsole.*; public class ComboBoxes extends JFrame { private String[J description = { "Sbullient", "Obtuse, "Recalcitrant", "Brilliant", "Soranescent", "Timorous", "Floridn, "Putrescer.t"

} prvate JTextField t - new JTextField(15) ; private JComboBox c = new JComboBox() ; private JButtoji b = new JButton ("Add tems") ; private int count = 0; public ComboBoxes() { for(int i = 0; i < 4; i++) c .adaItem(descri ption [count-*+3); t.setEditable(f alse); i

22 Interfaces grficas de usuario 2340


b.

onListener(new

addActi

ActionListener() { public void actionPerformed(ActionEv ent e) { if(count < description.length) c.addItem(description[count++]);

} }>;
c.

addActionListener(new ActionListener() { t. setText C'index: ff+ c .getSelectedIndex() + " "-t((JCoirboBox) e.getSource()) .getSelectedltemO ) ;

public void actionPerformed(ActionKvent e) {

) }>; setLayout(new FlowLayout <)); add(t); add (c) ; add(b);

22 Interfaces grficas de usuario 2341 } public static void raain(String[] args) j run(new ComboBoxes(), 200, 175);

} ///:-

El control JTextField muestra el ndice seleccionado", que es el nmero de secuencia del elemento actualmente seleccionado. junto con el texto que ese elemento tiene en el recuadro combinado. Cuadros de lista

Los cuadros de lista difieren significativamente de los cuadros JConiboBox? y no slo por su distinta apariencia visual. Mientras que un recuadro JComboBox se despliega cuando los activamos, un contro JUst ocupa un nmero fijo de lnea dentro de una i

22 Interfaces grficas de usuario 2342 pantalla durante todo el tiempo, y no se modifica. Si queremos ver los elementos de una lista, basta con invocar getSe!ectedValues(), que produce una matriz de objetos String de los elementos que hayan sido seleccionados.

Un control JT.ist permite efectuar selecciones mltiples; si hacemos control-clic sobre ms de un elemento (manteniendo pulsada la tecla Control mientras realizamos clics adicionales con el ratn), el elemento original continuar estando resaltado 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. //: gui/List.java import javax.swing.*; import javax.swing.bo rder.*; import javax.swing.ev enl.*; import j ava.awt.*; import java.awt.event. import static net.mindview.util.SwingConscle.*; public class List extends JFrame { private String[] flavors = { "Chocolate", "Strawberry", "VaniHa Fudge Swirl", "Mint Chip", "Mocha Almona Fudge", nRum Raisin", }; "Praline Cream", nMud Pie"

private DefaultListModel lltems = new DefaultListModel(); private JList lst = new JList(lltems); private JTextArea t = i

22 Interfaces grficas de usuario 2343 new JTextArea(flavors.length, 20); private JButton b = new J3utton("Add Item"); private ActionListener bl = new ActionListener() { public void actionPerformed(ActionEvent e) { if(count < flavors.length) { 1Items.add(0, flavors[count++]); } else { // Desactivar, ya que no quedan ms // sabores por aadir a la lista. b.setSnabled(false) ;

}; private LiscSelectionLiscener 11 = i

22 Interfaces grficas de usuario 2344 new ListSelectionListener() { public void valueChanged(ListSelectio nEven^ e) { if(e.getValuelsAdjustin gO) return; t .setText C") ; or(Object item : 3st.getSelectedValues() ) t.apoend(item + "\n");

};

prvate int count = 0? public List() { t.setEditable(false); setLayout(new FlowLayout()) ; // Crear bordes para los componentes: Border brd = BorderPactory.createMatteBorder( 1, 1, 2, 2, Color.BLACK); lsc.setBorder(brd); t.setBorder(brd); // Aadir los primeros cuatro elementos a ]a lista fordnt i = 0; i < 4; i+ + ) 1Items.addElement(flavors fcounc++]); add(t); add(Ist); add(b); // Registrar escuchas de sucesos lst.addListSele ctionListener(1 1); i

22 Interfaces grficas de usuario 2345 b.addActionList ener(bl);

) public static void main(String[] args) { run(new List(), 250, 375);

} ) ///.-

Como podr observar tambin se han aadido bordes a las listas.

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 lisia automticamente. La nica razn para usa: el modelo de lista en el ejemplo precedente es para poder manipular la lista durante la ejecucin del programa. i

22 Interfaces grficas de usuario 2346 JList no proporciona un soporte directo automtico para el desplazamiento de pantalla. Por supuesto, nos bastara con insertar el control JList en un panel JScrollPane. que se encargara de gestionar todos los detalles por nosotros.

Ejercicio 16: (5)SimplifiqueList.javapasando la posibilidad de adicin dinmi

la matriz al constructor y eliminando

ca de elementos a la lista. Tableros con fichas

F.I control JTabbedPane 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 fichas, aparece un cuadro de dilogo diferente. //: gui/TabbedPanel.java // Ejemplo de panel con fichas, import j avax.swing.*; import j avax.swing.event.*; import j ava.awt.*; import static net.mindview.til.SwingConsole.*; public class TabbedPanel i

22 Interfaces grficas de usuario 2347 extends JFrame { private String[] flavors = { "Chocolate", ''Strawberry,,, "Vanilla Fudge Swirl", "Mint Chip", "Mocha Almond Fudge", "Rum Raisin", }; "Praline Cream", "Mud Pe"

private JTabbedPane tabs = new JTabbedPane(}; private JTextField txt = new JTextField(20) ; public TabbedPanel() { int i = 0 ; for(String flavor : flavors) tabs.addTab(flavorslij , new JButton("Tabbed pane " -r i++)) ; tabs.addChangeListener(n ew ChangeListener() { public void stateChanged(Change3ven t e) { txt.setText("Tab selected: " + tabs.getSelected! ndex());

}>; i

22 Interfaces grficas de usuario 2348 add (Borde r Layout. SOUTH, t xt) add(tabs);

} public static void main(String[] args) { run(new TabbedPanel0, 400, 250);

} } ///:-

Al ejecutar el programa, vemos que el control JTabbedPane 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 ejecutar el programa desde la lnea de comandos de la consola. Recuadros de mensaje

22 Interfaces grficas de usuario 2349 Los entornos de ventanas suelen contener un conjunto estndar de recuadros de mensaje que permite presentar rpidamente informacin al usuario o capturar informacin del usuario. En Swing, estos recuadros de mensajes estn con:enidos 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 confirmacin, que se invocan con los mtodos estticos JOptionPane.showMessageDialog( ) y JOptionPane.showConfiiinDialog( ). El siguiente ejemplo muestra un subconjunto de los recuadros de mensajes disponibles con JOptionPane: //: aui/MessageBoxes.java // Ejemplo de JOptionPane. impert. j avax. swing. * ; import java.awt.*; import java.awt.event.*; import static net.mindview.til.SwingConsole.*; public class MessageBoxes extends JFrame ( private JButton[] b = { new JButton("Alert"), new JButton("Yes/No"), new JButton (Color*), new JButton (H InputM) , new JButton("3 Vals")

> private JTextFieid txt = new JTextField(15), private ActionListener al = new ActionListener() { public void actionPerformed(ActionBvent e) { String id = ((JButton)e.getSource()) .getText(); if (id.equals { " Alert")) JOptionPane.showMessageDialog(null, i

22 Interfaces grficas de usuario 2350 "There 1s a bug on you!M, "Hey!", JOptionPane.ERR0R_MESSAG3) ; else if (id.equals ("Yes/No")) JOptionPane.showConfirmDialog{null, "or no", choose yes", JOptionPane. YES__NO_OPTION) ; else If(id.equals("Color")) { Object [] options = { Red", "Green }; int sel = JOptionPane.showOpti onDialog( null, "Choose a Color!1', "Warning", JOptionPane.DEFAULT_0PTI0N, JOptionPane.WARNING_MESSAG E, null, options, options[0]); if(sel !* JOptionPane.CLOSED_OPTION) txt.setText("Color Selected: " + options[selj); ) else if(id.equals("Input")) "How many fingers do you see?"); txt.setText(val); } else if(id.equals("3 Vals")) { Object[] selections = {"First", "Second", "Third"}; Object val = JOptionPane.showInputDialog( null, "Choose one", "Input", JOptionPane.INF0RMA7I0N_ME SSAGE, null, selections, selections[Oj); if(val 1= null) txt.setText(val.toString()); { String val <* JOptionPane . showInputDialog (

22 Interfaces grficas de usuario 2351 } public MessageBoxes() { setLayout(new FlowLayout())/ for(int i = 0; i < b.length; i++) { b[i].adActionListener(al ); add (b ti]);

) add (txt) ,*

} public static void main(String[] args) { run(new MessageBoxes(), 200, 200);

} } ///:*

22 Interfaces grficas de usuario 2352 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 fcil obtener la etiqueta de manera errnea, normalmente en lo que respecta al uso de maysculas y minsculas, y estos errores pueden ser difciles de detectar.

Observe que showOptionDialog() y sho\vInputDialog( ) 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 JOptionPane para proporcionar un mensaje de confirmacin al usuario .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 botn). Mens

22 Interfaces grficas de usuario 2353 Cada componente capaz de almacenar un men, incluyendo JApplet, JFrame, JDialog y sus descendcndientes, dispone de un mtodo setlMen Bar() que acepta un objeto JMenuBar que representa una barra de men (slo puede haber un componente JMenuBar en cada componente concreto). Lo que hacemos es aadir mens JMenu a la barra de mens JMenuBar y elementos de men Jnienultem a los mens JMenu. Cada objeto JMeuultem puede tener asociado un escucha 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. lie aqu un ejemplo simple de men: //: gui/SimpleMenus.java import javax.swing.*; import j ava.awt.*; import java.awt.event.*; import static net.mindview.til.SwingConsole.*; public class SimpleMenus extends JFrame { private JTextField t new JTextField(15); private ActionListener al * new ActionListenerO { public void actionPerformed(ActicnEvent e) { t.setText(((JMenuItem)e.getSource()).getTextO);

22 Interfaces grficas de usuario 2354 }; private JMenu[] menus = { new JMenu("Winken"), new JMenu("Blinken"), new JMenu("Nod")

}; private JMenuItem [] itema new JMenuItem("Fee"), new JMenuItem("Fon) , new JMenuItem("Zap"), , new JMenuItem("011y"), new JMenuItem("Oxen"), new JMenuItem("Free") new new { JMenuItem("Fi"), JMenuItem("Z ip"), new JMenuItem("Zot")

); public SimpleMenus() { for(int i = 0; i < iteras.length; i++) { items[il.addActionLi stener(al); menus[i % 3].add(tems[ i ]);

22 Interfaces grficas de usuario 2355 } JMenuBar mb ^ new JMenuBar () ; for(JMenu jm : menus) mb.add(jm); setJMenuBar (mb); setLayout(n ew FlowLayout( )); add(t);

} public atatic void main(String(] args) { run(new Sirr^leMenus () , 200, 150);

} } ///:-

El uso del operador mdulo en *li%3 distribuye los elementos de men entre los tres controles JMenu. Cada objeto JMenuItem puede tener asociado un escucha i

22 Interfaces grficas de usuario 2356 ActionListener; aqu, se utiliza el mismo escucha ActionListener en todas partes, pero lo normal es que necesitemos un escucha distinto para cada elemento JMenuItem.

JMenuItem hereda de AbstractButton, 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); JCheckBoxMenultem, que produce una casilla de verificacin para indicar si dicho elemento de men est seleccionado y JRadioButtonMeniiItcm. que contiene un botn de opcin.

Como ejemplo ms sofisticado, he aqu de nuevo el ejemplo de los sabores de helados para crear mens. Este ejemplo tambin ilustra la definicin de mens en cascada, de atajos de teclado, de elementos JCheckBoxMenultem, y tambin muestra la forma de cambiar dinmicamente los mens. //: gui/Menus.java // Submens, elementos de men con casillas e verificacin, mens // intercambiables (atajos de teclado) y comandos de accin, import j avax.swing.*; import java.awt.*; import j ava. awt. everit. * ; import static net .mindview.util .SwingConsole. * public class Menus extends JFrame { prvate String ] flavors = { "Chocolate", "Strawberry", "Vanilla Fudge Swirl", "Mint Chip", "Mocha Almond Fudge1, "Rum Raisin", }; i "Praline Cream", "Mud Pie"

22 Interfaces grficas de usuario 2357 private JTextField t= fiavor", 30); private JMenuBar mbl= new private JMenu f = new JMenu("File") , m = new JMenu("Flavors"), s = new JMenu ("Safety11) ; // Solucin alternativa: private JCheckBoxMenuItemfl safety = { new JCheckBoxMenuItem(,lGuardn), new JCheckBoxMenuI tem ("Hide,r) private JMenuItemC] file = { new JMenuItem("Open") // Una segunda oarra de men para efectuar un intercambio: private JMenuBar mb2 = new JMenuBar(); private JMenu fooBar= new JMenu("focBar") ; private JMenuItem[] other = { // Adicin de un atajo de teclado es muy simple, // pero slo pueden incluirse los atajos en les constructores // en el caso de los elementos JMenuItem: new JMenuItem("Foo", KeyEvent.VK_F), new JMenuItem("Bar", KeyEvent.VK_A), }; // Sin atajo de teclado: new .JMenuItem("Baz") , }; new JTextFiel( "No

JMenuBar O;

private JButton b = new JButton("Swap Menus"); clasa BL implements ActionListener { public void actionPerformed(ActionEvent e) } JMenuBar m - getJMenuBar(); set JMenuBar (m =-- mbl ? mb2 : mbl) ; valdate(); // Refrescar el marco

22 Interfaces grficas de usuario 2358 >

} class ML implements ActionListener { public void actionPerformed(ActionSvent e) { JMenuItem target =* (JMenuItem) e.getSource ()

22 Interfaces grficas de usjario 2359 ;String actionCommand = target .get Act i or* Command (} ; if (actionCommand.equals ('Open)} { String s = t.getText(); boolean chosen = false; for(String flavor : flavors) if (s.equals(flavor)) chosen => true; if(!chosen) t.setText("Choose a flavor first I"); else t.setText ("Opening n + s + ". Mmm, mral") ;

} class FL implements ActionListener ( public void actionPerformed(ActionSvent e) { JMenuItem target = (JMenuItem)e.getSourceO; t.setText(target.getText{));

2360 Piensa en Java }

} // Alternativamente, podemos crear una clase diferente // para cada elemento Menultem. Entonces, no tenemos // por qu tratar de figuramos cul es: class FooL implements ActionListener { public void actionPerformed(Action3vent e) { t.setText("Foo selected");

} class BarL implements ActionListener { public void actionPerformed(Actionsvent e) { t.setText("Bar selected");

22 Interfaces grficas de usjario 2361 } class BazL implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText("Baz selected");

>

} class CMIL implements ItemListener { public void itemstateChanged(Item2vent e) { JCheckBoxMenuItem target = (JCheckBoxMenuItem)e.getSource(); String actionCommand = target.getActionCommand(); if(actionCommand.equals("Guard")) t.setText("Guard the Ice Cream! " + "Guarding is " + target.getState()); else if(actionCommand.equals("Hide") ) t.setText("Hide the Ice Cream! " + "Is it hidden? ' + target .getState()) ;

2362 Piensa en Java } public Menus() { ML ml = new ML () ; CMIL cmil = new CMIL(); safety[0].setActionCommand("Guard"); safety[0].setMnemonic(KeySvent.VK_G); safety[0].aadltemListener(cmil); safety[1].setActionCommand("Hide"); safety llj .setMnemonic(KeySvent.VKH) ;safety[l].addltemListener(cmil); other101.addActionListener(new FooL{)); other [1] .addActionListener(new BarLO ) ; other [2] .addActionListener(new BazLO ) ; FL fl = new FL () ; int n = 0; for(String flavor : flavorsj { JMenuItem mi = new JMenuItem(flavor); mi.addActionListene r(f1); m.add(mi); // Aadir separadores a intervalos: if ((n+ + + 1) % 3 == 0) m.addSeparauor();

} for(JCheckBoxMenult em sfty : safety) s.add(sfty); s.setMnemonic(KeyEv ent.VK_A); f.add(s); f.setMnemonic(KeyEv ent.VK_F); for (int i = 0; i < file.length; i++) { file [11 .addActionListener( fl) ; f.add(file[il)?

22 Interfaces grficas de usjario 2363 } mbl.add(f); mbl.add(m); setJMenuBar(mbl); t.setEditable(false); add(t, BorderLayout.CEKTER); // preparar un sistema para intercambiar mens: b.addActionListener(new BL()); b.setMnemonic(KeyEvent.VK _S); add(b, 3orderLayout.NORTH); for(JMenuItem oth : other) fooBar.add(oth)? CooBar.setMnemonic(KeyEve nt.VK_B); mb2.add(fooBar);

} public static void main(String[] args) { run(new Menus(), 300, 200);

} } III:-

2364 Piensa en Java En este programa, hemos colocado los elementos de men en matrices y luego hemos recorrido cada matri2 invocando add() para cada elemento JMenuItem. Esto hace que la adicin o eliminacin de un elemento de men sea algo menos tedioso.

Este programa crea dos controles JmcnuBar para demostrar que las barras de men pueden intercambiarse de manera activa mientras el programa se est ejecutando. Podemos ver cmo los patrones JMenuBar estn compuestos de elementos JMenu, y que cada control JMenu est formado por elementos JMenuItem, JCheckBoxMenultem, o incluso por otros elementos JMenu (lo que permite obtener submens). Cuando se define un contTol JMenuBar, se puede instalar en el programa actual mediante el mtodo setJMenuBar(). Observe que, cuando se pulsa el botn se comprueba qu men est instalado actualmente invocando getJMenuBar() y luego se sustituye por el otro men.

A la hora de comprobar la correspondencia con la cadena de caracteres Opcn, observe que la ortografa y la utilizacin de maysculas y minsculas son crticas, pero que Java no proporciona ninguna seal de error si no se detecta ninguna correspondencia con Open. Este tipo de comparacin entre cadenas de caracteres constituye una fuente de errores de programacin.

La activacin y desactivacin de los elementos de men se gestiona automticamente. El cdigo que gestiona los elementos JCheckBoxMenultem muestra dos formas distintas de determinar qu es lo que se ha activado: comparacin de cadena 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 estado de un objeto JCheckBoxMenutem con setState().

22 Interfaces grficas de usjario 2365 Los sucesos relacionados con los mens son algo incoherentes y pueden conducir a confusin: los elementos JMenuItem utilizan escuchas ActionListener, mientras que los elementos JCheckBoxMenutem utlizan escuchas IteinListener. Los objetos JMenu tambin pueden soportar los escuchas ActionListener, aunque eso no suele resultar til. En general, asociaremos escuchas a cada elemento JMenuItem, JCheckBoxMenutem 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 (botones, elementos de men, etc.) utilizando el teclado en lugar del ratn. Estos atajos funcionan de forma muy simple; para JMenuItem, podemos utilizar el constructor sobrecargado que admite como segundo argumento el identifcador de la tecla. Sin embargo, la mayora de las clases AbstractButton no tiene constructores como ste, por lo que la forma 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 informan del atajo de teclado.

Tambin podemos ver cmo se utiliza el mtodo $etActionCommand(). Este mtodo parece algo extrao, porque en cada caso el comando de accin, definido por el mtodo coincide exactamente con la etiqueta del men. Por qu no utilizar simplemente la etiqueta en lugar de esta cadena de caracteres alternativa? El problema estriba en la intemacionalizacin. 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 modificar. Todo el cdigo funciona con el comando de accin, as no se ve afectado por los cambios que efectuemos en las etiquetas de men. Observe que en este programa, no se examinan todos los componentes de men para determinados comandos de accin, de modo que no hemos configurado aquellos componentes de men donde ese examen no se realiza.

2366 Piensa en Java 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 ActionF.vent y proyectndolo sobre JMenuItem, despus de lo cual se extrae el comando de accin para pasarlo a travs de una cascada de instrucciones 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, convier.e emplear la solucin por FooL, BarL y BazL, en la que cada escucha se asocia a un nico componente de men, lo que hace innecesario utilizar una lgica adicional de deteccin y nos permite saber exactamente quin ha invocado el escucha. Incluso con la profusin de clases que de esta manera se genera, el cdigo tiende a ser ms pequeo, y el proceso est ms libre de errores.

Como puede ver, el cdigo de especificacin de mens puede llegar rpidamente a ser muy largo y contuso. 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 verificacin en los

22 Interfaces grficas de usjario 2367 mens.

Ejercicio 20: (6) Cree un programa que descomponga un archivo texto en sus palabras componentes. Distribuya dichas

palabras como etiquetas en una serie de mens y submens. Mens emergentes

La forma 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 comportamiento emergente: //: gui/Popup.j ava // Creacin de mens emergentes con Swing. import j avax.swi ng.*; import j ava.awt.*; import j ava.awt.event.*; import static net.mindview.utiI.SwingConsole.*; public class Popup extends JFrame { private JPopupMenu popup = new JPopupMenuO;

2368 Piensa en Java private JTex~Field t = new JTextField(10); public Popup() { setLayout(new FlowLayout()); add(t); ActionListener al ** new ActionListener () { public void actionPerfomted(ActicnEve nt e) { t.setText(((JMenultem)e.getSource()).getText());

};

JMenuItem m = new JMenuItem("Hither") ; m.addActionListener(al); popup.add(m); m = new JMenuItem("Yon"); m.addActionListener(al); popup.add(m); m new JMenuItem("Afar"); m.addActionListener(al); popup.add(m); popup.addSeparator(); m = new JMenuItem("Stay Here"); m.addActionListener(al); popup.add(m); PopupListener pi = new PopupListener() ; addMouseListener(pi); t.addMouseListener(pi);

22 Interfaces grficas de usjario 2369 } class PopupListener extends MouseAdapter { public void mousePressed(MouseEve nt e) { maybeShowPopup(e);

} public void mouseReleased(MouseEvent e) { maybeshowpopup(e);

} private void maybeshowpopup(MouseEvent e) { if(e.isPopupTrigger()) popup, show (e. get Component (), e.getXO, e.getYO);

2370 Piensa en Java } public static void main(String[] args) { run(new PopupO, 300, 200);

} } m-.~

En cl ejemplo se aade el mismo escucha ActionListener a cada elemento JMenuItem. El escucha extrae el texto de la etiqueta 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 suelen 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.

22 Interfaces grficas de usjario 2371 Kn aras de la simplicidad, considere el problema de representar una serie de datos en la pantalla; aqu, los dalos estarn proporcionados por el mtodo predefinido Math.sm(), que genera una funcin matemtica seno. Para hacer las cosas algo ms interesantes y para demostrar lo fcil que es utilizar los componentes Swing, colocaremos un deslizador en la parte inferior del formulario, para controlar dinmicamente el nmero de ciclos de la onda senoidal que se van mostrando. Adems, si cambiamos el tamao de la ventana, veremos que la onda seno se reajusta automticamente en la nueva ventana.

Aunque cualquier control JComponent puede ser pintado y ser utilizado como el lienzo, si queremos disponer de una superficie de dibujo sencillo, lo que haremos normalmente ser heredar de J Panel. El nico mtodo que hay que sustituir es paintConiponent( ), que se invoca cada vez que hay que repintar dicho componente (normalmente no hace falta preocuparse por esto, porque Swing toma la decisin). Cuando se invoca el mtodo, Swing le pasa un objeto de tipo Graphics, 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 SineDravv: la clase SlneWave simplemente configura el programa y el control deslizador. Dentro de SlneDraw, el mtodo setCycles() proporciona un mecanismo para que otro objeto (en este caso, el control deslizador) controle el nmero de ciclos. //.- gui/SineWave.java // Dibujo con Swing, utilizando un concrol JSlider. import javax.swing.*; import j avax.swing.event.*; import j ava.awt.*; import static net.mindview.til.SwingConsole.*; class SlneDraw extends JPanel { private static final int SCALEFACTOR = 200; private int cycl.es;

2372 Piensa en Java private int points; private double [] sines; private int [] pts; public SineDrawO { setCycies (5) ; } public void paintComponent(Graphics g) ( super.paintComponent(g); int maxWdth = getWidthO; double hstep = (double)maxWidth / (double)points; int maxHeight = getEeightO; pts new int [points] ; for(int i = 0; i < points; i++) pts ti] = (int)(sines[i] * maxHeight/2 * .95 + maxHeight/2); g.setColor(Color.RED); for(int i = l ; i < points; i++) { int xl = (int)((i- 1)*hstep); int x2 = (int)(i * hstep); ; int yl = pts [i'l] int y2 = pts[i];
g.

}'

drawLine(xl, yl, x2, y2);

} public void setCycies (int newCycles) { eyeles newCycles; points = SCALEFACTOR * eyeles * 2; sines = new double[points] ; or(int i - 0; i < points; i++) { double radians = (Math.Pl / SCALEFACTOR) * i; sines[i] = Math.sin(radians);

22 Interfaces grficas de usjario 2373 i repaint();

} public class SineWave extends JFrame { private SlneDraw sine3 - new SlneDraw(); private JSlider adjustCycles = new JSlider(l/ 30, 5); public SineWave{) { add(sines); adjustCycles.addChangeListener (new ChangeListener() { public void stateChanged(ChangeEvent e) { sines.setCycies( ((JSlider)e.getSource()).getValue());

} )> add(BorderLayout.SOUTH, adjustCycles);

2374 Piensa en Java } public static void main (String [] args) { run(new SineWaveO, 700, 400);

} } ///:-

Todos los campos y matrices se utilizan en el clculo de los puntos que definen la onda senoidal; eyeles 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 JPand. El mtodo setCycles() crea las matrices de acuerdo con el nmero de puntos necesarios y rellena la matriz sines con una serie de valores. Invocando repaint(), setCycles( ) fuerza a que se llame a paintComponcnt(), 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() es invocar la versin de la clase 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.com), para dibujar y pintar pxelcs 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() y drawLine(). Probablemente se encuentre, al hacer sus propios programas

22 Interfaces grficas de usjario 2375 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 propio proceso de dibujo ser bastante simple.

Cuando cre este programa, la mayor paite del tiempo lo invert en intentar que se visualizara la onda sinusoidal. Una vez resuelto eso, pens que sera bastante atractivo poder cambiar dinmicamente el nmero de ciclos. Mis experiencias de programacin intentando hacer cosas como sta en otros lenguajes haca que fuera un poco renuente a realizar esto, pero result 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 disparaba 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(), que proporcionaba un objeto ChangeEvent con el que se poda 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 proceso similar, y adems ver que es un proceso bastante simple, incluso s no ha utilizado un componente concreto anteriormente.

Si el problema es ms complejo existen otras alternativas ms sofisticadas para el tema de dibujo, incluyendo componentes JavaBeans de otras fuentes y la API 2D de Java. Estas soluciones caen fuera del alcance de este libro, pero puede informarse acerca de ellas si el cdigo que est empleando para dibujar resulta demasiado complejo.

2376 Piensa en Java Ejercicio 21: (5) Modifique SineWave.java para transformar SlneDraw 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 formulario 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.

Ejercicio 23: (8) Utilizando SineWave.java corno punto de partida, cree un programa que muestre un cuadrado rotato

22 Interfaces grficas de usjario 2377 rio en la pantalla. Un deslizador debe controlar la velocidad de rotacin y un segundo deslizador controlar 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 juguete, utilizando 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.util.Timer. La velocidad de la animacin debe poder regularse mediante un control javax.swingJSlider.

2378 Piensa en Java 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 represent un dado (slo una clase sin interfaz GUI). Cree cinco dados y lncelos

22 Interfaces grficas de usjario 2379 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 entornos de programacin basados en ventanas.

Para crear un cuadro de dilogo, hay que heredar de JDialog, que es simplemente otro tipo de objeto Window, como JFrame. 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: //: gui/Dialoga.java // Creacin y uso de cuadros de dilogo, import javax.swing.*; import jova.awt. * import j ava.awt.event.*; import static net.roindview.util.SwingConsole.*; class MyDialog extends JDialog { public MyDialog(JFrame parent) { super(parent, "My dialog", true); setLayout(new FlowLayout()); add(new JLabel("Here is my dialog")); JButton ok = new JButton "OK1) ;

2380 Piensa en Java ok.addActionListener(new ActionListener() { public void acLionPerformed(ActionEv ent e) { dispose O; // Cierra el cuadro de dilogo

}); add(ok); setSize(150,125);

} public class Dialogs extends JFrame { private JButton bl = new JButton ("Dialog Box");

22 Interfaces grficas de usjario 2381 privare MyDialog dlg = new MyDialog(nuil); public DialogsO { bi.addActionListener(new ActionListener() { public void actionPerformed(ActionEven t e) { dlg.setVisible(true);

} add(bl);

} public static void main(String[J args) { runnew DialogsO, 125, 75);

} } ///:-

2382 Piensa en Java Una vez creado el control JDialng, 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 formado por una cuadrcula (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 blanco y luego, dependiendo de a quien le corresponda el tumo cambia a una V o a una o. Sin embargo, tambin alternar entre x y o cuando se haga 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. //: gui/TicTacToe.java // Cuadros de dilogo y creacin de sus propios componentes, import j avax.swing.*; import java.awt.*; import j ava.awt.event.*; import static net.mindview.til.SwingConsole.*; public class TicTacToe extends JFrame { privace JTextField rows = new JTextField("3M), cois = new JTextField(n3*) ; private enum State { BLANK, XX, 00 } static class ToeDialog extends JDialog { private State tura - State.XX; // Comenzar con el turno de x ToeDialog(int cellsWide, int cellsHigh) { setTitle("1116 game itself") ; setLayout(new GridLayout(cellsWide, cellsHigh)); for(int i = 0; i < cellsWide * cellsHigh; i++) add(new ToeButton()); setSize(cellsWide * 50, cellsHigh * 50); setDefaultCloseOperaticn(DISPOSE 0N_CL0S8);

22 Interfaces grficas de usjario 2383 } class ToeButton extends JPanel { private State State = State.BLANK; public ToeButton() { addMouseListener(new ML()); } public void paintComponent(Graphics g) { super.paintComponent(g); int xl = 0, yl = 0f x2 = getSize().w idth - 1, y2 = getSize().h eight - 1; dra wRect (xl, yl, x2, y2) ; xl = X2/4; yl = y2/4;
g.

int wide = x2/2, high = y2/2; if(state == State.XX) {


g. g.

high) ; }"
g.

drawLine(xl,

yl,

xl

wide.

yl

drawLine(xl, yl * high, xl + wide, yl);

if(state == State.00) drawOvai high/2) ; (xl, yl, xl + wide/2, yl -i

} cias3 ML extends MouseAdapter { public void mousePressed(MouseE vent e) { if(state == State.BLANK) { state = turn; turn =

2384 Piensa en Java (tum == State.XX ? State.00 : State.XX);

} else state = (state == State.XX ? State.00 : State.XX); repaint();

22 Interfaces grficas de usjario 2385 } class BL implements ActionListener { public void actionPerformed(ActionEvent e) { JDialog d * new ToeDialog( new Integer(rows.getTex t()) , new Integer(cois.getTex t())); d.setVisible(true);

} public TicTacToe() { JPanel p = new JPanelO; p.setLayout(new GridLayout(2, 2)) ; p.add(new JLabel("Rows", JLabel.CENTER)); p.add(rows); p.add(new JLabel("ColumnsM, JLabel-CENTER)); p.add(cois); add (p, Boirdei*Iiyout. NORTH) ; TButton b = new JButton ("go") ; b.addActionL istener(new

2386 Piensa en Java BL()); add(b, BorderLayout .SOUTH);

} public static void main(String[1 args) { run(new TicTacToeO, 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() dibuja el cuadrado alrededor del panel y la indicacin x u o. Este proceso est lleno de tediosos clculos, pero resulta bastante sencillo.

22 Interfaces grficas de usjario 2387 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 quin le corresponde el turno, lo que establece el estado del control ToeButtun. Gracias al mecanismo de la clase interna, el control ToeButton puede acceder a los datos del padre y pasar el tumo. Si el botn ya est mostrando una indicacin xM u o, entonces se invierte esa indicacin. Podemos ver, dentro de los clculos la comodidad que proporciona el uso de la instruccin if-else ternaria, descrita en el Captulo 3, Operadores. Despus de cada cambio de estado, se redibuja el control ToeButton.

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 pxeles de lado.

TieTacToe configura la aplicacin completa creando los campos JTextField (para introducirlos en las filas y columnas de la cuadrcula de botones) y el botn inicio con su escucha ActonListener. Cuando se pulsa el botn, es necesario extraer los datos de JtextField y, como estn en formato Strlng, transformarlos a formato nt utilizando el constructor de Integer que toma un argumento de tipo Strng. 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 permiten abrir y guardar archivos, as que un componente de Java, JfileChooser, encapsula estas operaciones para facilitar su realizacin.

2388 Piensa en Java La siguiente aplicacin ilustra dos formas de cuadros de dilogo JFileChooser, una para abrir un archivo y otra para guardarlo. La mayor parte del cdigo debera resultar familiar al lector, y todas las actividades de inters tienen lugar deno de los escuchas de accin asociados con los dos clics de ratn distintos: //: gui/FileChooserTest.java // Ejemplo de cuadros de dilogo de archivos. import j avax.swing.*; import java.awt.*; import j ava.awt.event.*; import static net.mindview.til.SwingConsole.*; public class FileChooserTest extends JFrame { private JTextField fileName = new JTextFieldO , dir = new JTextFieldO; private JButton open = new JButton ("Open") , save = new JButton("Sa ve"); public FileChooser Test() { JPanel p = new JPanelO; open.addActionListener(new OpenLO ) ; p.add (open) ; save.adoActionList ener(new SaveL()); p.add(3dvc); add(p, BorderLayout.SOUTH); dir.setEditable(false) fileName.setEditable(false); p = new JPanel(); p. setLayout (r.ew GridLayout (2,1)) ; p.add(filsName); p.add(dir);

22 Interfaces grficas de usjario 2389 add(p, BorderLayout.NCRTH);

} class OpenL implements ActionListener { public void actionPerformed(Actio nEvent e) ( jFileChooser c = new JFileChooser(); // Ejemplo de cuadro de dilogo "Open" (abrir): int rVal = c.showOpenDialog(FileChoose rTest.this); if(rVal == JFileChooser.APPROVE OPTION) { fileName.setText (c.getSelectedFile O .getNameO) ; dir.setText (c.getCurrentDirectoryO .toStringO ) ;

} if(rVal == JFileChooser.CANCEL _OPTION) { fileName.setText( "You pressed cancel"); dir.setText("");

2390 Piensa en Java }

} class SaveL implements ActionListener { public void actionPerformed(ActionEvent e) { JFileChooser c = new JFileChooser(); // Ejemplo de cuadro de dilogo "Save" (guardar): int rVal = c.showSaveDialog(Fd1eChooser Test.this); if(rVal == JFileChooser.APPROVE_OPTION) { fiieName. setText (c .getSelectedFile () .getNameO) ; dir. setText (c. getCurrentDirectory () . toString () ) ,

) ifrVal == JFileChooser.CANCEL _0PTI0N) { fiieName.setText(

22 Interfaces grficas de usjario 2391 "You pressed cancel"); dir.setText("");

} public static void main(String[] args) { run(new FileChooserTest0, 250, 150);

} } ///:-

2392 Piensa en Java Observe que hay muchas variantes que podernos aplicar a JFileChooser. incluyendo filtros para seleccionar los nombres de archivo permitidos.

Para un cuadro de dilogo de apertura de archivos (open file), invocamos sho\vOpcnDialog(), y para un cuadro de dilogo para guardar el archivo (save file), invocamos sho\vSaveDia!og( ). listos 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 getSeIectedFile( ) y getCurrentDirectory( ) son dos formas que permiten preguntar por el resultado de la operacin. Si devuelven nuil, quiere decir que el usuario ha cancelado el cuadro de dilogo.

Ejercicio 29: (3) En la documentacin del JDK correspondiente a javai.swing, busque el control JColorChooser.

Escriba un programa con un botn que haga aparecer en forma 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

22 Interfaces grficas de usjario 2393 retormateado de acuerdo con las reglas del lenguaje IITML. Esto quiere decir que podemos aadir muy fcilmente texto formateado a un componente Swing. Por ejemplo: //: gui/HTMLButton.j ava // Adicin de texto HTML en componentes Swing. import j avax.swing.*; import j ava.awt.*; import java.awt.event.*; import static net.mindview.til.SwingConsole; public class HTMLButton extends JFrame { private JButton b = new JButton( "chtmlxbxfcnt size-+2>1' + "<center>HeIlo! <brxi>Presa me nowl"); public HTMLButton() { b.addActionListener(new ActionListener() public void actionPerformed(Actio nEvent e) { add(new JLabel(<html> + "<ixfont size=+4>Kapcwl " )) ; // Forzar una redisposicin para incluir una nueva etiqueta*, validate(); {

; setLayout(new FlowLayout()) ; add(b);

2394 Piensa en Java ) pubiic static void main(String[] args) { run(new HTMLButton( ) , 200, 500);

) ) ///:

Es necesario iniciar el texto con <html>, despus de lo cual se pueden emplear marcadores HTML. Observe que no estamos obligados a incluir los marcadores normales de cierre.

ActionListener aade una nueva etiqueta JLahel al formulario, que tambin contiene texto HTML. Sin embargo, esta etiqueta no se aade durante la construccin, asi que es necesario invocar el mtodo validate( ) 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 JTabbedPane. JMenultem, JToolTip, JRadioButton, y JCheckBox.

22 Interfaces grficas de usjario 2395 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.java) 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 una 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 deslizador 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 ProgressMonitor, que es un cuadro de dilogo emergente con una funcionalidad ms rica: //: gui/Progress.java // Utilizacin de deslizadores, barras de progreso y monitores de progreso. import j avax.swing.77; import j avax.swing.border.*; import j avax.swing.event.*; import java.awt.*; import static net.mindview.util.SwingConsole.*; public class Progress extends JFrame { private JProgressBar pb s new JProgressBar(); Cabria discutir si las capacidades de visualizacin de Swing hacen justicia al sistema operativo.
77

2396 Piensa en Java private ProgressMonitor pm = new ProgressMonitor( this, "Monitoring Progress", "Test", 0, 100); private JSlider sb = new JSlider(JSlider.HORIZONTAL , 0, 100, 60); public Progress() { setLayout(new GridLayout(2,1)); add(pb); pm.setProgress(0); pm.setMillisToPopup(1000); sb.setValue(0); sb.setPalntTicks(true); sb.setMajorTickSpacing(20); sb.setMinorTickSpacing(5); sb.setBorder(new Titled3order("Slide Me")) ; pb.setModel(sb.getModel()); // Modele compartido add(sb); sb.addChangeListener(ne w ChangeListener() { public void stateChanged(ChangeE vent e) ( pm.setProgress(sb. getValue());

22 Interfaces grficas de usjario 2397 }>;

} public static void main(String[] args) { run(new Progresa(), 300, 200);

} } ///:-

La clave para asociar los componentes deslizador y barra 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 algunas situaciones sencillas. El control ProgressMonitor no tiene un modelo, por lo que es obligatorio utilizar el mtodo basado

2398 Piensa en Java 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 JSlider tiene bastantes opciones, como por ejemplo las que lijan 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)ModifiqueProgress.java para que, en lugar de compartir los modelos, utilice un escucha para conec

22 Interfaces grficas de usjario 2399 tar el deslizador y la barra de progreso. Seleccin del aspecto y del estilo

El concepto aspecto y estilo seleccionares permite al programa emular el aspecto y el estilo de diversos sistemas operativos. Podemos incluso cambiar dinmicamente el aspecto y el estilo mientras el programa se est ejecutando. Sin embargo, 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 actualmente, de modo que el programa Java parezca haber sido creado especficamente para dicho sistema (est es casi siempre la mejor eleccin en la mayora 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 basndose 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 estilo actuales del sistema operativo, basta con insertar el siguiente cdigo, normalmente al principio de main(). psro al menos antes de aadir ningn componente: try { UlManager.setLookAndFeel( UlManager.getSystemLcokAndFeelClassNameO); } catch(Exception e) { throw new RuntimeKxception(e);

2400 Piensa en Java }

No hace falta incluir nada en la clusula catch porque el gestor de la interfaz de usuario UlManager tomar como opcin predeterminada 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 catch.

He aqu un programa que acepta un argumento de la lnea de comandos para seleccionar un determinado aspecto y estilo que muestra el aspecto de los distintos componentes con la opcin elegida: //: gui/LookAndFeel.java // Seleccin del aspecto y el estilo de la aplicacin. // {Args: motif} import javax.swing.*; import j ava.awt.*; import static net .mindview.ut.il .SwngConsol e. *; public class LookAndFeel extends JFrame { private StringIJ choices = ''Eeny Meeny Minnie Mickey Moe Larry Curly" . split (" "); private Component[] samples = { new JButton("JButton")f new JTextFieid{"JTextField" ) , new JLabel("JLabel"), new JCheckBox("JCheckBox"), new JRadioButton("Radio"), new JComboBox(choices), new JList(choices) , public LookAndFeel() { super (Look And Feel8n) ;

22 Interfaces grficas de usjario 2401 setLayout(new FlowLayout()); for(Component component : samples) add(component);

} private static void usageErrorO { Sys Dem.out.println( "Usage:LookAndFeel [cross|system|motif]"); System.exit(1);

> public static void main(String[] args) { if(args.length == 0) usageErrorO; if(arg9[0].equals("cross") ) { try { UlManager.setLookAndFeel(UlManager. getCrossPlatformLookAndFeelClassName()); } catch( Except ion e) { e.pr intSta ckTrac e();

2402 Piensa en Java } } else if(args [01 .equals("system")) { try { UlManager.setLookAnd Feel(UlManager. getSystemLookAndFeel ClassName()); } catch( Except ion e) { e.pr intSta ckTrac e();

) } else if(args[0].equals("motif")) { try { UlManager.setLookAndFeel("com.sun.j ava." + "swing.plaf.motif.MotifLookAndFeel"); } catch(Exception e) { e.printStackTr ace( ) ;

} ) else usageErrorO; I f Observe que el aspecto y estilo deben configurarse // anees de crear cualquier

22 Interfaces grficas de usjario 2403 componente.

runtnew LookAndFeel(), 300, 300);

} } ///:-

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 invocando getSystemLookAndFeeIClassName() 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).

2404 Piensa en Java rboles, tablas y portapapeles

Podr encontrar una breve introduccin y diversos ejemplos sobre estos temas en el suplemento en lnea para este captulo (en ingls) disponible en www.MindView.nei. JNLP y Java Web Start

Resulta posible firmar un appief por razones de seguridad. Esto se muestra en el suplemento en lnea de este captulo (en ingls) disponible en www.MindView.net. 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 confusa. El explorador web tiene su propio conjunto de mens y barras de herramientas, que aparecern por encima del applet.78

El protocolo JNLP {Java Network Launch Protocol, protocolo Java de inicio a travs de red) resuelve el problema sin sacrificar las ventajas de los applets. Con una aplicacin JNLP, podemos tratar de descargar e instalar una aplicacin Java autnoma 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.

78

Jercmy Mcyer lia desarrollado esta seccin.

22 Interfaces grficas de usjario 2405 Una aplicacin JNLP 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 paite del sistema clicatc. 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 ejecucin del programa.

JNLP describe un protocolo, no una implementacin, as que hace falta una implementacin para poder usarlo. Java Web Start, o JAWS, es la implementacin de referencia oficial de Sun, disponible de forma gratuita y distf ibuida como parte de Java SE5. 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 un servidor web. deber comprobar que el servidor rccononozca el tipo MIME applicatlon/x-java-jnlp-file. Si est empleando una versin reciente del servidor Tomcat (http://jakarta.apache.org/tomcat) este tipo MIME ya estar configurado. Consulte la gua de usuario de su servidor concreto.

Crear una aplicacin JNLP no es difcil. Lo que hacemos es crear una aplicacin estndar que se archiva en un archivo JAR, y luego proporcionar un archivo de arranque, que es

2406 Piensa en Java XML simple 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.

He aqu una variante FileChooserTest.java utilizando 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. //: gui/jnlp/JnlpFileChooser.java // Apertura de archivos en una mquina local con JNLP. // {Requires: javax.jnlp.FiieOpenService; // Hay que tener javaws.jar en la ruta de clases} // Para crear el archivo jnlpfilechooser.jar, haga esto: // cd .. // cd ... // jar cvf gui/jnip/jnlpfilechooser.jar gui/jnlp/*.class package gui.jnlp; import javax.jnlp.*; import j avax.swing.*; import j ava.awt.*; import j ava.awt.event.*; import j ava.io.*; public class JnlpFileChcoser extends JFrame { private JTextField fileName = new JTextFieldO; private JButton open = new JButton ("Open"), save = new JButton("Save"); private JEditorPane ep - new JEditorPane(); private JScrollPane jsp ^ r.ew JScrollPane(); private FileContents

22 Interfaces grficas de usjario 2407 fileContents; public JnlpFileChooser() { JPanel p new JPanelO; open.addActionListener(new OpenLO); p.add(open); save.addActionListener (new SaveL()); p.add(save); jsp.getviewpcrtO .add(ep); add(j sp, BorderLayout.CENTER); add(p, BorderLayout.SOUTH); fileName.setEditable(false); p = new JPanelO; p.setLayout(new GridLayout(2,1)); p.add(fileName); add(p, BorderLayout.NORTH); ep.setContentType("text"); save.setEnabled(false);

} class OpenL implements ActionListener ( public void actionPerformed(ActionEvent e) { FiieOpenService fs = nuil; try { fs = (FiieOpenService)ServiceManager.lookup( "javax.jnlp.FiieOpenService"); } catch(UnavailableS erviceException use) { throw new RuntimeExceotion(u

2408 Piensa en Java se);

} if(fs != nuil) { try { fileContents = fs.openFiieDialog(". ", new Stringf] {"txt", "*"}); if(fileContents == nuil) return; fileName.setText(fileCo ntents.getNameO); ep.read(fileContents.ge tlnputStream(), nuil); } catch{Excepcin exc) { throw new RuntimeSxception(exc);

} save.setEr.abled(true) ;

22 Interfaces grficas de usjario 2409 }

} class SaveL implements ActionListener { public void actionPerormed(ActionEvent e) { FileSaveServ ice fs = nuil; try { fs = (FileSaveService)ServiceManager.lookup( "j avax.jnlp.FileSaveService"); } catch(UnavailableServiceException use) { throw new RuntimeExeeotion(use) ;

} if(fs = nuil) { try { fileContents = fs.saveFileDia! og(".", new String[]{"txt"}, new ByteArrayInputStrea m( ep.getTexl().get Bytes()), fileContents.getNam e()) ; if(fileContents == nuil) return; fileName.setText (fileContents.getName()) ; } catch(Sxception exc) {

2410 Piensa en Java throw new RuntimeException(exc);

} public static void main(StringLJ args) { JnipPileChooser fe = new JnlpFiieChooser() ; fe.setSize(400, 300); fc.setvisible(true);

22 Interfaces grficas de usjario 2411 } i m-.-

Observe que las clases FileOpenService y FileCloseService se importan del paquete javax.jnlp y que en ninguna parte del cdigo se liacc referencia directa aJ cuadro de dilogo JFilcChooscr. Los dos servicios usados aqu deben solicitarse empleando el mtodo ServiceManager.lookup(), y slo se podr acceder a los recursos del sistema cliente a travs de los objetos devueltos por este mtodo. En este caso, los archivos del sistema de archivos del cliente estn siendo escritos y ledos mediante la interfaz FileContcnt, proporcionada por JNLP. Cualquier intento de acceder a los recursos directamente utilizando, por ejemplo, un objeto File o un objeto FileReader hara que se generara una excepcin SecurityExccption de la misma manera que si se intentaran emplear desde un applet no firmado. Si desea utilizar estas clases y no quedarse restringido a las interfaces de servicios de JNLP, tendr que firmar el archivo JAR.

El comando comentado jar en JnlpFileChooser.java generar el archivo JAR necesario. He aqu un archivo de arranque apropiado para el ejemplo anterior. / / : l gui/jnlp/filechooser.jnlp <?xml version*-"1. 0" encoding="UTF-8"?> cjnlp spec = "1.0+" codebase="file:C:/AA ATIJ4/code/gui/jnlp' href ="f ilechooser.j nlp"> <information> <tit!e>FileChooser demo application</title> <vendor>Mindview Inc.</vendor> <description> Jnlp File chooser

2412 Piensa en Java Application </description> <description kind= short" > Ilustra la apertura, lectura y escritura de un archivo de texto </description> cicon href="mindview.gif"/> <of f 1 me -al lowed/ > </information> <resources> <j2se version="1.3+" href='11rht tp: / / j ava. sun. coin/products/autodl / j 2 se" /> <jar href="jnlpfilechooser.jar" download="eager"/> </resources> <app!ication-desc main-class*"gui.j nlp.JnlpFileChooser"/> </jnlp>

///:**

Puede encontrar este archivo de arranque en el cdigo fuente descargable del libro (disponible en www.MindView.net) guardado 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 si mismos.

22 Interfaces grficas de usjario 2413 El atributo spec del elemento jnlp dice al sistema cliente con qu versin de JNLP puede ejecutarse la aplicacin. El atributo codebase apunta a la URL donde se encuentran el archivo de arranque y los recursos. En este caso, apunta a un directorio de la mquina local, lo que constituye una buena forma de probar la aplicacin. Observe que no tendr que cambiar esta rula, ya que indico el directorio correcto en su mquina, para que el programa se cargue con xito. El atributo href 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 ejecutarla desde la linea de comandos, utilizando atajos, etc.

El marcador resources sirve a un propsito similar que le marcador de applet 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 lazy" que indican a la implementacin de JNLP si se necesita o no descargar el archivo completo antes de poder ejecutar la aplicacin.

F.I atributo application-desc indica a la implementacin JNLP qu clase es la clase ejecutable, o punto de entrada, al archivo JAR.

2414 Piensa en Java Otro til sub-elemento del marcador jnlp es el marcador security, que no se ha mostrado en este ejemplo. He aqu, el aspecto de un marcador security: <security> <all-permissions/> <security/>

Utilice el marcador security cuando la aplicacin est implantada en un archivo JAR firmado. En el ejemplo anterior no era necesario porque es posible acceder a todos los recursos locales a travs de los servicios JNLP.

Hay disponibles unos pocos ms marcadores, cuyos detalles puede consultarlos en la especificacin disponible en http://java.sun.com/products/javawebstart/downloadspec.html.

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): //:! gui/jnlp/filechooser .html <htmi> Follow the instructions in JnlpFileChooser.java to build jnlpfilechooser.jar, then:

22 Interfaces grficas de usjario 2415 <a href="filechooser.jnlp">click here</a> </html>

///:-

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 configurablc.

Aqu slo se cubren dos de los servicios JNLP, aunque existen siete servicios en la versin actual. Cada uno de ellos est diseado para realizar una tarea especfica, como imprimir, o cortar y pegar en el portapapeles. Puede encontrar ms informacin en http;//ja\'a.sun.com. Concurrencia y Swing

Al programar con Swing se emplean hebras. Hemos visto esto al principio del capitulo al estudiar que todo debera ser enviado a la hebra de despacho de sucesos de Swing a travs de SwngUtiIities.nvokeLater(). Sin embargo, el hecho de que no se haya creado

2416 Piensa en Java explcitamente un objeto Thread significa 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 alli, gestionando todos los sucesos Swing extrayndolos uno a uno de la cola de sucesos y ejecutndolos. Recordar que la hebra de despacho de sucesos est presente le ayudar a garantizar que la aplicacin no se vea sometida a interbloqueos o condiciones 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 interfaz grfica de usuario consiste en emplear accidentalmente la hebra de despacho de sucesos para ejecutar una tarea de larga duracin. He aqu un ejemplo simple: //: gui/LongRunningTask.j ava // Un programa mal diseado, import javax.swing.*; import java.awt.*; import j ava.awt.event.*; import java.til.concurrenL.*; imporl static net.mindview.util.SwingConeole.*; public class LongRunningTask extends JFrame ( prvate JButton bl = new JButton("Start Long Running Task"), b2 = new JButton("3nd Long Running Task") ; public LongRunningTask() { bl.addActionListener(new ActionListener() {

22 Interfaces grficas de usjario 2417 public void actionPerformed(ActionS vent eve) { try { TimeUnit.SECONDS.sleep(3); } catchdnterruptedException e) { System.out.println(" Task interrupted"); retum;

) System.out,println{"Task comnleted");

}); b2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { // Interrumpirse a s mismo? Thread.currentThread().interrupt()

} 22 Interfaces grficas de usuario 2418 ;i

}); setLayout (new FlowLayout ()) ; add(bl); add(b2); public static void main (String [] args) { run(new LongRunningTask(), 200, 150); j } ///:-

Cuando se pulsa bl, 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 normalmente 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 tarca de bl y la hebra de despacho de sucesos vuelva a estar disponible. El cdigo 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 objeto ejecutor con la hebra Executor, que pone automticamente las tareas pendientes en cola y las ejecuta de una en una. //: gui/InterruptableLongRunningTask. java // Tareas de larga duracin dentro de hebras. import j avax.swing.*;

} 22 Interfaces grficas de usuario 2419 import j ava.awt.*; import j ava.awt.event.*; import j ava.util.concurrent.*; import static net.mindview.util.SwingConsole.*; class Task implements Runnable { private static int counter = 0; private final int id = counter++; public void run() { System.out.println(this + r started"); try { TimeUnit.SECONDS.sieep(3); } catch(InterruptedException e) { System.out.println(this - " interrupted"); return;

) System.out.println(this + " completed");

> public String toStringO { return "Task " + id; } nubile long id() { return id; }

}? public class InterruptableLongRunningTask extends JFrame { private JButton

} 22 Interfaces grficas de usuario 2420 bl = new JButton("Start Long Running Task"), b2 = new JButton("End Long Running Task"); SxecutorService executor = Executors.newSingleThreadExecut or(); public InterruptableLongRunningTask() { bl.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Task task * new Task(); executor.execute(task); System.out .println (task " added to the queue");

}); b2.addActionlistener(new ActionListener() { public void actionPerfomed(ActionEvent e) { executor.shutdownNow(); // Solucin drstica

}>;

setLayout(new FlowLayout()) ,* add(bl);

} 22 Interfaces grficas de usuario 2421 add(b2);

} public static void main (String [] args) { runinew InterruptableLongRunningTask(), 200, 150);

} } III:-

Esta versin es mejor, pero cuando pulsamos b2, se llama a shutdown.Now( ) sobre el objeto ExccutorService, con lo que se desactiva. Si intentamos aadir ms tareas, se genera una excepcin. Por tanto, pulsar b2 hace que el programa deje de funcionar correctamente. Lo que nos gustara es poder terminar la tarea actual (y cancelar las tareas pendientes) sin detener nada. El mecanismo de Callable/Future de Java SE5 descrito en el Captulo 21, Concurrencia, es justo loque necesitamos. Definiremos una nueva clase denominada TaskManager, que contienen tupias que almacenan el objeto Callable que representa la tarea y el objeto Future devuelto por el objeto Callable. La razn de que sea necesaria la tupia es que nos permite mantener el control de la tarea original, con lo cual podemos tener informacin adicional que no est disponible en el objeto Future. He aqu cmo se hara: //: net/mindview/util/Taskltem.java / / U n objeto Future y el objeto Callable que 1c produce, package net.mindview.util; import java.util.concurrent.*; public class TaskItem<R,C extends Callable<R>> { public final Future<R> future; public final C task; public TaskItem(Future<R> future, C task) { this.future = future; this.task

} 22 Interfaces grficas de usuario 2422 = task;

) } ///:~

En la biblioteca java.util.concurrent, la tarea no est disponible a travs del objeto Future de manera predeterminada, porque la tarca no tendra por qu seguir necesariamente existiendo cuando obtengamos 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.utU para que est disponible como utilidad de propsito general: //: net/mindview/util/TaskManager.java // Gestin y ejecucin de una cola de tareas. package net.mindview.uti1; import j ava.util.concurrent.*; import java.til.*; public class TaskManager<R,C extends Callable<R extends ArrayList<TaskItem<R, C { private BxecutorService exec = Executors.newSingleThreadExecutor(); public void add(C task) { add(new TaskItem<R,C>(exec.submit(task),task));

} 22 Interfaces grficas de usuario 2423 } public List<R> getResultsO { Iterator<TaskItem<R,C items = iterator(); List<R> results new ArrayList<R>() ; while(items.hasNext()) ( TaskItem<R/C> item = items.next 0; if(item.future.isDone()) { try { results.add(item.future.get()); } catch(Exception e) { threw new RuntimeExceotion(e);

} item3.remove();

} return results;

} 22 Interfaces grficas de usuario 2424 } public List<String> purge() { Iterator<TaskItem<R,C>> items = iterator(); LisL<String> results = new ArrayList<Strir.g> (); while(items.hasNext()) ( TaskItem<R,C> item = items .next(); // Dejar las tareas completadas para insertar los resultados: if(!item.future.isDone()) { results.add("Cancelling " + item.task); item.future.cancel(true); // Puede interrumpir items.remove();

} return results;

} } ///:-

} 22 Interfaces grficas de usuario 2425 TaskManager es un contenedor ArrayLlst de elementos Taskliem. 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 tarca, 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: //: gui/InterruptableLongRunningCallable.java // Utilizacin duracin. de de objetos Callable para tareas de larga

import j avax.swi ng. *; import j ava.awt.*; import j ava.awt.event.*; import j ava.util.concurrent.*; import net.mindview.util.*; import static net.mindview.util.SwingConsole.*; class CallableTask extends Task implements Callable<String> { public String call() ( run(); return "Return value of " + this;

} 22 Interfaces grficas de usuario 2426 ) public class InterruptableLongRunningCallable extends JFrame { private JButton bl = new JButton("Start Long Running Task"), b2 new JButton("End Long Running Task"), b3 new JButton("Get results); private TaskManager<String/CaliableTask> manager = new TaskManager<String,CallableTask>(); public InterruptableLongRunningCallable0 { bl.addActionListener(new ActionListener() { public void actionPerformed(ActicnEvent e) { CallableTask task = new CallableTask(}; manager.add{task); System.out.println(task * w added to the queue");

});

} {

b2.addActionListener(new ActionListener() public void actionPerformed(ActionSvent e) { for(String result : manager.purge()) System.out.println(result);

}>;

} b3 .addActionListener (nev; ActionListener ) { public void actionPerformed(ActonEvent e) { // Llamada de ejemplo a un mtodo de una tarea: for(TaskItem<String,CallableTask> tt : manager)

} 22 Interfaces grficas de usuario 2427 tt.task.idO; // No se requiere proyeccin for(String result : manager.getResults()) System.out.println(result);

}>;

setLayout(new FlowLayout()); add(bl); add(b2) ; add(b3);

} public 3tatic void main(String[] args) { run(new InterruptableLongRunningCallable(), 200, 150);

} } m-.-

Como podemos ver, CallableTask hace exactameme lo mismo que Task excepto en que devuelve un resultado (en este caso, un objeto String que identifica la tarea).

} 22 Interfaces grficas de usuario 2428 Se han creado utilidades no Swing (que no forman parte de la distribucin estndar de Java) denominadas SwingWorker (disponibles en el sitio web de Sun) y Foxtrot (disponibles en http://foxtrot.sourceforge.net) para resolver un problema similar, pero en el momento de escribir estas lneas, dichas utilidades no han sido modificadas para aprovechar el mecanismo Callahle/Futnre do Java SF.5.

A menudo, es importante proporcionar al usuario final algn tipo de indicacin visual de que una tarca se est ejecutando, y de su progreso. Normalmente, esto se hace mediante una barra de progreso JProgressBar o un monitor ce progreso ProgressMonitor. Este ejemplo utiliza un monitor de progreso ProgressMonitor: //: gui/MonitoredLongRunningCallable.java // visualizacin del progreso de la tarea con ProgressMonitors. import javax.swing.*,* import j ava.awt.*; import j ava.awt.event.*; import java.util.concurrent.*; import net.mindview.util. import static net.mindview.util.SwingConsole.*;

} 22 Interfaces grficas de usuario 2429 class MonitoredCallable implements Callable<String> { private static int counter - 0; private final int id = counter++; private final ProgressMonitor monitor; private final static int MAX = 8; public MonitoredCallable(ProgressMonitor monitor) { this.monitor = monitor; monitor.setNote(toString{ ) ) ; monitor.setMaximura(MAX - 1); monitor.setMi11isToPopup(500); public String call() { System, out .println (this " started"); try { for(int i = 0; i < MAX; i++) { TimeUnit.MILLISECONDS.sleep(500); if(monitor.isCancelea()) Thread.currentThread().interrupt(); final int progress * i; SwingUtilities.invokeLater( new Runnable() { public void run() { monitor.setProgress(progress);

} 22 Interfaces grficas de usuario 2430 );

} } catch(InterruptedException e) { monitor.close(); System.out.printIn(this + " interrupted"); return "Result: " + this + " interrupted";

> System, out .pr n nt In (this + " completed*'); return "Result: " + this + " conroleted";

} }: public String toStringO { return "Task " -r id; }

public class MonitoredLongRunningCal lable extends JFrame { private JButton bl = new JButton("Start Long Running Task"), b2 = new JButton("End Long Running Task"), b3 = new JButton("Get results"); private TaskManager<String,MonitoredCallable> manager = new TaskManager<String,MonitoredCallable>(); public MonitoredLongRunningCallable() {

} 22 Interfaces grficas de usuario 2431 bl.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { MonitoredCallable task - new MonitoredCaliable( new ProgressMonitor( MonitoredLongRunningCallable.this, "LongRunning Task", ", 0, 0)

); manager.add(task); System, out .println (task " added tc the queue'1);

}>;

b2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { or(String result : manager.purge()) System.out.println(result);

));

b3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for(String result : manager.getResults())

} 22 Interfaces grficas de usuario 2432 System.out.println(result) ;> }>: setLayout(new FlowLayout()) ; add(bl); add(b2); add(b3);

} public static void main (String [] args) { run(new MonitoredLongRunningCallable(), 200, 500);

} ///:-

El constructor MonitoredCallable toma un objeto ProgressMouitor como argumento, y su mtodo call() actualiza ese objeto cada medio segundo. Observe que un objeto MonitoredCallable es una tarea separada y no debe, por tanto, intentar controlar la interfaz de usuario directamente, as que se utiliza SwingUtilities.invokeLater() para enviar los cambios en la informacin de progreso al monitor. El tutorial de Swing elaborado por Sun (disponible en http://java.sun.com) muestra una tcnica alternativa, consistente un utilizar el temporizado Timer de Swing, el cual comprueba el estado de la tarea y actualiza el monitor.

} 22 Interfaces grficas de usuario 2433 Si se pulsa el botn de cancelacin' para el monitor, monitor.isCanceled() devolver true. Aqu, la tarea se limita a invocar interrupt() sobre su propia hebra, lo que ahora hace que entre en la clusula catch donde se termina el monitor con el mtodo closc().

El resto del cdigo es, en la prctica, igual que antes salvo por la creacin del objeto ProgressMonitor como parte del constructor MonitoredLongRunningCallable.

Ejercicio 33: (6) Modifique InterruptableLongRunningCallable.java para que se ejecuten todas las tareas en parale

lo en lugar de secuencialmenle. Hebras visuales

El siguiente ejemplo define una clase .Panel 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 tamao de la cuadrcula de colores y cunto tiempo hay que dormir (con slccp()) entre los sucesivos cambios de color. Jugando con estos valores, podemos descubrir algunas caractersticas interesantes y posiblemente inexplicables en la implementacin del mecanismo muLtihebra en nuestra plataforma: //: gui/ColorBoxes.java // Demostracin visual del mecanismo multihebra. // {Args: 12 50} import j avax.swing.*; import j ava.awt.*; import java.util.concurrent.*; import java.util.*; import static net.mindview.util.SwingConsole.*; class C3ox extends JPanel implements Runnable { private int pause; private static Random rand ^ new RandomO ; private Color color ^ new Color(0); public void

} 22 Interfaces grficas de usuario 2434 painiComponent(Graphics g) { g.setColor(color); Dimensin s = getSizeO;


g.

fillRect(0, 0, s.width, s.height);

} public CBcx(int pause) { this.pause = pause; } public void run() { try { while(!Thread.interrupted()) { color = new Color(rand.nextInt(OxFFFFFF)); repaintO; // Solicitar asincronamente el redibujo TimeUnit.MILLISECONDS.sieep(p ause); } } catchdnterruptedException e) { // Forma aceptable de salir

} 22 Interfaces grficas de usuario 2435 ) public class ColorBoxes exlends JFrame { private int grid = 12; prvate int pause = 50; private static ExecutorService exec = Executors.newCached ThreadPoolO; public ColorBoxes() { setLayout(new Gridlayout(grid, grid)); forint i = 0; i < grid * grid; i++) { C3ox cb = new CBox(pause); add(cb); exec.execute(cb);

) public static void main(String[] aras) { ColorBoxes boxes = new ColorBoxes() ; if(args.length > 0) boxes.grid = new Integer(aras[0]); if(args.length > 1) boxes.pause = new Integer(args[1]); run(boxes, 500, 400);

} 22 Interfaces grficas de usuario 2436 } } ///:-

ColorBoxes configura un objeto GridLayout de tal manera que tenga una serie de celdas grid en cada dimensin. A continuacin aade el nmero apropiado de objetos CBox para rellenar la cuadrcula, pasando el valor pause a cada uno de esos objetos. En main() podemos ver que pause y grid tienen valores predeterminados que pueden modificarse proporcionando los correspondientes argumentos a tTavs de la lnea de comandos.

Todo el trabajo se realiza en CBox. Esta clase hereda de JPanel e implemenia la interfaz Runnable, de tal foma 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.

paintComponent() es bastante simple; slo asigna un color a cColor y rellena el JPanel completo con dicho color.

En run( ). podemos ver el bucle infinito que asigna un nuevo color aleatorio a cColor y luego invoca repaint() para mostrarlo. A continuacin, la hebra pasa a dormir (con sleep( )) durante el intervalo de tiempo especificado en la lnea de comandos.

La llamada a repaint() en run() 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 slo debemos enviar hebras a la cola de sucesos. Sin embargo, estas hebras no estn en realidad modificando el recurso compartido. Cuando llaman a repaint(), 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 problema de gestin de hebras con Swing.

} 22 Interfaces grficas de usuario 2437 Cuando la hebra de despacho de sucesos realiza verdaderamente un redibujo con paint( ), llama primero a paimComponcnt(), luego a paintBorder() y paintChildren(). Si necesitramos sustituir paint() en un componente derivado, tenemos que acordamos de llamar a la versin de la clase base de palnt(), de modo que se sigan realizando las acciones apropiadas.

Precisamente porque este diseo es flexible y el mecanismo de hebras est asociado con cada elemento JFPancl. podemos experimentar creando tantas hebras como queramos (en realidad, hay una restriccin impuesta por el nmero de hebras que la mquina JVM sea capaz de gestionar).

Este programa tambin constituye una interesante prueba comparativa de rendimiento, ya que puede mostrar enormes diferencias entre prestaciones y comportamiento entre una implementacin multihebra de la JVM y otra, as como entre unas plataformas y otras.

Ejercicio 34: (4) Modifique ColorBoxes.java comience distribuyendo puntos (estrellas) por el lienzo,

de

modo

que

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 Java para la creacin de fragmentos de cdigo rcutiliza- ble. I,a unidad de cdigo ms rcutilizable ha sido la clase, ya que comprende una unidad cohesionada de caractersticas (campos) y comportamientos (mtodos) que pueden rcutilizarse bien directamente, mediante composicin, o bien mediante 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 necesitamos. 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

} 22 Interfaces grficas de usuario 2438 la construccin modular'.

La programacin visual" consigui su primer xito (un gran xito) con Visual BASIC (VB) de Microsoft, seguido de un diseo de segunda generacin representado por Delphi de Borland (que fue la principal inspiracin para el diseo de JavaBeans). 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 un botn o un campo de texto. De hecho. la representacin visual coincide a menudo con la forma exacta que el componente tendr cuando el programa se ejecuta. Por tanto, parte del proceso de programacin visual implica arrastrar un componente desde una paleta y depositarlo sobre el formulario. El entorno integrado de desarrollo (IDE. IntegratedDevelopment Environment) Application Builder escribe automticamente el cdigo a medida que realizamos estas operaciones y dicho cdigo har que se cree el componente dentro del programa.

Normalmente, para completar el programa no es suficiente con colocar el componente sobre un formulario. A menudo, es preciso cambiar las caractersticas 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 un objeto es ms que una serie de caractersticas: tambin es un conjunto de comportamientos. En tiempo de diseo, los comportamientos de un componente visual estn parcialmente 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 queremos que ocurra, asociando a ese suceso un cierto cdigo.

La parte crtica es la siguiente: el entorno IDE utiliza el mecanismo de reflexin para interrogar dinmicamente al componente y averiguar qu propiedades de sucesos soporta. Una vez que sabe cules son. puede mostrar las propiedades y permitimos modificarlas (guardando el estado cuando construyamos el programa), y tambin muesfra los sucesos. En general, lo que nosotros hacemos es un doble clic sobre un suceso y el enlomo IDE crea un cuerpo de cdigo y lo asocia con el suceso particular. Lo nico que hace falta en dicho punto es escribir el cdigo que tenga que ejecutarse cuando ocurra el suceso. lodo esto significa que el entorno IDE hace una gran cantidad de trabajo por nosotros. Como

} 22 Interfaces grficas de usuario 2439 resultado, podemos centrarnos en el aspecto del programa y en lo que se supone que el programa debe hacer, delegando en IDE la gestin de todos los detalles de conexin. La razn de que las herramientas de programacin visual hayan tenido tanto xito es que permiten acelerar enormemente el proceso de construccin de una aplicacin, por supuesto, la interfaz de usuario, pero a menudo tambin se acelera la construccin de otras partes de la aplicacin

22 Interfaces grficas de usuario 2440 .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 IDE para descubrir las propiedades y sucesos de cada componente. Para crear un componente 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 diseado especficamente 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 transformar algo en un componente Bean. Lo nico necesario es, de hecho, modificar ligeramente la forma de denominar los mtodos. Es el nombre del mtodo lo que le dice al entorno IDE si se trata de una propiedad, de un suceso o de un mtodo normal.

En la documentacin del JDK, este convenio de denominacin se llama, de manera contusa, patrn de diseo. Resulta bastante desafortunado que esto sea as, ya que los patrones de diseo (consulte Thinking in Patterns en www.MindView.nei) ya son lo suficientemente complejos sin necesidad de que se introduzca confusin adicional. El convenio de denominacin no es un patrn de diseo, y resulta bastante sencillo:

1.

Para una propiedad denominada xxx, normalmente creamos dos mtodos: getXxx() y setXxx(). La primera letra despus de get o set ser puesta automticamente en minscula por las herramientas que examinen los mtodos, con el fin de generar el nombre de la propiedad. El tipo producido por el mtodo get coincide con el tipo del argumento del mtodo set. El nombre de la propiedad y el tipo de los mtodos get y set no estn relacionados.

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 get.

3.

Los mtodos normales del componente Bean no se adaptan al convenio de denominacin anterior, pero son de tipo public.

public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2441


4.

Para ios sucesos, se utiliza la solucin Swing basada en escuchas. Es exactamente el mismo convenio que hemos visto anteriormente: addBounceListeuer(BounceListener) y removeBouncel.istcner(BounccListener) para gestionar un suceso BounceEvent. La mayor parte de las veces, los sucesos y escuchas predefinidos 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 JavaBear. trivial. package frogbean; import j ava.awt.*; import j ava.awt.event.*; class Spots {} public class Frog { prvate int jumps; private Color color; private Spots spots; private boolean jmpr; public int getJumps() { return jumps; } public void setJuraps(int newJumps) { j ump s ~ newJumos;

} public Color getColorO { return color; } public void setColor(Color newColor) { color = newColor; public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2442 } public void setSpots(Spots newSpots) ( spots newSpots,* public boolean isJumperO { return jmpr; } public void setJumper(boolean j) { jmpr = j; } public void addActionListener(ActionListe ner 1) { //...

} public voi removeActionListener (AcnionLisz.ener 1) {

} public voi adKeyListener(KeyListener 1){

} public voi removeKeyListener(KeyListener 1) n ...

public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2443 } // Un mtodo pblico "normal": public void croak() { System.out.println("Ribbet!");

} } ///:-

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 propiedad). Aunque el nombre del idcntificador 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 variables 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 mtodos '4add'* 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 parles ms crticas del esquema JavaBean tiene lugar cuando arrastramos una Bean de una paleta y la colocamos en un formulario. El entorno DF. debe ser capa/, de crear la Bean (lo cual puede hacerse si existe un constructor predeterminado) y luego, sin tener ningn 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.

public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2444 Parte de la solucin resulta evidente a partir de Jo comentado en el Captulo 14. Informacin de tipos: el mecanismo de reflexin 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 sealizacin de objetos y la invocacin remota de mtodos, RMI, que resulta til para la programacin normal). Por tanto, podramos esperar que el creador del entorno IDE usara el mecanismo 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 creacin de otros componentes Bean ms complejos. Esta herramienta es la clase Introspector, y el mtodo ms importante de esta clase es el mtodo esttico getBeannfo(). Basta con pasar una referencia Class a este mtodo y el mtodo se encargar de interrogar a fondo a dicha clase y de devolver un objeto Beanlnfo que se puede diseccionar para determinar las propiedades, los mtodos y los sucesos. Normalmente, nosotros no tenemos que preguntamos nada acerca de esto; lo 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 arrastrar los componentes sobre el formulario, configurar sus propiedades y escribir las rutinas de tratamiento para los suceso sde inters. Sin embargo, la utilizacin de Introspectnr para mostrar informacin acerca de una Bean constituye un ejercicio educativo excelente. He aqu una herramienta que hace precisamente esto: //: gui/BeanDumper.java // Obtencin de la informacin acerca de una Bean. import javax.swing.*; import java.awt.*; import j ava.awt.event.*; import java.beans.*; import java.lang.reflect.*; import static net.mindview.til.SwingConsole.*; public class BeanDumper extends JFrame { private JTextField query = new JTextField(20); private JTextArea results = new JTextArea(); public void print(String s) { results.append(s + "\n"); } public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2445 public void dump(Class<?> bean) { results.setText{""); Beanlnfo bi = nuil; try { bi = Introspector.getBeanlnfo(bean, Object.class); } catch(IntrospectionException e) { print("Couldn't introspect " -f bean.getMame ()) ; return;

} for(PropertyDescriptor d: bi.getPropertyDescriptors()){ Class<?> p = d.getPropertyType() ; if(p == null) continue; print(MProperty typc:\n " + p.getName() + "Property name:\n " + d.getName()); Method readMethod = d.getReadMethod(); if(readMethod 1= null) print("Read method:\n " 4 readMethod); Method writeMethod = d.getWriteMethod(); if(writeMethod 1= null) print("Write method:\n + writeMethod); print ("====================') ;

i px'int { "Public methods : " ) ; for(MethodDescriptor m : bi.getMethodDescriptors()) public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2446 print(m.getMethod().toString( )); print(====================== "); pr i nt(M Svent support:"); for(EventSetDescriptor e: bi.getEventSetDescriptors()){ print("Listener type:\n " i e.getListenerType().getName()); for(Method lm : e.getListenerMethods()) print("Listener method:\n n + lm.gecName()); for(MethodDescriptor lmd : e.getListenerMethodDescrip tors() ) print("Method descriptor:\n n + lmd.getMethod()); Method addListener= e.geLAddListenerMethodO; print("Add Listener Method:\n " + addListener); Method removeListener = e.getRemoveListenerMethod(); print("Remove Listener Method:\n n+ remcveListener); print("====================") ;

} class Dumper implements ActionListener ( public void actionPerformed(ActionEvent e) ( public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2447 String ame = query.getText(); Class<?> c = nuil; try ( c = Class.forName(ame); } catch(ClassNorFoundException ex) { resulte.setText("Could n't find " + ame); re tura;

} dump(c);

) public BeanDumperO { JPanel p * new JPanelO; p.setLayout(new FlowLayout()) ; p.add(new JLabel("Qualified bean ame:")); p.add(query); add(BorderLayout.NORTH, p); add(new JScrollPane(reaults)); Dumper dmpr = new Dumper(); query.addActi onListener(dm public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2448 pr); query.setText ("frcgbean.Fr og"}; // Forzar la evaluacin dmpr.actionPerformed(new ActionEvent(dmpr, 0,

} public static void main(String[] args) { run(new 3eanDumper(), 600, 500);

} ///:-

BcanDumper.dump() se encarga de realizar todo el trabajo. Primero trata de crear un objeto Beanlnfo, y si tiene xito, invoca los mtodos de BeanInfu que generan la public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2449 informacin acerca de las propiedades, mtodos y sucesos, lin lntrospector.getBeanInfo(), podemos ver que hay un segundo argumento que le dice a Introspector dnde detenerse dentro de la jerarqua de herencia. En este ejemplo, se detiene antes de analizar todos los mtodos de Object. ya que no estamos interesados en ellos.

Para las propiedades, getPropertyDescriptors( ) devuelve una matriz de objetos PropertyDescriptor. Para cada objeto PropertyDescriptor, 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 getReadMethod( ) y el mtodo de escritura con get\Vrte.Method(). 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), getMethodDescrlptors() devuelve ima matriz de objetos MethodDescriptors. Para cada uno, podemos obtener el objeto Method asociado e imprimir su nombre.

Para los sucesos, getEventSetI)escriptors() devuelve una matriz de objetos EventSetDescriptor. Cada uno de estos objetos 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 BeanDumpcr visualiza toda esta informacin. public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2450 En el arranque, el programa fuerza la evaluacin de frogbean.Frog. La salida, despus de eliminar los detalles innecesarios, es: Property type: Color Property ame: color Read method: public Color getColorO Write method: public void setColor(Color )Property type: boolean Froperty name: j umper Read method: public boolean isJumper() Write method: public void setJumper(boolean) Property Property public public Froperty Property public public type : int name: jumps Read method: int getJumpsO Write method: void setJumps(int) type: frogbean.Spots name: spots Read method: frogbean.Spots getSpotsO Write method: void setSpots(frogbean.Spots)

Public methods: public void setSpots(frogbean.Spots) public void setColor(Color) public void secJumps(int) public boolean isJumperO public frogbean.Spots getSpotsO public void croak{) public void addActionListener(ActionListener) public void addKeyListener(KeyListener) public Color getColorO public void cctJunper(boolean) public int getJumpsO public void removeActionliistener (ActionListener) public void remcveKeyListener(KeyListener) Event support: Listener type: KeyListener Listener method: keyPressed Listener method: keyReleased Listener method: keyTyped Method descriptor: public abstract void keyPressed(KeyEvent) Method descriptor: public abstract void keyReleased(KeyEvent) Method descriptor: public abstract void keyTyped(KeyEvent) Add Listener Method: public void addKeyListener(KeyListener )Remove Listener Method: public void removeKeyListener(KeyListener) **=== = = = = n =tsss === s public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2451 Listener type: Ac t ion Li s t ene r Lis ten er met hod : act ior .Pe rfo rme d Met hod des cri pto r: public abstract void actionPerformed(ActionEvent ) Add Listener Method: public void addActionListener(ActionLis tener) Remove Listener Method: public void removeActionListener(ActionListener)

lista salida nos revela la mayor parte de la informacin que Introspector ve a medida que genera un objeto Beanlnfo a partir 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 nombres de mtodos que podemos ver aqu (como por ejemplo los de lectura y escritura) son producidos por un objeto Method que puede emplearse para invocar el mtodo asociado sobre el objeto.

public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2452 La lista de los mtodos pblicos incluye los mtodos que no estn asociados con una propiedad o un suceso, como por ejemplo croak( ), asi como los que s estn asociados. Se trata de todos los mtodos que se pueden invocar mediante programa para una Bcan, y el entorno IDE puede facilitamos la tarca de programacin enumerando todos esos mtodos mientras realizamos 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 Bcan. 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 ofrecida por el mecanismo de reflexin). Una Bean ms sofisticada

El ejemplo siguiente es ligeramente ms sofisticado aunque un poco frivolo. 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 muestra cuando se pulsa el botn del ratn. El componente BangBean tambin tiene sus propios mtodos addActionListener() y removeActionListener(), de modo que podemos asociar nuestro propio escucha que se public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2453 disparar cuando el usuario haga clic sobre el componente BangBean. Resulta sencillo identificar en el ejemplo el soporte y propiedades del suceso: //: bangbean/ BangBean. java // Dna Bean grfica, package bangbean; import j avax. swing. * import j ava.awt.* ; import j ava.awt.e vent.*; i mport j ava.io.*; import java.til .*; public class BangBean extends JPanel implements Serializable { private int xm, ym; private int cSize = 20; // Tamao del crculo private String text = "Bang!"; private int fontSize 48; private Color tColor Color.RED; private ActionListener actionListener ;public BangBeanO ( addMouseListener (new ML (} ) ; addMouseMotionListener (new MML (} ) ;

} public int getCircleSize() f return cSize; } public void setCircleSize(int newSize) { cSize = newSize;

public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2454 } public String aetBangText() { return text:; } public void setBangTex-(String newText) { text = newText;

} public int getFontsizeO { return fontSize; } public void setFontSize(int newSize) { fontSize = newSize;

} public Color getTextColor() (return tColor; } public void setTextColor(Color newColor) { tColor = newColor;

} public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.BLACK); g.drawCval(xm - cSize/2, yrn - cSize/2, cSize, cSize); } ' //Se trata de un escucha unidifusion, cue es la // forma ms simple de gestionar ios escuchas: public void addActionListener(ActionListener 1) throws TooManyListenersException { if(actionListener i = null) throw new TooManyListenersException0 ; actionListener = 1; public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2455 } public void removeActionListener(ActionListener 1) actionListener null; {

} class ML extends MouseAdapter { public void mousePressed(MouseSvent e) { Graphics g = getGraphics(); g.setColor(tColor); g.setPont( new Font ( "TimesRoman", Pont.BOIiD, fontSize)); int width = g.getFontMetrics0 .stringWidth(text); g.drawstring(text, (getSize{).width - width) /2, getSizeO .height/2) ; g.dispose0 ; // Invocar el mtodo del escucha: if{actionListener 1= null) actionListener.actionPerformed( new ActionEvent(BangBean.this, ActionEvent.ACTION_PERPORMED, null));

} class MML extends MouseMotionAdapter { public void mouseMoved(MouseEvent e) { xm = e.getXO; yrn = e.getY(); repaint 0 ; public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2456 ) }public Dimensin getPreferredSize() { return new Dimensin(200, 200);

} } m-.-

Lo primero que podemos observar es que BangBean implementa la interfaz Serializable. Esto quiere decir que el entorno IDE puede extraer toda la informacin de BangBean utilizando sealizacin 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 ejecutando, estas propiedades extradas se restauran de modo que podamos obtener exactamente lo que deseamos.

Si examinamos la signatura de addActionListener(), vemos que puede generar una excepcin TooMany- ListenersException. Esto indica que se trata de un escucha de unidifitsin, 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 muldifusin, de modo que muchos escuchas puedan ser notificados por un suceso. Sin embargo, eso nos hara tropezar con cuestiones de programacin multihebra, por lo que volveremos sobre el tema en la siguiente seccin, Sincronizacin en JavaBeans. Mientras tanto, un suceso de unidifsin nos permite resolver momentneamente el problema. public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2457 Cuando hacemos clic con el ratn, el texto aparece en la parte central del componente BangBcan, y si el campo actionListener es distinto de nuil, se invoca su mtodos actionPerformed) creando un nuevo objeto 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: //: bangbean/BangBeanTest.java // {Timeout: 5} Abortar despus de 5 segundos durante las pruebas package bangbean; import j avax.swing.*; import j ava.awt.*; import j ava.awt.event.*; import java.til.*; import static net.mindview.util.SwingConsole.*; public class BangBeanTest extends JFrame { private JTextField txt - new JTextField(20) ; // Durante las pruebas, informar de las acciones: class BBL iraplements ActionListener { private int count = 0; public void actionPerormed(ActionE vent e) { txt.setText("BangBean action "+ count++);

public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2458 } public BangBeanTest() { BangBean bb = new BangBean() ; try { bb.addActionListener(new BBL()); } catch(TooManyListenersBxception e) { txt.setText("Too many listeners");

} add(bb); add(BorderLayout.SOUTH, txt);

} public static void main(String[] args) { run(new BangBeanTest(), 400, 500);

} ) m-.-

public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2459 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 BangBcan dentro del marco JFrame. asociando un escucha ActionListener simple con el componente BangBcan con el fin de imprimir un recuento de sucesos en el campo .rTextField cada vez que se produzca un suceso ActonEvent. Por supuesto, normalmente el IDE creara la mayor parte del cdigo que utilice la Bean.

Cuando ejecute el componente BangBean a travs de BcanDumper o lo incluya dentro de un enlomo de desarrollo preparado para componentes Bean, observar que hay muchas ms propiedades y acciones de las que el cdigo precedente permite 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 Internet, 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

public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2460 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 significativamente en las versiones recientes del JDK). Si esto es un problema, podemos dejar sin sincronizar los mtodos 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 getCircleSize() en el ejemplo siguiente) 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 sincronizar 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 synchronizcd de un mtodo nicamente cuando estemos completamente seguros de que eso va a aumentar la velocidad y podemos eliminar la palabra con seguridad.

2.

Cuando se dispara un suceso multidiiusin 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.

public Spots getSpotsO { return spots; }

22 Interfaces grficas de usuario 2461 El primer punto es bastante sencillo de entender, pero el segundo requiere algo ms de reflexin. En BangBean.java evitamos los problemas de concurrencia ignorando la palabra clave synchronizcd y haciendo que el suceso fuera de unidifsin. lie aqu una versin modificada que trabaja en un entorno multihebra y utiliza el mecanismo de multidifsin para los sucesos: //: gui/BangBear*2.java // Deera escribir sus componentes Beans de esta forma para // poder ejecutarlos en un entorno multihebra. import javax.swing.*; import j ava.awt.*; import j ava.awt.event.*; import java.io.*; import java.til.* import static net.mindview.til.SwingConsole public class BangBean2 extends JPanel implements Serializable { private int xm, ym; private int cSize = 20; // Tamao del circulo private String text = "Bang!"; private int fontSize = 48; private Color tColor =Color.RED; private ArrayList<ActionL3tener> actionListeners = new ArrayList<ActionListener>(); public BangBean2() { aadMouseLisLener(new ML())

public Spots getSpotsO { return spots; }

addMouseMotionListener(new MM()); 2462 Piensa en Java ;} public synchronized int getCircleSize() {return } public synchronized void setCircieSize(int newSize) { cSize = newSize; cSize;

} public synchronized String get3angText() { return text; } public synchronized void setBangText(String newText) { text = newText;

} public synchronized int getfrontSize(){ return fontSize; } public synchronized void setFontSize(int newSize) { fontSize = newSize;

> public synchronized Color getTextColor(){ return tColor;} public synchronized void setTextColor(Color newColor) { tColor newColor;

addMouseMotionListener(new MM()); 2463 Piensa en Java } public void paintComponent(Graphics g) { super.pa intComponent(g); g.setColor(Color.BLACK); g.drawOval(xm - cSize/2, ym - cSize/2, cSize, cSize);

) // ste es un escucha de mutidifusin, que se usa ms //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(1);

) // Observe que esto no est sincronizado: public void notifyListeners() { ActonEvent a = new ActionEvent(BangBean2.this, ActionBvent.ACTION_FERFORMED, nuil); ArrayList<ActionLi.stener> lv = nuil; / / Hacer una copia somera de la lieta oor si alguien // aade un escucha mientras estamos //

addMouseMotionListener(new MM()); 2464 Piensa en Java invocando los escuchas: synchronized(this) { lv = new ArrayList<ActionListener>(actionListeners);

i // Invocar todos los mtodos escucha: for(ActionListener al : lv) al.actionPerformed(a);

} class MI. extends MouseAdapter { public void mouseFressed(MouseEvent e) { Graphics g * getGraphics(); g.setColor(tColor); g.setFont( new Font("TimesRoman"f Font.BOLD, fontSize)); int width = g.getFontMetrics().stringWidth(text); g.drawstring(text, (getSize().width width) /2, getSizeO . height/2) ; g.dispose(); notifyListeners(); }}

class MM extends MouseMotionAdapter { public void mouseMoved(MouseEve nt e) { xm = e.getX(); ym = e.getY(); repaint();

addMouseMotionListener(new MM()); 2465 Piensa en Java }

} public static void main(String[] args) { Bang3ean2 bb2 - new BangBean2(); bb2.addActionListener(new ActionListener() { public void actionPerformed(ActionEve nt e) { System.out .println(''ActionEvent" + e) ;

});

} bb2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println(nBangBean2 action"); )

}> i bb2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("More action");

addMouseMotionListener(new MM()); 2466 Piensa en Java }

}>; JFrame frame = new JFrame(); frame.add(bb2); run(frame, 300, 300);

} } ///:-

Aadir la palabra clave synchronized a los mtodos es un cambio sencillo. Sin embargo, observe en addActionListener() y renioveAetionListcner( ) que los escuchas ActionListener se aaden y eliminan ahora en un contenedor Array List, por lo que podemos tener tantos como queramos.

Podemos ver que el mtodo aotifyListeners() no est sincronizado. Este mtodo puede ser invocado desde ms de una hebra a la vez. Tambin resulta posible invocar addActionListener() o removcActionl.istencr() en mitad de una llamada a notify

addMouseMotionListener(new MM()); 2467 Piensa en Java Listeners( ), lo que constituye un problema, porque este ltimo mtodo recorre el contenedor actionListeners de tipo ArrayList. Para aliviar el problema, el contenedor ArrayList se clona dentro de una clusula synchronized, y lo que se hace es recorrer el clon (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 notifvListeners().

El mtodo paintConiponent() 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 paintConiponent( ) parece funcionar correctamente independientemente de si se sincroniza o no. Sin embargo, las cuestiones que hay que tener en cuenta son las siguientes:

Modifica el mtodo del estado de las variables crticas dentro del objeto? Para descubrir si las variables son criticas, 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 paintConiponenl(), no se realiza ninguna modificacin.
1.

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
2.

addMouseMotionListener(new MM()); 2468 Piensa en Java caso, podemos preguntarnos: Qu es lo peor que puede suceder si se cambia cSize 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 sincronizar paintComponcnt() para evitar el coste adicional asociado a la llamada al mtodo sincronizado.

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 ejemplo, un campo que s que se cambia mediante mtodos sincronizados (cSi/.e) se ha mezclado en la frmula de paintComponent() y podra 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 automticamente en la versin sustituida de la clase derivada.

4.

pant() y pantComponent() 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 necesita 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 capacidades de multidifusin de BangBean2 aadiendo escuchas adicionales. Empaquetado de una Bean

addMouseMotionListener(new MM()); 2469 Piensa en Java Antes de poder incluir componentes JavaBcan en un entorno IDE preparado para ese tipo de componentes, es necesario incluirlo en un contenedor Bcan, que es un archivo JAR que incluye todas las clases Bean, junto con un archivo de manifiesto que dice: Esto es una Bean. Un archivo de manifiesto es simplemente un archivo de texto que se ajusta a un formato concreto. Para el componente BangBcan. el archivo de manifiesto tendra el aspecto siguiente: Manifesr> Versin: 1.0 Nane: bangbean/BangBean.cla ss Java-Bean: True

La primera linca 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 lnea (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 ame:. Si volvemos a examinar BangBean.java, veremos que se encuentra en el paquete bangbean (y por tanto en un subdirectorio denominado 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 paquete, 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

addMouseMotionListener(new MM()); 2470 Piensa en Java 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 preguntamos, Qu sucede con las dems clases que se generaron en el momento de compilar BangBean.java? Todas las clases han terminado incluidas dentro del subdirectorio bangbean, y como puede ver, el ltimo argumento de la lnea de comandos jar anterior es un subdirectorio bangbean. Cuando se da a jar el nombre de un subdirectorio. 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 Bcans). Adems, si invertimos el proceso y desempaquetamos un archivo JAR recin creado, descubriremos qus 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.MF y colocado dentro del subdirectorio META-TNF (meta-informacin). Si abre este archivo de manifiesto, ver tambin que jar ha aadido informacin de firma digital para cada archivo, de la forma: Diaest-Algorithms: SHA MD5 SKA-Digest: pDpEAG9NaeCx8aFtqP:4udSX/00 MD5-Digest: 04NcSlhE3Smnzlp2hj 6qeg-=

En general, no tenemos que preocuparnos por nada de esto, y si realizamos modificaciones, basta con cambiar nuestro archivo de manifiesto original y volver a invocar jar para crear un nuevo archivo JAR para nuestra Bean. Tambin pedemos aadir otros componentes Bean al archivo JAR simplemente aadiendo la informacin correspondiente al manifiesto.

addMouseMotionListener(new MM()); 2471 Piensa en Java Un aspecto que hay que resaltar es que normalmente conviene colocar cada Bean en su propio subdircctorio, ya que en el momento de crear un archivo JAR le entregamos a la utilidad jar el nombre de un subdircctorio y esta utilidad se encarga de colocar todo lo que esc subdircctorio 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 entorno IDE 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://javci. siin.com/beans). Podemos insertar una Bean dentro de Bean Builder simplemente copiando el archivo JAR en el subdirector io correcto.

Ejercicio 36: (4)Aada Frog.class al archivo de manifiesto de esta seccin y ejecute jar 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 construccin de programas con Beans y aada el archivo JAR al entorno, para poder probar los dos componentes Bean.

addMouseMotionListener(new MM()); 2472 Piensa en Java Ejercicio 37: (5) Cree su propia JavaBean denominada Valve que contenga dos propiedades: un valor de tipo boolean

denominado on y un valor int denominado level. Cree un archivo de manifiesto, utilice jar para empaquetar 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 descrito aqu. La arquitectura JavaBeans proporciona un punto de entrada muy simple para poder aprender los fundamentos, pero tambin permite 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 htp://ja va. sun. cotn/beans.

Un aspecto en el que se puede aadir sofisticacin es el relativo a las propiedades. Los ejemplos que hemos visto hasta ahora mostraban nicamente 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 propiedades indexadas para que el entorno TDF. pueda responder adecuadamente.

addMouseMotionListener(new MM()); 2473 Piensa en Java Las propiedades puedan estar acopladas, lo que significa que enviarn notificaciones a otros objetos mediante un suceso PropertyChangeEvent. Los otros objetos pueden entonces decidir modificarse a s mismos basndose en el cambio sufrido por la Bean.

Las propiedades pueden estar restringidas, lo que significa que otros objetos pueden vetar una cierta modificacin de la propiedad si sta resulta inaceptable. Los restantes objetos reciben una notificacin utilizando un suceso PropertyChangeEvent. y pueden generar una excepcin PropertyVetoException para impedir que ese cambio tenga lugar y para restaurar los antiguos valores.

Tambin se puede modificar 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 propiedades normal se emplear para todos los restantes componentes Bean, pero cuando se seleccione nuestra Bean se invocar automticamente la hoja personalizada.

addMouseMotionListener(new MM()); 2474 Piensa en Java


2.

Podemos crcar un editor personalizado para una propiedad concreta, de modo que se utilice la hoja de propiedades normal, pero que cuando se edite la propiedad especial, se invoque automticamente el editor especificado.

3.

Podemos proporcionar una clase Heanlnfo 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 distinguir entre caractersticas bsicas y otras ms complicadas.

Ms informacin sobre componentes Bean

Hay disponibles varios libros acerca de JavaBeans: por ejemplo, JavaBeans de Elliottc Rusty Harold (IDG, 1998). Alternativas a Swing

addMouseMotionListener(new MM()); 2475 Piensa en Java Aunque la biblioteca Swing es la GUI recomendada por Sun, no es en modo alguno la nica forma de crear interfaces grficas de usuario. Dos alternativas importantes son Macromedia Flash, que usa el sistema de programacin Flex. para interfaces GUI del lado del cliente a travs de la Web, y la biblioteca de cdigo abierto SWT {Standard Widget Toolkit) de Eclipse para aplicaciones de escritorio.

Por qu merecera la pena considerar otias alternativas? Para los clientes web, podemos sostener con cierta rotundidad que los applets 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 v/eb basada en applets, ni siquiera Sun usa applets en todas partes. He aqu un ejemplo: http://java.sun.com/developer/onlineTraining/new2java/javamap/intro.html

Un mapa interactivo de las caractersticas de Java en el sitio de Sun parecera un candidato bastante apropiado para construir un applet Java, a pesar de lo cual han construido este papel interactivo en Flash. Esto parece ser un reconocimiento tcito de que las applets no han sido precisamente un xito. Adems, el reproductor Flash Player est instalado en ms del 98 por ciento de las plataformas 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 applets, debemos seguir convenciendo al cliente de que descargue el entorno JRli, mientras que Flash Player es de pequeo tamao y se descarga muy rpidamente.

addMouseMotionListener(new MM()); 2476 Piensa en Java 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 trabajo 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 aplicaciones 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 pane 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.79

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 controles de widget (componentes), y se usan scripts 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 compilador 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.

79

Scan Neville lia creado una paite fundamental del material de esta seccin.

addMouseMotionListener(new MM()); 2477 Piensa en Java Observe que existe una alternativa de cdigo abierto a Flex disponible en http://openlaszlo.org\ esta alternativa tiene una estructura similar a 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): //:1 gui/flex/helloflexl.mxml <?xmi versin1.0" encoding="utf-8"?> <mx:App1ication xmlns :mx= "http: / /www.macromedia.com/2003 /mxml" backgroundColor= <mx:Label id^"output text"Hello, Flex! /> </mx:Application>

///:-

Los archivos MXML son documentos XML; por lo que comienzan con una directiva XML de versin/codificacin. El elemento MXML ms externo es el elemento

addMouseMotionListener(new MM()); 2478 Piensa en Java Application, que es el contenedor visual y lgico de mayor nivel para una interfaz de usuario Flex. Podemos declarar marcadores que presenten controles visuales, como por ejemplo la etiqueta Label del ejemplo anterior, dentro del elemento Application. Los controles se incluyen siempre dentro de un contenedor, y los contenedores encapsulan gestores de disposicin, entre otros mecanismos para poder gestionar la disposicin de las controles incluidos en ellos. En el caso ms simple, como en el ejemplo anterior. Application acta como el contenedor. El gestor de dispositivo predeterminado de Application se limita a colocar los controles vcrticalmente 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 cierto comportamiento. Aqu, se utiliza el control MXML Script para incluir cdigo ActionScript directamente dentro del archivo MXML: //:! gui/flex/helloflex2.mxml <?xml versin-"!.0" encoding="utf-8"?> <mx: Appl i catin xmlns:mx="http://www.mac romedia.com/2003/mxml" backgroundColor= <mx:Script> <! [CHATA [ unction updateOutput() { output.text = "Helio! " + input.text;

} 13 > </mx:Script> <mx:TextInput id=nninput" width=,,200" change="updateOutput()

addMouseMotionListener(new MM()); 2479 Piensa en Java n /> <mx:Labsl id-noutputw text-"HelioI" /> </mx:Application>

///:-

Un control Texnput acepta la entrada de usuario y un control Label muestra los datos a medida que se los escribe. Observe que el atributo id de cada control est accesible dentro del script en forma de nombre de variable, por lo que el script puede hacer referencias a instancias de los marcadores MXML. En el campo Textlnput, podemos ver que el atributo 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 Miw.macromedia.com/sqftware/flex/triaI.80 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.config situado en el directorio bin de la instalacin de Flex. 80 Observe que es preciso descargar Flex y no FlexBuilder. Esta ltima es una herramienta de diseflo IDE.

addMouseMotionListener(new MM()); 2480 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 .nixml se compilen en tiempo de ejecucin cada vez que un explorador solicite la URL del documento MXML.

2.

Podemos compilar el archivo MXML utilizando el compilador de la lnea 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 ser\>let de actualizarse con la informacin de configuracin de Flex, como por ejemplo los mapeos de servlet que se aaden al descriptor web.xmJ, 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 explorador. Flex compilar la aplicacin cuando se reciba la primera solicitud, de forma similar a la que sucede en el modelo JSPf y en lo sucesivo suministrar el cdigo SWF compilado y almacenado en cach dentro de un envoltorio HTML.

addMouseMotionListener(new MM()); 2481 Piensa en Java 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 bin 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 linea de comandos flexlib, 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: mxsilc.exe helloflexl.mxn\l mxmlc.exe hellof lex2 .mxrr:l

Esto genera un archivo helloflex2.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 hellof!ex2.swf, veremos la siguiente interfaz de usuario ejecutndose en Flash Player: This was not too hard to do.. Helio, This was not too hard to do...

addMouseMotionListener(new MM()); 2482 Piensa en Java En aplicaciones ms complejas, podemos separar el cdigo MXML y ActionScript haciendo referencia a funciones en archivos ActionScript externos. Desde MXML, se utiliza la siguiente sintaxis para el control Script:

<mx:Script source-"MyExterna]Script.as" />

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 rcfcrcnciadas, despus de lo cual compila y monta el cdigo ActionScript para crear un archivo SWF.

addMouseMotionListener(new MM()); 2483 Piensa en Java 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 controles, se declaran tpicamente utilizando MXML, mientras que la lgica asociada, como por ejemplo las rutinas de tratamiento 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 documento MXML al que luego podamos hacer referencia mediante un marcador en otro documento MXML. El sitio web de Macromedia contiene ms informacin acerca de cmo hacer esto

22 Interfaces grficas de usuario 2484 .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, recuadros 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: //:! gui/flex/songs.mxml <?xml version="l.0" encoding="utf-8"?> <mx:Application xmlns :mx="http: //www.macromedia.com/20Q3/mxml" backgroundColor=#B9CAD2" pageTitIe="Flex Song Manager initialize="gelSongs () "> <mx:Script source="songScript.as" />

22 Interfaces grficas de usuario 2485 <mx:Style source="songStyle3.css,,,,/> <mx:Panel id="songListP3nel" titleStyleDeclaration="header Text" title="Flex MP3 Library"> <mx: HBox verticalAlign= "botcom" > <mx:DataGrid id=nsongGrid" cellPress-"3elect$ong{event)" rowCount="8"> <mx:columns> <rcx:Array> <mx: DataGri dColumn columnNanie= "ame" headerText"Song ame" width=''12Q" /> <tnx:DataGridColumn columnName="artistM headerText="Arlist width="18C" /> <mx: DaraGridColumn columnName="lbum" headerText="Album" widrhs=,'160o /> </mx-.Array> </mx:columns> </mx:DataGrid> <mx:VBox> <mx:HBox height="100" > <mx:Image id=albunlmage" sources"" height="80" width="100" mouseOverEfect="resizeBig" mouseOntEffect="resizeSmall" /> <mx:TextArea id="songInfo" styleName="boidText" height="100%" width="120" vScrollPolicy="off" borderStyle="none" /> </mx:HBox> <tnx:MediaPlayback id="songPlayer" cor.tent Path="" mediaType="M?3" height="70" width="230" controllerPolicy= " on" autoPlay="false" visible="false" /> </mx:VBcx> </mx:HBox> <mx:ControlBar horizontalAlign="right"> <mx:Button id="refreshSongsButton" label="Reresh Scngs" width=H100" coolTip="Refresh Song

22 Interfaces grficas de usuario 2486 List" elickr songService.getSongs{)n / > </mx:ControlBar> </mx:Panel> <mx:Effect> <mx:Resize name="resizeBig" heightTo'l100" duration="500"/> <nuc:Resize name=MresizeSmalIH heightTo-"80" duration,500,/> </mx:Effect> <mx:RemoteObjecc id="songService" source=ngui.flex.SongSe rvice" result="onSongs(event.r esult)" fault="alert(event.faulc.faultstring, 'Error')n> <mx:method name="getSongs"/> </mx:RemoteObj ect> </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 marcadores MXML pueden hacer referencia a esa cuadrcula de datos mediante programa empleando songGrid como nombre de variable. La cuadrcula de datos DataGrid expone muchas ms propiedades que las que se muestran aqu; puede encontrar la API completa para los controles contenedores MXML en la direccin http://Uvedocs.macromedki.com/jlex/l5 /asdocs_en/\ndex. html.

22 Interfaces grficas de usuario 2487 El control DataGrid est seguido de un control VBox que contiene una imagen Image para mostrar la cartula 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 flujo de datos correspondiente, ios archivos pasan a formar parte del archivo SWF compilado y se suministran junto con la interfaz de usuario, en lugar de descargarse bajo demanda en tiempo de ejecucin.

El reproductor Flash Player contiene codees integrados para reproducir y descargar audio y vdeo en una diversidad de formatos. 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 (scaable 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 transformaciones altamente expresivas en tiempo de ejecucin. Los efectos Flex proporcionan una pequea muestra de este upo de animaciones. Los efectos son transformaciones que pueden aplicarse a los controles y contenedores utilizando sintaxis MXML.

El marcador Effect mostrado en el cdigo MXML produce dos resultados: el primer marcador anidado hace crecer dinmicamente una imagen cuando se desplaza el ratn sobre l, mientras que el segundo contrae dinmicamente dicha imagen cuando el ratn se aleja. Estos efectos se aplican a los sucesos de ratn disponibles en el control Image para albumimage.

22 Interfaces grficas de usuario 2488 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 verdaderamente 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 SheetSy hojas de estilo en cascada). Si asociamos un archivo CSS a un archivo MXML, los controles Flex se adaptarn a esos estilos. Para este ejemplo. songStyles.css contiene la siguiente declaracin CSS: //:! gui/flex/songStyles.c ss .headerTexr { font-family: Arial, "_sans"; font-size: 16; font-weight: bold; .boIdText { font-family: Arial, "sans"; font-size: 11; font-weight: bold;

///:-

22 Interfaces grficas de usuario 2489 Este archivo se importa y se emplea en la aplicacin de la biblioteca de canciones a travs del marcador Style en el archivo 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 songlnfo 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 controles con numerosos sucesos que cubren todos los aspectos de movimiento del ratn y de la utilizacin del teclado.

El atributo click de un botn Button, por ejemplo, representa uno de los sucesos disponibles en dicho control. El valor asignado a click puede ser una funcin o un pequeo script integrado. En el archivo MXML, por ejemplo, el control ControIBar 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(). En este ejemplo, el suceso click del control Button hace referencia al objeto RemoteOhject que se corresponde con el mtodo Java. Conexin con Java

El marcador RemoteObject situado al final del archivo MXML establece la conexin con la clase Java externa gui.flex.SongService. El cliente Flex utilizar el mtodo getSongs() 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 como 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(), que hay que invocar cuando se vuelva del mtodo Java. El marcador method anidado declara el mtodo gctSongs( ), que hacc que ese mtodo Java est accesible para el resto de la

22 Interfaces grficas de usuario 2490 aplicacin Flex.

Todas las invocaciones de servicios en Flex vuelven asincronamente 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 produzca un error.

Ahora podemos invocar el mtodo getSongs() desde Flash utilizando ActionScript: songService.getSongs();

Debido a la configuracin de MXML. esto har que se invoque getSongs() en la clase SongService: //: gui/flex/SongService.java package gui.flex; import java.util.*; public class SongService { private List<Song> songs = new ArrayList<Song>(}; public SongService() { filiTestData{); } public List<Song> getSongs () { retum songs; } public void addSong(Song song) { songs.add(song); } public void removeSong(Song song) { songs.remove(song); ) private void filiTestData() { addSong(new Song("Chocolate", "Snow Patrol", "Final Straw", "sp-final-straw.jpg", "chocolate.mp3")); addSong(new Song ( Concerto No. 2 in E", "Hilary Hahn", "Bach: Violin Concertos", "hahn.jpgu,

22 Interfaces grficas de usuario 2491 "bachviolin2.mp3")); addSong(new Song("'Round Midnight", "Wes Montgomery", "The Artistry of Wes Montgomery", "wesmontgomery. jpg", roundmidnight . mo 3")) ;

} } m-.-

Cada objeto Song es simplemente un contenedor de datos: //: gui/flex/Song.java package gui.flex; public class Song implements java.io.Serializable { private String name; private String artist; private String album; private String albumlmageUrl; private String songMediaUrl; public Song() {} public Song(String name, String artist, String album, String albumlmageUrl, String songMediaUrl) { this.name = name; this.artist = artist; this.album = album; this.albumlmageUrl = albumlmageUrl; this.songMediaUrl = songMediaUrl;

} public void setAlbum(String album) { this.album = album;} public String geLAlbumO { return album; } public void setAlbumlmageUrl(String albumlmageUrl)

22 Interfaces grficas de usuario 2492 { this.albumlmageUrl = albumlmageUrl;

} public String getAlbumlmageUrl() { return albumlmageUrl;} public void setArtist(String artist) { this.artist artist;

} public String getArtistO { return artist; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setSongMediaUrl(String songMediaUrl) ( this.songMediaUrl = songMediaUrl;

) public String gelSongMediaUrl()

{return songMediaUrl;

} /A^-

22 Interfaces grficas de usuario 2493 Ciiando se inicializa la aplicacin o se pulsa un botn refreshSongsButton, se invoca getSongs( ) y, al volver del mtodo, se llama al mtodo onSongs(event.rcsult) de ActionScript para rellenar la cuadrcula songGrid.

He aqu el listado de cdigo ActionScript, que est incluido dentro del control Script del archivo MXML: //: gui/flex/songScript.as function getSor.gsO { songService.getSonqs();

} function selectSong(event) {

22 Interfaces grficas de usuario 2494 var song = songGrid.getrtemAt(event.itemlndex); showSonglnfo(song);) function showSongInfo{song) { songlnfo.text = song.ame + newline; songlnfo.text += song.artist + newline; songlnfo.text += song.lbum + newline; albumlmage.source = song.albumlmageUrl; songPlayer.contentPath = song.songMediaUrl; songPlayer.visible = true;

} function onSongs(songs) { songGrid.dataProvider - songs;

} ///:-

Para gestionar la seleccin de celdas de la cuadrcula de datos DataGrid, aadimos el atributo de sucesos cePress a la declaracin DataGrid en el archivo MXML: cellPress="selectSong(event)M

22 Interfaces grficas de usuario 2495 Cuando el usuario hace clic sobre una cancin en la cuadricula de datos DataGrid, se invoca selectSong( ) en el cdigo ActionScript anterior. Modelos de datos y acoplamiento de datos

Los controles pueden invocar directamente servicios, y las retrollamadas 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 actualiza 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 automticamente que hace referencia a los mismos sin necesidad de intervencin del programador. La infraestructura de Flex responde correctamente a los sucesos de cambio de los datos y actualiza todos los controles que estn acoplados a ellos.

He aqu un ejemplo simple de sintaxis de acoplamiento de datos: <mx:Slder id="mySlidern/> <mx:Text text* {mySlider. valu} "/>

22 Interfaces grficas de usuario 2496 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 sofisticados. Estos controles tienen una propiedad dataprovider para facilitar el acoplamiento a colecciones de datos. La funcin ActionScript onSongs( ) muestra cmo est acoplado el mtodo SongServce.getSongs( ) al proveedor de datos dataprovider del control Flex DataGrid. Tal como se declara en el marcador RemoteObject del archivo MXML, esta funcin 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 Objects) de objetos de transferencia de datos 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 controles. 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 encontrar 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 visuales de los datos, facilitando el uso del patrn Modelo-Vista-Conlrolador (MVC). En aplicaciones ms sofisticadas y de mayor envergadura, la complejidad inicial provocada por la insercin de un modelo constituye una desventaja si la comparamos con el valor que nos proporciona una aplicacin MVC limpiamente desacoplada.

22 Interfaces grficas de usuario 2497 Adems de acceder a objetos Java, Flex tambin puede acceder a servicios web basados en SOAP y a servicios HTTP utilizando los controles WebScrvice y HttpSemce, respectivamente. El acceso a todos los servicios est sujeto a restricciones 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-conig.xnil mediante el indicador -lexlib. En mi instalacin. el comando que hay que utilizar es el siguiente, pero el lector tendr que modificarlo de acuerdo con su propia configuracin (el comando es una nica lnea, que se presenta en varias lneas debido a la limitacin de la anchura del libro): //:1 gui/flex/build-command.txt mxmlc -flexlib C:/nProgram Files"/Macroraedia/Flex/jrun4/servers/default/flex/WEB-INF/flex songs.mxml

///:-

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 contenedor cuando ejecute la aplicacin.

22 Interfaces grficas de usuario 2498 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 computadora despus de instalar Flex, o a travs de la lnea de comandos: jrun -atare default

Puede verificar que el servidor ha arrancado adecuadamente abriendo http://localhost:8700/samples en un explorador web y visualizando los diversos ejemplos (sta es tambin 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, inserte los archivos fuente del ejemplo de las canciones, la hoja de CSS, etc., en el directorio jrun4/servers/default/llex y acceda a ellos desde un explorador abriendo http://localhost:870/jlex/songs.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 SongScrvicc.java deben colocarse ou el directorio WEBINF/classcs. 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 WEBINF/Hb. Debe estar en un directorio que se ajuste a su estructura de paquetes Java. Si est usando, los archivos se colocarn en jrun4/servers/defauIt/flex/WEB-INF/classes/gui/flex/ Song.class y

22 Interfaces grficas de usuario 2499 jrun4/servers/default/flex/WEB-lNF/elasses/gul/flex/SongService.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/flex/WEB-INF/flex/flex-config.xinl. Localice la entrada <remote-ol)jects> en dicho archivo, y examine la seccin <whitelfst> incluida en ella y vea la siguiente nota:

</For security, the whitelist is locked down by default. Uncommenl the source element below lo enable access lo all classes during development. We strongly recommend not allowing access to all source files in production, since this exposes Java and Flex system classes. <source> *</source>

>

Elimine el comentario de esa entrada <source> para permitir el acceso, de modo que quede <souree>*</source>. El significado de sta y otras entradas se describe en los documentos de configuracin de Flex.

22 Interfaces grficas de usuario 2500 Ejercicio 39: (4) La descarga de cdigo para este libro no incluye los archivos JVP3 o JPG mostrados en

SongService.java. Localice algunos archivos MP3 y JPG, modifique SongService.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 lodos los componentes de la interfaz de usuario pxel por pxel, con el fin de proporcionar todos los componentes deseados independientemente de si el sistema operativo 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 equivalente. Adems, SWT tiende a ser un modelo de programacin menos complejo que Swing, lo cual puede resultar deseable en un gran nmero de aplicaciones.81

Puesto que SWT utiliza el sistema operativo nativo para realizar la mayor parte posible del trabajo, puede aprovechar automticamente algunas de las caractersticas del sistema operativo que pueden no estar disponibles con Swing; por ejemplo, Windows tiene mecanismo de representacin subpxeP que hace que las fuentes de caracteres parezcan ms ntidas en las pantallas LCD.

Chris Grindstaff result de mucha ayuda a la hora de traducir los ejemplos a SWT y de proporcionar informacin acerca de SWT.
81

22 Interfaces grficas de usuario 2501 Resulta incluso posible crear applcts 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.edipse.org. Tambin hay diversos libros de programacin con SWT, y posiblemente 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/downloacis/y seleccione uno de los sitios espejo. Siga los vnculos hasta localizar la versin Eclipse actual y localice un archivo comprimido con un nombre con swt e incluya el nombre de su plataforma (por ejemplo, win32*). Dentro de este archivo encontrar swt.jar. La forma ms fcil de instalar el archivo swtjar consiste en colocarlo en el directorio jrc/lib/ext (de esta forma, 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 plataforma. 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 PATO, pero puede ejecutar object/ShowProperties .java para descubrir el valor real de java.library.path). Una vez que haya hecho esto, debera poder compila* 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.

22 Interfaces grficas de usuario 2502 Una tcnica alternativa 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 worUT: //: swt/HelloSWT.java // {Requires: org.eclipse.swt.widgets.Display; You must // install the SWT library from http://www.eclipse.org ) iraport org.eclipse.swt.widgets.*;

2503 Piensa en Java public class HelloSWT {public static void main(String [] args) { Display dispiay = new DisplayO; Shell shell = new Shell(display); shell.setText('Hi there, SWT!")? // Barra de ttulo shell.open{); while(!shel1.isDisposed()) i (Idisplay.readA ndDispatch()) display.sleepO ; display.disnose 0 ;

} } ///:-

Si descarga el cdigo fuente de este libro, descubrir que la directiva de comentario Requires termina siendo incluida en el archivo build.xml de Ant como un pre-rcquisito 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; while(3shellsDi sposed())

2504 Piensa en Java forma parte de un Puente entre el sistema 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 ttulo 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 parle superior del bucle, miramos si la shell ha sido eliminada; observe que esto nos proporciona la opcin de insertar 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 preocupamos de enviar tareas a la hebra de interfaz de usuario, a diferencia de lo 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 necesitando enviar los cambios de la misma forma 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). while(3shellsDi sposed())

2505 Piensa en Java La actividad de la hebra niain( ) en este punto consiste en llamar a readAndDispatch() para el objeto Display (esto significa 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 inmediatamente. Sin embargo, si no hay nada pendiente, invocamos el mtodo sleep() del objeto Display para esperar durante un perodo 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 operativo 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: //: swt/ShellsAreMai nWindows.java import org.eclipse.swt. widgets.*; public class ShellsAreMainWindows { static Shell[] shells = new Shell[10] ; public static void main{String [] args) { Display display = new DisplayO ; torint i = 0; i < shells.length; i++) { shells[i] = new Shell(display); shells[i].setText("Shell #" + i); shells[i].open{); while(3shellsDi sposed())

2506 Piensa en Java }if (Idisplay.readAndDispatchO ) display.sleep(); display.disooseO ; static boolean shellsDisposed() { for(ir.t i = 0; i < shells .length; it-f) if(shells[i].isDisposed()) return true; return false;

} } ///.-

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 Systcm.gctPropertics() y lo aade a la shell: //: swt/DispiayPro perties.java import org.eelipse.sw t.*; import org.eclipse.sw while(3shellsDi sposed())

2507 Piensa en Java t.widgets.*; import org.eelipse.sw t.layout.*; import j ava.io.*; public class DisplayProperties { public static void main(String [] args) { Display display = new Display(); Shell shell - new Shell(display); shell.setText("Display Properties"); shell.setLayout(new FillLayout()); Text text - new Text(shell, SWT.WRAP | SWT.V_SCROLL); StringWriter props = new StringWriter(); Systerr.. get Properties () .list (new PrintWriter (props)) ; text.setText(props.toString() ) ; shell .openO ; while(!shell.isDisposed()) if(! display.readAn dDispatch()) display.sleep( ); display.dispos e 0;

} } m-.~

while(3shellsDi sposed())

2508 Piensa en Java 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 shell es el primer argumento. 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 automticos. 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 difciles o imposibles de cambiar, excepto a travs del constructor. Compruebe siempre la documentacin del constructor del widget para ver qu indicadores acepta. Observe que algunos constructores requieren un argumento indicador, aun cuando la documentacin 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 objet oShell a partir del objeto Display. creamos un bucle rcadAndDispatch(), 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 eliminar el cdigo duplicado, como hicimos con nctmindview.util.SwingConsole.

Tendremos que obligar a cada aplicacin a adaptarse a una interfaz: //: swt/util/SWTApplication.java package swt.til; while(3shellsDi sposed())

2509 Piensa en Java import org.eclipse.swt.widgets.*; public interface SWTAppcatin { void createContents{Composite parent);

} ///:-

A la aplicacin se le entrega un objeto Composite (Shell es una subclase) y la aplicacin debe usar este objeto para crear todos sus contenidos dentro de ereateContents(). SWTConsole.run( ) invoca createContents( ) en el punto apropiado, establece el tamao de la shell de acuerdo con lo que el usuario le haya pasado a run(), abre la shell y luego ejecuta el bucle de sucesos, eliminando finalmente la shell al salir del programa: //: swt/til/SWTConsole.java package swt.til; import org.eclipse.swt.widgets.*; public class SWTConsole ( public static void run(SWTApplication swtApp, int width, int height) { Display display = new Display(); Shell shell = new Shell(display); shell .setText (swtApp.getClass () .getSimpleName ()) ; swtApp.createContents(shell); shell .setSize(width, height:) ; shell.open(); while(!shell. isDisposedO ) { if(! display.readAndDispaten()) display.sleep();

while(3shellsDi sposed())

2510 Piensa en Java } display.dispose();

} } m-.-

Esto establece tambin la barra de ttulo, asignndole el nombre de la clase SWTApplication, y establece la anchura y altura de la Shell mediante los valores width y height.

Podemos crear una variante de DisplayProperties.java que muestre el entorno de la mquina, usando SWTConsole: //: swt/DisplayEnvironment.java import swt.til.*; import org.eclipse.swt; import org.eclipse.swt.widgets.*; imporn org.eclipse.swt.layout.*; import java.til.*; public class DisplayEnvironmenc implements SWTApplication { public void createContents(Composite parent) { parent.setLayout(new FillLayout()); Text text = new Text(parent, SWT.WRAP SWT.V_SCROLl); while(3shellsDi sposed())

2511 Piensa en Java for(Map.Entry entry: System.getenv().entrySet()) { text.append(entry.getKey() + " + entry.getValue() + Vn") ;

while(3shellsDi sposed())

} III i" } 22 Interfaces grficas de usuario 2512 }SWTConsole.run(new DisolayEnvironment{), 80G, 600);

SWTConsole nos permite centramos en los aspectos ms interesantes de una aplicacin, en lugar de tener que escribir el cdigo repetitivo.

Ejercicio 40: (4)Modifique DisplayProperiies.java para utilizar SWTConsole.

Ejercicio 41: (4)Modifique DisplayEnvironmcnt.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: //: swt/Menue.java public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2513 // Ejemplo de mens. import swt.til.*; import org.eclipse.swt.*; import org.eclipse.swt.widgets.*; import j ava.til.*; import net.mindview.util.*; public class Menus implements SWTAppiication { private static Shell shell; public void creaceContents(Composite parent) { shell *= parent.getShell(); Menu bar = new Menu (shell, SWT.3AR); shell.setMenuBar(bar); Set<String> words = new 7reeSet<String>( new Text File {Menus .java", "\\W+")) ; Iterator<String> it = words.iterator(); while(it.next().matches("[091 ) ; // Desplazarse hasta pasados ios nmeros. MenultemH mltem = ne;^ Menultemf7]; for(int i = 0; i < mltem.length; i++) { mltemfi] = new MenuTtem(bar, SWT.CASCADE); mltemTil.setText(it.next()); Menu submenu = new Menu (shell, SWT.DROP_lX)WN) ; mltem[i].setMenu(submenu);

} int i = 0; while (it .hasNext ()) { public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2514 addltem(bar, it, mltem[i]); i * (i + 1) % mltem. length;

} static Listener listener = new Listener() { public void handleEvent(Event e) { System.out .orintln(e. toStringO ) ;

} }; void addItem(Menu bar, lterator<String> it, Menultem mltem) { Menultem item * new Menultem(mltem.getMenu(),SWT. PUSH); item.addListener(SWT.Selectio n, listener); item.setText(it.next()); }SWTConsole.run(new MenusO, 600, 200); public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2515 }

} ///:-

Los objetos Men deben colocarse dentro de una Shell, y Composite permite determinar la shel mediante getShell( ). TextFle procede de net.mindview.udl y ya lo hemos descrito antes en el libro; aqu, se rellena un conjunto TreeSet con palabras, de forma que aparezcan ordenadas. Los elementos iniciales son nmeros, que se descartan. Utilizando el flujo de palabras, se asigna un nombre a los menis de nivel superior de la barra de mens y luego se crean los submens y se rellenan 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 informacin incluye la etiqueta del men, de manera que podemos pasar la respuesta del men en dicha informacin; o bien podemos proporcionar un escucha diferente para cada men (que es un enfoque ms seguro, de cara a la internacionalizacin). Paneles con fichas, botones y sucesos events

public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2516 SWT dispone de un rico conjunto de controles, denominados widgets. Examine la documentacin de org.eclipse.swt. widgets para ver los controles bsicos y org.eclipse.swt.custoni para ver otros ms sofisticados.

Para ilustrar algunos de los widgets 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 insertar elementos dentro de otros elementos; //: swt/TabbedPane.java // Colocacin de componentes SWT en panelee con fichas. import swt.til.*; import org.eclipse.SWL; import org.eclipse.swt.widgets.*; import org.eclpse.swt.events.*; import org.eclipse.swt.graph es. *; import org.eclipse.swt.layout.*; import oro.eclipse.swt.browser.*; public class TabbedPane implements SWTApplication { private static TabFolder folder; prvate static Shell shell; public void createContents(Composite parent) { shell = parenL.yeLShell(); parent.setLayoun(new FillLayout() ) ; folder = new TabFolder(shell, SWT.BORDER); labelTab(); rectoryDialogTabO ; buttonTab(); sliderTab(); scribbleTab(); browserTab();

public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2517 } public static void labelTab() { Tabltem tab = new TaoItem(folder, SWT.CLrOSE) ; tab.setText("A Label"); // Texto de la ficha tab.setToolTipText("A simple label"); Label label = new Label(folder, SWT.CKNTER); labe].setText("Label text"); tab.setControl(label);

) public static void directcryDialogTab() { Tabltem tab = new Tabltem(folder, SWT.CLOSE); tab.setText("Directory Dialog"); tab.setToolTipText("Select a directory") ;final Button b = new Button (folder, SWT. PUSH) ; b.setText("Select a Directory"); b.addListener(SWT.MouseDown, new Listener() { public void handieEvent(Event e) { DirectoryDialog ad = new DirectoryDialog(shell); String path = dd.openO; if(path != null) b.setText(oath);

) public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2518 }>; tab.setControl(b);

} public static void buttonTabO ( Tabltem tab = new Tabltem(folder, SWT.CLOSE); tab. setText ("Buttonsr11) ; tab.setToolTipText("Different kinds of Buttons"); Composite composite = new Composite(folder, SWT.NONE); composite.setLayout(new GridLayout(4, true)); for(int dir : new int [] { SWT.UP, SWT.RIGHT, SWT.LEFT, SWT.DOWN }> { Button b = new Button (composite, SWT.ARROW | dir); b.addListener(SWT.MouseDown, listener);

} newButton(composite, SWT.CHECK, "Check button"); new3utton(composite, SWT.PUSH, "Push button"); newButton(composite, SWT.RADIO, "Radio button"); newButton(composite, SWT.TOGGLE, "Toggle button"); newButton(composite, SWT.FLAT, "Flat button"); tab.setControl(composite);

} private static Listener listener = new Listener() public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2519 { public void handieEvent (Event e) { MessageBox m = new MessageBox(shell, SWT.OK); m.setMessage(e.toString()); m. open();

} }; private static void newButton(Composite composite, int type. String label) { Button b = new 3utton(composite, type); b.setText(label); b.addListener(SWT.MouseDown, listener);

) public static void sliderTab() { Tabltem tab = new Tabltem(folder, SWT.CLOSE); tab.setText("Sliders and Progress bars"); tab.setToolTipText("Tied Slider to ProgressBar"); Composite composite = new Composite(folder, SWT.NONE); composite.setLayout(new GridLayout(2, true)); final Slider slider = new Slider(composite, SWT.HORIZONTAL); final ProgressBar progress = new ProgressBar(composite, SWT.HORIZONTAL); slider.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { progress.setSelection(slider.getSelectionO);

public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2520 }>; }

tab.setControl(composite) ;public stacic void scribbleTabO { Tabltem tab = new Tabltem(folder, SWT.CLOSE); tab.setText(MSeribble"); tab.setToolTipText("Simple graphics: drawing"); final Canvas canvas = new Canvas (folder, SWT.NCNE) ScribbleMouseListener sml = new ScribbleMouseListener(); canvas.addMouseListener(sml); canvas.adaMouseMoveListener(sml) ; tab.setControl(canvas);

} private static class ScribbleMouseListener exter.ds MouseAdapter implements MouseMoveListener { private Foint p nev; Point(0, 0) ,* public void mouseMove(MouseEvent e) { if( (e.stateMask & SWT.BUTTON1) == 0} retum? GC ge = new GC((Canvas) e.widget) ; gc.drawLine(p.x, p.y, e.x, e.y); ge.dispose(); updatePoint(e);

public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2521 } public void mouseDown(MouseEvent e) { updatePoint(e); } private void updatePoint(MouseEvent e) { p.x = e.x; p.y = e.y;

} public static void browserTabO { Tabltem tab new Tabltem(folder, SWT.CLOSK); tab.setText("A Browser"); tab.setToolTipText("A Web browser"); Browser browser = nuil; try { browser = new Browser(folder, SWT.NONE); } catch(SWTError e) { Label label = new Label(folder, SWT.BORDER); label.setText("Could not initialize browser"); tab.setControl(label);

} public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2522 if(browser 1= nuil) { browser.setrl("http://www .mindview.net"); tab.setControl(browser);

} public static void main(String[] args) { SWTConsole.runnew TabbedPaneO, 800f 600);

} ///:-

public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2523 Aqu, crcateContents() 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 se(Text() (tambin podemos crear botones y grficos en una ficha), y cada una de las fichas establece tambin el texto de sugerencia. Al final 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. directoryDiaIog'lb( ) contiene un botn que abre un objeto estndar DircctoryDialog 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.

scribbleTab( ) es un divertido ejemplo de grficos. Se genera un programa de dibujo utilizando slo unas pocas lneas de cdigo.

public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2524 Por ltimo, browserTab() muestra la potencia del componente Browser de SWT, que es un explorador web completamente funcional empaquetado en un nico componente. Grficos

Me aqu el programa SineWave.java de Swing traducido a SWT: //: swt/SineWave.j ava // Traduccin a SWT del programa Swing SineWave.java. import swt.til.*; import org.eclipse.swt; import org.eclipse.swt.widgets.* import org.eclipse.swt.events.*; import org. eclipse. swt. layout. * class SineDraw extends Canvas { private static final int SCALEFACTOR = 200; private int cycles; private int points; private double [] sines; private int [] pts; public SineDraw(Composite parent, int style) { super(parent, style); addPaintListener(new PaintListener() { public void paintControl(PaintEv ent e) { int maxWidth = getSizeO.x; double hstep = (double)maxWidth / (double)points; int maxHeight = getSizeO .y; pts = new int{points]; for(int i = 0; i < points; i+4) ptsfi] = (int) ((sines[i] * maxlleight / 2 * .95) + (maxH public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2525 eight / 2)); e. gc.se tFore groun d( e.display.getSystemColor(SWT.COLOR RED)); Cui(iut i = 1; i < points; i++) { int xl=(int)((i - 1) * hstep); int x2=(int)(i* hstep); int yl=ptsfi -1] ; int y2=pts [i] ; e.gc.drawLine(xl, vl, x2, y2);

) }); setCycles(5);

} public void setCycles(int public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2526 newCycles) { cycles = newCycles; points = SCALEFACTOR * cycles * 2; sines = new double [points] ,* for (int i = 0; i < points; i+ + ) { double radians = (Math.PI / SCALKFACTOR) * i; sines [i] = Math.sin(radians);

} redraw();

} public class SineWave implements SWTApplication ( private SineDraw sines; private Slider slider; public void createContents(Composite parent) public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2527 { parent.setLayout(new GridLayout(1, true)); sines = new SineDraw(parent, SWT.NONE); sines.setLayoutData( new GridData{SWT.FILL, SWT.FILL, true, true)); sines.setFocus(); slider = new Slider {parent, SWT.HORIZONTAL) ; slider.setValues(5, 1, 30, 1, 1, 1) ; slider.setLayoutData{ new GridData(SWT.FILL, SWT.DEFAULT, true, false)); slider.addSelectionListener( new SelectionAdapter() { public void widgetSelected{SelectionEv ent event) { sines.setCycles(slider.g etSelection());

}>.

public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2528 } public static void main(String[] args) { SWTConsole.run(new SineWave(), 700, 40C);

} } ///:-

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 S WT, obtenemos el contexto grfico ge a partir del objeto suceso que se entrega al escucha PaintListener, y en Swing el objeto Graphics se entrega directamente al mtodo paintComponent( ). Pero las actividades realizadas con el objeto grfico son iguales y setCycles() es idntico.

ereateContents( ) requiere algo ms de cdigo que la versin Swing, para disponer los public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2529 elementos y configurar el deslizador 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 caracterstica monohebra de manera que se obtenga 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 c introduzca errores difciles de localizar dentro de un programa.

He aqu la traduccin a SWT del programa Swing ColorBoxes.java: //: swt/ColorBoxes.j ava // Traduccin a SWT del programa Swing ColorBoxes.java. import swt.til.*; import org.eclipse.swt; import org.eel ipse.swt.widgets.*; public static void main(String[1 args) {

} III i" } 22 Interfaces grficas de usuario 2530 import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.*; 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 { public void paintControl(PaintEvent e) { Color color = new Color(e.display, cColor)

public static void main(String[1 args) {

;e.gc.setBackground(color); Point size = getSizeO; e.gc.fillRectangle(0, 0, size.x, size.y); color.dispose{);

} private static Random rand = new Random{); private static RGB newColor() { return new RGB(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255));

} private int pause; private RGB cColor = newColor(); public CBox(Composite parent, int pause) { super(parent, SWT.NONE); this.pause * pause; addPaintListener(new CBoxPaintListener()) ;

} public void run() { try { while(!Thread.interrupted()) { cColor = newColor(); getDisplay () .asyncExec (nev; Runnable () { public void run() { try { redrawO ; } catch(SWTException e) {} // SWTException es OK cuando el padre es // terminado desde debajo de nosotros.

} }>; Timenit.MILLISECONDS.sleep(cause);

) } catch(InterruptedException e) { // Forma aceptable de salir } catch(SWTException e) { // Forma aceptable de salir: nuestro padre // ha sido terminado desde debajode nosotros.

} public class Color3oxes implements SWTApplication { private int grid = 12; private int pause = 50; public void createContents(Composite parent) { GridLayout gridLayout = new GridLayout(grid, true); gridLayout .horizontalSpacing *= 0; gridLayout.verticalSpacing = 0; parent.setLayout(gridLayout); ExecutorService exec = new DaemonThreaaPoolExecutor() for(int i = 0; i < (grid * grid); i++) { final CBox cb new CBox (parent, pause); cb.setLayoutData(new GridData(GridData.FILL_BOTH)); exec.execute(cb);

} public static void main(String[] args) { ColorBoxes boxes = new ColorBoxes() ; if(args.length > 0) boxes.grid = new Integer(args[01); if(args.length > 1 )boxes.pause = new Inzeger(args[1] ); SWTConsole.run(boxes, 500, 4 00);

} ///:**

Como en el ejemplo anterior, el dibujo se controla creando un escucha PaintListener con un mtodo paitContri>l() 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() directamente, sino que tiene que enviar la llamada a redraw() al mtodo asyncExec() del objeto Display, que equivale aproximadamente al mtodo SwingUtlites.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: lincas horizontales que atraviesan ocasionalmente un recuadro. Esto es debido a que SWT no utiliza doble buffer de manera predeterminada, mientras que Swing s lo utiliza. Trate de ejecutar la versin Swing al lado de la versin SWT y podr observar la diferencia ms claramente. Podemos escribir cdigo para utilizar un doble buffer SWT; podr encontrar ejemplos en

el sitio web www.eclipse.org.

Ejercicio 42: (4)Modifique swt/ColorBoxcs.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 difcil 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 forma ms sencilla de escribir programas que Swing. Sin embargo, 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 aspecto 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 tradzcalo a

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). Resumen

Las bibliotecas GUI de Java han experimentado cambios bastante drsticos a lo largo del tiempo de existencia del lenguaje. La biblioteca AWT de Java LO fue muy criticada por su pobre diseo, y aunque permita crear programas portables, la GUI resultante era igualmente mediocre en todas las plataformas. 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 preparado: ahora era posible crear componentes GUI que se podan arrastrar y colocar fcilmente dentro de un entorno IDE visual. Adems, el diseo del modelo de sucesos y de JavaBeans muestra claramente que los objetivos eran la facilidad de programacin y la utilizacin de cdigo fcilmente mantenible (algo que no era evidente en la biblioteca AWT de la versin 1.0). Pero la transicin no se concret hasta que aparecieron las clases JFC/Swing. Con los componentes Swing, la programacin de interfaces GUI multiplata forma puede resultar sencilla.

La verdadera revolucin radica en el uso de entornos IDE. Si desea adquirir un entorno IDE comercial para mejorar la programacin 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 IDE mejor, no estamos 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 ce los programadores.

Este captulo ha pretendido nicamente proporcionar una introduccin a la potencia de la programacin de interfaces GUI, 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 Flash/Flex tienen muchas otras caractersticas y funcionalidades adicionales, ya que se trata de herramientas de diseo de interfaces de usuario 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.ofg/presentations hacen un repaso muy adecuado tanto de Swing como de SWT. Puede encontrar las soluciones a los ejercicios seleccionados en el documento electrnico The Thinking in Java Annotated Salution Cuide, disponible para la vena 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 ios seminarios suelen ser pblicos, tambin pueden impartirse como seminarios privados en sus propias oficinas. Suplementos descargables

El cdigo correspondiente a este libro est disponible para su descarga en www.MindView.neL Esto incluye los archivos de construccin Ant 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 3a edicin de Thinking in Java, que no eran lo suficientemente relevantes como para incluirlos en la versin impresa de la cuarta edicin del libro.

Thinking in C: fundamentos para Java

En www.MindView.net, podr descargarse gratuitamente el seminario Thinking in C. Esta presentacin, creada por Chuck Allison y desarrollada por MindView, es un curso Flash multimedia que proporciona una introduccin a la sintaxis, los operadores 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 Player 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 Handu-On Java, se trata de nuestro principal seminario introductorio, que proporciona la base para seminarios ms avanzados. Una serie de materiales seleccionados de cada captulo representan una leccin, que va seguida por un perodo de ejercicios monitorizado, de modo que cada estudiante recibe una atencin personalizada. Puede encontrar informacin sobre horarios y lugares donde se imparte este seminario, junto con testimonios y otros detalles en www.MindView.net. Seminario en CD Hands-On Java

El CD Hcmds-On Java contiene una versin ampliada del material del seminario Thinking inJava 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 una conferencia audio y una serie de presentaciones correspondientes a cada uno de los captulos del libro, Yo he creado personalmente dicho seminario y me he encargado de narrar el material contenido en el CD. El material est en formato Flash, por lo que debera poder ejecutarse en cualquier plataforma que soporte Flash Player. El CD de Hands-On Java CD puede adquirirse a travs de 1www.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 general. unas pequeas herramientas como las tcnicas de planificacin mediante tarjetas ndices descritas en Planning Extreme Programming por Beck y Fovvler (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 una semana.

S est iniciando un proyecto y desea comenzar a utilizar tcnicas de diseo orientadas a objetos, podemos emplear su proyecto como ejemplo y disponer de un primer prototipo de diseo al final de la semana.

Visite www.MindView.net para obtener la informacin 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 programacin empresarial. Est disponible (aunque todava en desarrollo) en forma de descarga gratuita en www.MindYiew.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 interaces de usuario

Enterprise JavaBeans

XJVLL

Servicios Web

Pruebas automticas

Puede encontrar informacin sobre el estado actual (en ingls) de Thinking in Enterprise Java en www.MindView.net.

Thinking in Patterns (con Java)

lino de los pasos adelante ms importantes dentro del diseo orientado a objeto es el movimiento de los patrones de diseo, que se tratan en Design Patterns, de Gamma, Helm, Johnson & Vlissides (Addison-Wesley, 1995). Dicho libro muestra 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 programacin 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 perspectiva 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 capitulo 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 escribir 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 & Systems y Thinking in Patterns).

Puede encontrar ms informacin acerca de este libro en wmv.MindView.net.

Seminario Thinking in Patterns

Este seminario ha evolucionado a partir del seminario Objects & Patterns que Bill Venners y yo mismo hemos impartido en los ltimos aos. Dicho seminario lleg a estar demasiado cargado de contenido, por lo que lo dividimos en dos seminarios independientes: ste y el seminario Designing Objects & Systems, descrito anteriormente.

El seminario se ajusta de manera bastante fiel al material y a la presentacin del libro Thinking in Patterns, por lo que la mejor forma de conocer los detalles sobre el seminario es consultar la informacin relativa al libro en www.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 proyecto 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 reaJ puede comenzar en forma de una solucin adecuada a un problema concreto, para terminar evolucionando hasta convertirse en una solucin flexible para toda una clase de problemas.

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 www.MindVtew.net para obtener ms informacin acerca de los horarios y los lugares donde se imparte este seminario, 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 implementaciu como ayuda durante el ciclo de desarrollo de un proyecto, incluyendo los primeros proyectos Java de cualquier empresa. Visite www.MindView.net para obtener ms informacin sobre disponibilidad y otros detalles

.Recursos

Software

El JDK disponible en http://java.sun.com. Incluso si decide emplear un entorno de desarrollo de otro fabricante, siempre es conveniente tener el JDK a mano, por si acaso se tropieza con algn posible error del compilador. 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 bien documentado.

La documentacin del JDK disponible en http://java.sun.com, en formato 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 faltara informacin. 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 inicialmentc un recurso en lnea en lugar de un libro en papel, pero merece la pena superar esa incomodidad inicial y consultar los documentos HTML, al menos para obtener una panormica general. Si le sigue sin gustar el mtodo de las referencias en lnea, adquiera los libros impresos. Editores y entornos IDE

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 Pestov, escrito en Java, con lo que obtenemos la ventaja adicional de poder ver en accin una aplicacin de escritorio desarrollada Java. Este editor est basado fuertemente en la utilizacin de plug- ins, muchos de los cuales han sido escritos por la comunidad Java. Puede descargarlo en www.jedit.org.

NetBeans, un entorno IDE gratuito de Sun, disponible en >vww.netbeans.org. 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 IBM, entre otros. La plataforma Eclipse tambin est diseada para constituir una base ampliablc de desarrollo, de modo que se pueden construir aplicaciones autnomas sobre Eclpse. Este proyecto desarroll el lenguaje SWT descrito en el Captulo 22, Interfaces grficas de usuaiio. Puede descargarlo en www.Eclipse.otg.

IntelliJ IDEA, el entorno comercial favorito de un gran nmero de programadores Java, muchos de los cuales afirman que IDEA siempre est a uno o dos pasos de Eclipse, posiblemente porque IntelliJ no trata tanto de crear un entorno IDE como una plataforma de desarrollo, sino que se limita a los aspectos del entorno IDE. Puede descargar una versin gratuita en www.jetbrains.com.

Libros

Core Java 2, 7*h Edition, Volmenes I & II, 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 Java y se necesite comenzar a profundizar en los distintos temas.

The Javam Class Librarles: An Annotated Reference. por Patrick Chan and Rosanna I^ee (AddisonWesley, 1997). Aunque est desactualizado, este libro es lo que la referencia JDK debera haber sido: incluye las suficientes descripciones

como para hacerlo utilizable. Uno de los revisores tcnicos de Thinking in Java me dijo: Si tuviera un nico libro sobre Java, sera ste (adems del tuyo, por supuesto). Yo no esloy tan entusiasmo con este libro, como dicho revisor. Es voluminoso, resulta caro y la calidad de los ejemplos no me parece adecuada. Sin embargo, es un buen lugar en el que consultar 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 Programming, 2aEdicin, por Elliotte Rusty Harold (OReilly, 2000). Personalmente, no empec a comprender 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, informativo y actualizado en lo que respecta 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 Pattems, por Gamma, Helm, Johnson y Vlissides (Addison-Wesley, 1995). El libro original que dio comienzo al movimiento de patrones de diseo dentro del campo de la programacin y que hemos mencionado en numerosos lugares a lo largo del libro.

Refactoring io Pattems, por Joshua Kerievsky (Addison-Wesley. 2005). Combina el tema de la reingenieria 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 U N I X 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, 2nJ Edition, por Kent Beck con Cynthia Andrs. (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 actualmente. 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. Extreme 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 demasiado 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 estuerzo y que resulta excitante. Uno puede imaginarse a s mismo en ese tipo de atmsfera y le asaltan visiones de un nuevo mundo que se abre a sus pies

UML Distilled, 2a 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 disef.o, en lugar de preocuparse acerca de todos los aditamentos que hacen falla para conseguirlo. De hecho, la mayor parte de los lectores no necesitarn la primera mitad del libro. Es un libro muy atractivo, de pequeo 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 principal herramienta del proceso de diseo. 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.

2 Todo es un objeto 2555 The Vnifted 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. Pareca tener todos los ingredientes de un aburrido libro universitario. Sin embargo, me vi gratamente sorprendido (aunque hay algunas partes que incluyen explicaciones 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 tambin forma parte del arsenal del mundo UML: incluso aquellos que no desean utilizar XP suelen estar de acuerdo en que UML es un buen lenguaje de modelado (independientemente del nivel experiencia real que tengan los que emiten estas 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 vendemos 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 embargo, los seres humanos tienen una tendencia psicolgica muy curiosa. Si quieren creer que algo resolver sus problemas, tratarn 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 realidad, que no es tan bueno). El mecanismo que subyace a este comportamiento es que si podemos conseguir que otras personas 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 pane, sino slo que debemos utilizar las herramientas mentales que nos permitan permanecer 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 necesitamos cambiarlo). En mi opinin, los siguientes libros que hay que \cex antes de elegir un mtodo, le proporcionarn esas herramientas fundamentales.

2 Todo es un objeto 2556 Software Creativity, 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 necesaria; el autor no divaga ni aburre. Tampoco se dedica a vender humo: hay cientos de referencias a otros artculos y estudios. Todos los programadores y gestores deberan leer este libro antes de entrar en el tema de las metodologas.

Software Runaways: Monumental Software Disasters, 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), y eso 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. 2a 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 trabajo 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 decidir las icglas que licen que seguir para ser cuiiipuucntcs adecuados de una mquina. Esta ltima aptitud, en mi opiniu, es la que ms contribuye a que los programadores se sonran y se burlen cuando se adopta el mtodo XYZ, despus de lo cual continan haciendo en silencio lo que siempre han estado haciendo.

Secrets of Consulting: A Guide to Giving & Getting Advice Successfully, por Gerald M. Weinberg

2 Todo es un objeto 2557 (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 historias y ancdotas que nos ensean cmo llegar hasta el fondo del asunto con un esfuerzo mnimo. Lea tambin More Secrets of Consulting, publicado en 2002, o cualquier otro de los libros de Weinberg.

Complexity. por M. Mitchell Waldrop (Simn & 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 Informtica, 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 forma que se aparta del determinismo 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 absentar 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 algoritmos genticos). Este tipo de pensamiento, en mi opinin, resulta til para ver formas de gestionar proyectos de software cada vez ms complejos. Python

Leaming Python, 2a Edicin, por Mark Lutz y David Ascher (OReilly, 2003). Una buena introduccin a mi lenguaje favorito que 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

2 Todo es un objeto 2558 No todos estos libros estn actualmente disponibles, pero algunos de ellos pueden encontrarse en la libreras de segunda mano.

Computer Interfacing with Pascal & C (publicado por Eisys, 1988. Disponible a la venta nicamente en www.MindView.net). Una introduccin a la electrnica escrita en los aos en que CP/M segua siendo el rey y DOS no pasaba de ser un advenedizo. Por aquel entonces, yo usaba lenguajes de alto nivel y a menudo el puerto paralelo de la computadora para controlar diversos proyectos electrnicos. Adaptado a partir de las columnas que 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 que Internet apareciera. La creacin de este libro fue una experiencia editorial extremadamente satisfactoria.

Using C++ (Osbome/McGraw-Hill, 1989). Uno de los primeros libros sobre GH\ Est agotado y ha sido sustituido por su segunda edicin, cuyo ttulo es C++ Inside & Out.

C++ Inside & Out (Osbome/McGraw-Hill, 1993). Como ya digo, es la segunda edicin de Using-C+ +. El lenguaje C*B- de este libro es razonablemente preciso, pero fue escrito en tomo a 1992 y posteriormente fue sustituido por Thinking in C++. Puede encontrar ms detalles acerca de este libro y descargar el cdigo fuente en www.MindView.net.

2 Todo es un objeto 2559 Thinking in C++, Edicin (Prentice Hall, 1995). Consigui el premio Jolt de la revista Software Development Magazine como mejor libro del ao.

Thinking in C++, 2aEdicin, Volumen I (Prentice Hall, 2000). Puede descargarlo de www. MindView.net. Actualizado para adaptarlo al estndar del lenguaje recin finalizado.

Thinking in C++, 2* Edicin, Volumen 2. Escrito en colaboracin con Chuck Allison (Prentice Hall, 2003). Puede descargarlo en www.MindView.net.

Black lielt C++: The Mster s Collection, Bruce Eckel, editor (M&T Books, 1994). Agotado. Una coleccin de captulos escritos por diversos expertos en C++ y basados en sus presentaciones dentro del ciclo dedicado a C++ en la conferencia Software Development Conference, de la cual fui moderador. La cubierta de este libro ie la que me anim a decidir todos los futuros diseos de cubierta.

Thinking in Java, Ia Edicin (Prentice Hall, 1998). La primera edicin de este libro gan el premio a la productividad de la revista Software Development Magazine, el premio del editor de la revista Java Developer 's Journal y el premio de los lectores de la revista JavaWorld como mejor libro. Puede descargarlo en www.MindView.net.

2 Todo es un objeto 2560 Thinking in Java, 2a Edicin (Prentice Hall, 2000). Esta edicin gan el premio del editor de la revista Ja\>aWorld como mejor libro. Puede descargarse en www.MindView.net. Thinking in Java, 3a Edicin, (Prentice Hall, 2003). Esta edicin gan el premio Jolt de la revista Software Development Magazine como mejor libro del ao, junto con otros premios que se indican en la contraportada. Puede descargarlo de www. MindView. net.ndice

! 51 - 49 &

smbolo para anotaciones 693 @author 38

^Override 693

@.param 38 & 55 &&51 &= 55 @Dcprecated, anotacin 693 @deprecated, marcador Javadoc 39 @docRoot 38 @inheritDoc 38

@Retention 694

(interfaces y extends, palabra clave- 700 .NET 20 -new, sintaxis -214 .this, sintaxis 214 @ @ return 38

@link 37

2 Todo es un objeto 2561 @see 37 A 55 *=55 <

@since 38
I-

<49 55 = - 55 <=49 55

(^Suppress Warnings @Target @Test 694

= 49 > 693 694


II-

*55

51 h

@Test para @Unit 709 @TestObjectCleanup para @Unit 715 @TestObjectCreate para @Unit -713 @throws 38 @Unit 709 (^version 38 1

>49 >=4 9 55 = 55 A

+ 48 String, conversin con operador + 44,59.317 abstraccin 1

\ ], operador de indexacin 110

2 Todo es un objeto 2562 Abstract Window Toolkit (AWT) -857 abstract, palabra clave 189 AbstractButton 876 AbstractSequentialList 558 AbstractSet -514 acceso: ActionScript, para Macromedia Flex 933 adaptador, patrn de diseo 198, 204, 270,402,472,474,515 adaptadores de escucha 873 add( ), ArrayList 242 addActionListcner( ) 924, 929 addChangeListener 897 addListener 869 Adler32 635 AND: bit a bit S), 60 lgico (&&) 51 anidamiento de interfaces 206 anotaciones 693 apt, herramientas de procesamiento de- 703

clases internas y derechos de 213 control de 121, 136 control de, violacin con la reflexin 387 de clase 134 de paquete 129

elementos 694, 697 marcadora 694 agotar la memoria, solucin mediante Refcrences 579 agregacin 6

dentro de un directorio a travs del paquete predeterminado 130 especificadores de 5, 121, 128 acoplamiento: de las llamadas a mtodos 168 dinmico 168

procesador basado en la reflexin 700 procesador de 696 agmpamientos, @Unit y JUnit *717 alias, creacin de 45 Allison, Chuck - 4, 18, 955, 962 allocate( ) 617 aIlocateDircct() 617 mbitos, clases internas en mtodos y 217 valores predeterminados de los elementos 695, 697 aplicacin de un mtodo a ur.a secuencia 468

dinmico, tardo o en tiempo de ejecucin 165, 168 tardo 10, 165, 168 temprano 10 ActionEvent 894, 926 ActionListener 865

ampliabilidad 171

Application Builder 918 apt. herramienta de

2 Todo es un objeto 2563 procesamiento de anotaciones 703 archivos: caractersticas de 594 cuadros de dilogo de 901 de salida, errores y volcado 606 File, clase 587, 597, 602 archivos (continuacin) programming (AOP) 458 assert y @Unit -711 atmica, operacin 760 atomicidad en la programacin concurrente754 Atomiclnteger 764 AtomicLong 764 AtomicReference 764 autodecremento, operador 48 autoincremento. operador 48 availably) 605 BASIC -918

BasicArrowButton 877

File.list() 587 JAR 123 bloqueo de 632 mapeados en memoria 629 argumentos: constructor 86 covariantes 453 final 158, 589

B Bean:

barra de progreso 903 base 16 53 base 8 53

inferencia del argumento de tipo 403 lista de argumentos variable- 113 Arnold. Ken 858 ArrayBlockingQueue 796 ArrayList 242, 249, 530 add() 242 geK) 242 size() 242 Arrays: asList() 245, 272, 530 binarySearch() 508 clase, 502 asCharBufFer() 619 asignacin 44

base, clase 131,142,168 abstracta 189 constmctor de la 176 interfaz -171 base, tipos 7

application builder -918 archivo de manifiesto 930 Beanlnfo 931 componente -919 convenio de denominacin -919 empaquetado de 930 EventSetDescriptors 922 FeatureDescriptor 931 getBeanInfo() 920 getEventSetDescriptors( ) 922 getMethodDeseriptors() 922 getName() 922 getPropertyDescriptors() 922

aspecto y estilo seleccionabas 904 aspect-oriented

BASIC,

Microsoft

Visual

2 Todo es un objeto 2564 getPropertyType() 922 getReadMethod() 922 getWriteMelhod() 922 hoja de propiedades personalizada 931 Introspector - 920 Method 922 MethodDescriptors 922 programacin visual 918 PropertyChangeEvent - 931 PropcrtyDescriptors 922 propiedades -918 propiedades indexadas 931 ProptertyVetoException 931 reflexin -918,920 Serializable 926 sucesos 919 y Delphi de Borland -918 y Microsoft Visual BASIC -918 Beanlnfo- 931 Beck, Kent 960 biblioteca AND operador (&) 55, 60 EXCLUSIVE OR o XOR (A) 55 NOT - 55 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 BufteredlnputStream 599 BufferedOutputStream 600 BuffercdReader 304, 602, 603 BufferedWriter -601, 605 bsqueda: de archivos de clases durante la carga 124

OR operador (]) 55, 60 BitSet 584

creador de, y programador de clientes 121

Bloch, Joshua 98, 659, 751, 762 BlockingQueue 796, 809 bloqueo: contienda 835 de archivos * 632 en concurrencia 756 en programas concurrentes729 optimista 847 y available() 605 Booch, Grady 960 Boolean 70: lgebra booclana- 55 C y C++-51

en una matriz 508 y ordenacin en listas 575 BultonGroup 877, 884 ByteArraylnputStream 597 BytcArrayOutputStrca m 598 ByteBuffer -616

diseo de 121 uso 121

C CU. lenguaje de programacin 20 C++ 49, 586 plantillas 394, 417 tratamiento de excepciones operadores que no funcionan * 310 CachedThreadPool 734 con el tipo boolean 49 y Cadena de responsabilidad, proyeccin 60 borrado 447 patrn de diseo 676 Callable, en genricos -415 boln: concurrencia 736 cambio de contexto- 728 campos, inicializacin en interfaces 205 canal, nio 616 canalizacin 597 canalizaciones para la E/S 800

binarios, impresin de nmeros 58 binaryScarch( ) 508, 576 bit a bit, operadores 55:

2 Todo es un objeto 2565 capacidad inicial de un HashMap o HashSet 571 .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 INSENSITIVEjORDER, comparador 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 chcckedCollecton() 456 CheckedlnputStream 634 checkedList() 456 checkedMap() 456 CheckedOutputStream 634 checkcdSet() 456 checkedSortedMap( ) 45

Indice 2566 6checkedSortedSel( ) 456 Checksum, clase 635 Chiba, Shigeni, 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 inicial izacin * 162, 357 inioializacin de campos de 1 o2 inicializacin de la clase base 143 inicializacin de la clase derivada- 143 inicializacin de miembros 140 instanceof/islnstance( ) y equivalencia de-373 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

Indice 2567 anidamiento dentro de un mbito arbitrario 218 annimas -219, 589, 864 cierre 229

derechos de acceso -213 en mtodos y mbitos -217 genricos -412 heredar de 236

dentificadores y archivos .class 239 local 217 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

Indice 2568 y marcos de control 230 y super 236 y sustitucin 236 y Swing - 869 Class 878 Class. objeto 353, 651, 757 forNamc( ) 354, 872 getCanonicalName() 356 getClass() 287 getConstructors() 377 gctlnterfaces() 356 getMethods() 377 getSimpleNaine( ) 356 gctSuperclass() 356 isAssignableFrom() 369 islnstance() 368 islnterface<) 356 limites 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 ClassNolFoundException 365 classpath 124 clear(). nio 618

cliente, programador de 5 y creador de bibliotecas -5, 121 closef ) 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

comntanos y documentacin embebida 35

Indice 2569 comodines: de supertipo 438 en genricos * 434 no limitados 440 y referencias Class 359 Comparable 504, 533, 538 comparacin de matrices 5C3 Comparator 505, 533 compareTof), en java.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 anotaciones con @Unit 709 tcnicas de 226 concurrencia: almacenamiento local de habras 771 Array Block ingQueuc 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 LinkedBlockingQucue - 7% lock explcito 758 long y double 760 objetos activos 850 optimizacin del rendimiento 834 prioridad 738 PriorityBlockingQueue -811 productor-consumidor 791 concurrencia (continuacin)

Prueba de Goctz para evitar la sincronizacin 760 ReadWriteLock 848 Regla de Brian de la

Indice 2570 sincronizacin 757

ScheduJcdExeculor -814 semforo -817 seales perdidas 788 sleep() 737 SynchronousQueue 826 terminacin de tareas 772 UneaughtExceptionHandler - 752 y contenedores 577 y excepciones 759 y Swing 910 ConcurrentHashMap 542, 841. 845 ConcurrentLinkedQueue 841 ConcurrentModificationException 578 uso de CopyOnWriteArrayList para eliminar 841, 852 condicin de carrera, concurrencia 755 condicional, compilacin 128 condicional, operador 58 Condition, concurrencia 793 conjunto compartido de objetos -817 consola:

entorno de visualizacin Swing 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

Indice 2571 comportamiento de los mtodos poli- mrficos 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 las llamadas a 176 predeterminado 92 predeterminado sintetizado 377 sin argumentos 86, 92 sobrecarga 87 static, clusula 108 valor de retomo 86 y clases internas annimas 219 y concurrencia 745 y fmally 303

y polimorfismo 175 y tratamiento de excepciones 302 consultoria y formacin proporcionada por MindView, Inc. 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 Copen, Jim 451 CopyOnWriteArrayList -821, 841 CopyOnWrite Array Set 841 copyright, cdigo fuente xxx cortocircuitos y operadores lgicos 52 CountDownLatch- para concurrencia -805 covariante: argumento 453 matrices 434

Indice 2572 tipo de retomo 183,371, 453 CRC32 635

CSS (Cascading Style Sheets) y Macromedia Flex - 936 cuadro combinado 885 cuadros de dilogo 898 con Helias 887 de archivos 901 cuantificadores 335 CyclicBarrier, para concurrencia 807 D

DatagramChannel 632 Datalnput 603

DatalnputStream 599, 602, 604 DataOutput 603 DataOutputStream 600, 602 datos: final 156

inicializacin de datos estticos - 106 tipos de datos primitivos y uso con operadores 62 decodc( ), conjunto de caracteres 620 Decorador, patrn de diseo 461 decremento, operador 48 default. palabra clave en una instruccin switch 82 default ReadObject( ) 648

Indice 2573 defaultWriteObject() 647 DeflateiOutputStream 634 Delayed * 811

DclayQueue, para concurrencia 809 delegacin 145, 461 Delphi de Borland -918 DeMarco, Tora 961 demonio, hebras 740 depuracin de memoria 97,98, 100 cmo funciona el depurador de memoria 100 objetos alcanzables 578 y limpieza 148 desacoplamicnto 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 deslizador 903 despacho mltiple: con EnumMap 690 y enumeraciones 684 despacho simple * 684 desplazamiento a la derecha, operador ()' 55

desplazamiento a la izquierda, operador () * 55 desplazamiento, operadores de 55 destructor 97, 98,296 Java no tiene 148 diagramas de herencia II, 155 dibujo en un JPanel en Swing 895 diccionario 244 diferida, inicializacin 140 Dijkstra, Edsger 801 directorios: bsqueda y creacin de 594 listados de 587 y paquetes 128 disciplina de gestin de colas 264 diseo 183 adicin de ms mtodos al 137 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 EnumMap 690 documentacin 17 comentarios y documentacin embebida 35 double: valor literal (d o D) 53 y hebras 760 do-while 73

Indice 2574 downcasting. Vese especializacin E

East, BorderLayout 866 editor, creacin con JTcxtPane 882 efecto colateral 44,49,92 eficiencia: y final 161 y matrices 483 ejecucin de programas del sistema operativo desde dentro de Java -614 ejecucin de un programa Java 35 else, palabra clave 71 encadenamiento de excepciones -291, 313 encapsulacin 133, 387 encodc(), conjunto de caracteres 620 entorno de visualizacin para Swing 861 Entrada/salida: availab!e() 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 canalizaciones para la - 800 caractersticas de archivos - 594 CharArrayReader 601 CharArrayWriter 601 CheckedlnputStrcam 634 CheckedOutputStream 634 close() 604

configuraciones tpicas de 603 control en la sealizacin 642 creacin y bsqueda de directorios

594

Datalnput 603

Indice 2575 DatalnputStream 599, 602, 605 DataOutput 603 DataOutputStream - 600, 602 DeflatcrOutputStream 634 E/S de red 616 entrada 596

entrada estndar, lectura de la 613 Extemalizable 643 File 597, 602 File, clase 587 Fi! c.list() 587 FileDescriptor 597 FilelnputReader 603 FlelnputStream 597 FilenameFilter 587 FileOuiputStream - 598 FileReader 304, 601

FileWriter-601,605 FilterOutputStream 598 FilterReader 602 FilterWnter 602 flujo de datos canalizado 609 GZIPInputStream 634 GZIPOutputStream 634 InflaterlnputStream 634 InputStream 596 InputStreamReader 600, 601 internacionalizacin 601 mterrumpible 778 LineNumberlnputStream * 599 LineNumberReader 602 listado de directorios- 587 mark() 603 mkdirs() 596 new nio 616 ObjeclOutputStream 639 OutputStream 596, 598 OutputStrcamWriter 600. 601 persistencia ligera 639 PipedlnputStream 597 PipedOutputStream * 597, 598 PipedRcader 601 Piped Writer 601 PrintStream - 600 Print Writer 602, 605, 606 PushbacklnputStream 599 PushbackReader 602 RandomAcccssFile 602, 603, 608 read() 596 readDoub!e() 608 Reader 596, 600, 601 readExtcmaI() 643 readLinc() 305, 602, 606. 613 readObject() 639

redireccionamicnto de la E/S estndar 613

renameTo() 596

Indice 2576 resetC) 603

salida 596

seek() 602, 608

SequencelnputStream - 597, 602

Serializable 643

setErri PrintStream) -614

Indice 2577 setlnflnputStream) -614

setOut(PrintStream) -614

StreamTokenizer 602

StrmgBufTer 597

StringBufferlnputStrcam 597

StringReader -601, 604

Indice 2578 String Writer 601

System.err -613

Systcm.in 613

System.out -613

transient 646

Unicode 601

Indice 2579 uso bsico, ejemplos 603

write() 596 writeBytes() 607 writeChars() 607 writeDouble() 607 writeFxtemalC) * 643 writeObject() 639 Writer 596, 600. 601 ZipEntry 637 ZiplnputStream 634 ZipOutputStream 634 enttySet( ) en Map 549 enum:

adicin de mtodos 661 e importaciones estticas 660 e interfaces 667

grupos de valores constantes en C y C++ 205 mtodos especficos de constante 673, 688

palabra clave 117, 659 values() 659, 663 y despacho mltiple- 684 y herencia 665 y mquinas de estado 680 y patrn de diseo Cadena de responsabilidad 676 y seleccin aleatoria 666 y switch 662 enumeracin Vese enum enumerados, tipos 117 Enumeration 581 EnumMap 672

Indice 2580 EnumSet *410, 585 en lugar de indicadores 671 envo de mensajes 3 equals() 50 condiciones que debe satisfacer 547 y estructuras de datos hash 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 informacin de 309 recuperacin 277

tratamiento de errores mediante excepciones 277 y diseo 137 es-como-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 especializacin 155. 18, 362 especificacin de la excepcin 286, 309 especificacin explcita del tipo de argumento 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 enum 660 inicializacin de datos 106 inicializador -371 synchronized 757

y comprobacin de tipos dinmicos 528 y final * 156 estilo de codificacin 39 de creacin de clases 133 estrategia, patrn de diseo 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 condicin excepcional 278 constructores 303

conversin de comprobadas a no comprobadas -313 creacin de nuestras propias 281 deteccin de una excepcin 279 directrices de uso *315 encadenamiento de 291, 313 Error, clase - 294

Indice 2581 especificacin de 286. 310 Exception, clase 294 FileNotFoundException 304 fillInStackTracc( ) 289 finally 295 generar 278 genricos 457

informe de excepciones mediante logger 284 jerarquas de clases y 307 localizacin de 307 no comprobadas 294 NulIPointerException * 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 -301, 307 y la consola 312 Exchanger820

exclusin mutua (mutex), concurrencia 756

Executor, concurrencia 734 ExecutorService 734 explorador de clases 134 expresiones regulares- 331 extends - 131, 142, 185 e interfaces 202 palabra clave 142 y (^interfaces 700 extensin con ceros 55 extensin de signo 55 Extemalizable 643 una alternativa a 647 Extreme Programming (XP) 960 F

Indice 2582 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 931 Fibonacci 401 Field, para reflexin 375 F1FO (first-in, first out) - 263 File, clase 587 FileChannel - 616 FileDescriptor - 597 FilelnputReadcr 603 FilelnputStream 597 FileLock 632 FilenameFilter 587 FileNotFoundException 304 FileOutputStream 598 FileRcadcr 304, 601 FileWriter 601, 605 fillInStackTracc ) 289 FilterlnputStream 597 FilterOutputStream 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

Indice 2583 valores final en blanco 158 589 y eficiencia 161 y prvate 159 y static 156 finalize() 97, 305 finally 148. 150, 295 error 300

no ejecutar con hebras demonio 743 palabra clave - 295 y constructores 303 y retum 299 FixedThreadPool 734 Flex 932 flip(), mo 617 float: valor literal (F) 53 FlowLayout 867 flujo de dato de E/S 596 flujo de datos canalizado 609 for, palabra clave 73 foreach 74, 78, 114, 115, 127, 232, 243, 262, 267. 345,401,402,446, 659, 677

c Iterable 269

y mtodo basado en adaptadores -270 forma (shape): ejemplo 7, 169 y RTT1 351 format() 324 formato: anchura 326

de cadenas de caracteres 324 especificadores de 326 precisin 326 Formatter - 325 forName() 354. 872 FORTRAN, lenguaje de programacin 54

Indice 2584 Fowler. Martin 121, 960 funcin hash perfecta- 551 Funcin, objetos de 474 Future 737

generador 399. 406, 494, 515, 666, 681 de propsito general 407 para rellenar un objeto Collection 406 generalizacin II, 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 @Unit con- 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 islnstance() 424 limites - 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 autolimitados 450 vararas y mtodos genricos 405 y contenedores seguros respecto al tipo

Indice 2585
242

gestor de invocaciones para proxy dinmico- 379 get():

ArrayList 242 HashMap 261

no hay gct() para Collection 525 getBeanInfo() - 920 getBytes() - 605 getCanonicalName() 356 getChanncK) * 617 getClass() 287, 354 getConstructor() 878 getConstructors( ) 377 getcnv() 269

getHvenlSetDescriptors() 922 getlnterfaces ) 356 getMethodDescriptors ) 922 getMcthods() 377 getName() 922 getPropertyDcscriptors( ) 922 getPropertyType{) 922 gctReadMethod() 922 gctSelectedValues() 886 getSimpleName() 356 gctStatc( ) * 894 getSuperclass) 356 getWriteMethod() 922 Glass. Roben 961

Indice 2586 Goetz, prueba de, para evitar la sincronizacin - 760 Goetz, Brian 757, 760. 835. 855 goto, no existe en Java 78 Graphics, clase 896 GridBagLayout 868 GridLayout 867, 917 Grindstaff, Chris 941 grupo de hebras 751 grupos, expresiones regulares- 338 GUI {graphical user interface) 231, 857 constructores de interfaces- 858 GZIPInputStream 634 GZIPOutputStream 634 H

Harold, Hlliotte 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, 845r 876 HashSet 258, 533, 567 Hashtable 570, 582 hasNext(), Iterator 253 hebras:

almacenamiento local de 771 estados de las 775 grupo de 751 interrupt ) 776 isDaemon( ) 742 notifyAll() 784 planificador de - 732 prioridad 738

resume{) e interbloqueos 775 safety, biblioteca estndar Java 807 stop() e interbloqueos 776 suspend{ ), e interbloqueos 775 wait() 784

Indice 2587 y tareas, terminologa 748 herencia 6, 131, 139, 142. 165 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

hexadecimal 53

hojas de estilo en cascada Vase CSS

Indice 2588 Holub. Alien 851

irrML en componentes Swing 902

Iconos- 878

IdentityHashMap 542, 570 if-else, instruccin 71 if-else, operador temario 58 IllegalAccessException 365 IllegalMonitorStateException 785 Imagelcon 878 implementacin 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 [ J 110 indcxOf(), String 377 indicador de fin 399 indicadores en lugar de EnumSet 671 inferencia del argumento de tipo 403 InflaterlnputStream 634 informacin de tipos en tiempo de ejecucin. Vase RTTl ingeniera de cdigo intermedio - 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

Indice 2589 orden de inicializacin - 105, 182 static 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

intercambiador Vese Exchanger interface, palabra clave 192 interfaces 189-210: anidaroiento de 206 clases dentro de 225 colisiones de nombres al combinar 202 comn 189 de un objeto 3

Indice 2590 e implementacin 5, 133, 152 868 generalizacin a una 194 inicializacin de campos en las 205 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,1857 internacional izacin en la biblioteca de E/S-601 interrupt): concurrencia 776 hebras 748 Introspector 920

isA$signableFrom(), mtodo de Class

369

isDaemon() - 742 islnstance() 368 y genricos 424 islnterfacc() 356 Itcrable 269. 401.517 y foreach 269 y matrices 269 lterador nulo, patrn de diseo 381 Itcrador, patrn de diseo 214 Iterator 252, 253, 266 hasNext() 252 next() 252 5

Indice 2591 Jacobsen,Ivar 960 JApplet 866 menus 890 JAR 930 archivo 123

archivos jar y classpath 125 utilidad 637 Java:

AWT 857

compilacin y ejecucin de un programa 35

Java Foundation Classes (JFC/Swing) 857

Java Web Start 906 JVM 353

Indice 2592 seguridad de las hebras en la biblioteca

807

y lenguaje ensamblador -319 JavaBean. Vase Bean 918 javac 35 javadoc 36

javap, descompilador 318, 389, 422 Javassist 723 JButtoo 878 Swing 862 JCheckBox - 878, 883 JCheckBoxMenultem -891, 893 JComboBox 885 JComponent 880, 896 JDialog 898, 890 JDK. descarga e instalacin 35 JFC, Java Foundation Classes (Swing) 857

JFileChooser 901 JFrame 866, 890 JIT, compiladores just-in-time 102 JLabel 881 JList 886 JMenu 890, 893 JMenuBar 890, 893 JMenuItem 878, 890, 893, 894, 895 JNLP, Java NetWork Launch Protocol 906

Indice 2593 join(), hebras 748 JOptionPane 888 Joy, Bill 49 JPanel 877, 896, 917 JPopupMenu 894 JProgressBar 904 JRadioButton 878, 884 JScrollPane 865, 887 JSlider 904 JTabbedPane 887 JTextArea 864 JTextField 863, 880 JTextPane 882 JToggleButton 877 JUnit, problemas con 709 JVM (Java Virtual Machine) - 353

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 L1FO (last-in, firstout) 256 ligero: objeto 252 lmites: y referencias Class 360 en genricos 418,431 superclase y referencias Class 361 tipos genricos autolimitados 450 limpieza: con finally 296

realizacin 98

verificar la condicin de terminacin con finalize() 98 y depurador de memoria 148 LineNumberlnputStrcam 599 I.ineNumbcrReader - 602 LinkedBlockingQueue 796

Indice 2594 LinkedlIashMap 542, 545. 571 LinkedHashSct 25S, 533, 567, 569 LinkedList 249, 255,263, 530 List 241, 244, 249, 530 comparacin de rendimiento 561 ordenaciones y bsqueda -575 lista: cuadro de 886 desplegable 885 listas variables de argumentos (varargs) 113, 469 y mtodos genricos 405 Lister, Timothy - 961 Listlterator 530 literales: de clase 357, 367 double 53 float 53 long 53 valores * 53 local: clase interna -217 variable 29 lock explcito, objeto 758 logaritmo natural 54 long: y hebras - 760 valor literal (L) - 53 LRU (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 MappedByteBuffcr 629 maqueta, objeto 387 mquinas de estado con enumeraciones 680

marcadora, anotacin 694 marco de trabajo de una aplicacin * 231 marcos de control y clases internas 230 mark( ) 603

ndice 2595 matcher, expresin regular 336 matrices: asociativa 244 244. 540 comparacin con contenedor 484 comparacin de 503 comprobacin de limites 111copia en 502 covariante 434 de objetos 485 de objetos genricos 552 de primitivas 485 desigual 488

devolucin de una matriz 487 inicializacin * LIO

inicializacin agregada dinmica 486 lterablc 269 length 111,485 multidimensional 488 objetos de primera clase 485 mayor o igual que (>=) 49 mayor que (>) 49 menor o igual que (<=) 49 menor que (<) 49 mensaje, envo de 3 Mensajero * 396, 516, 559 mens:

JDialog, JApplet, JFrame 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,

ndice 2596 843

mtodos:

acoplamiento de las llamadas a 168 adicin de ms mtodos al diseo 137 aplicacin de un mtodo a una secuencia 469 clases internas en mbitos y 217 comportamiento de los mtodos poli- mrficos entre de constructores - 181

creacin de alias en las llamadas a 46 distincin entre mtodos sobrecargados 88

final - 159, 168, 182 genricos 403

inicializacin do variables de 102

ndice 2597 llamadas a mtodos en lnea 159

polimorfismo 165

prvate 173,

protected 153

recursivos 322

sobrecarga 87

ndice 2598 static 96

sustitucin de mtodos prvate 173 Meyer, Jeremy 693,721, 906 Meyers, Scott 5

tnicropruebas de rendimiento 566, 835 Microsoft Visual BASIC -918 miembro:

de datos 4

inicializadores de 177 objeto 6

migracin, compatibilidad de la 419 mixin 458 mkdirs() 596

ndice 2599 mnemnicos (atajos de teclado) 894 mdulo - 46

monitor, para concurrencia 756 Mono 20

montaje de clases 357 multidi fusin 926 multidimensional, matriz 488 multiparadigma, programacin 2 multiplicacin - 46 multitarea 729

MXML, Macromedia Flex formato de entrada 932 mxmtc. Macromedia Flex compilador 933

net.mindview.util.SwingConsole - 861 Neville, Sean 932 new, E/S - 616 new, operador 97 y matrices -111 ne\vlnstance( ) 356. 878 reflexin 356 next(). Iterator 253 nio 616 buffet' -616

ndice 2600 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 notifyAll() 784 notifyListeners() - 929 nuil 26

NulIPointerException 294 O

Object: clase raz estndar 142 herencia 142 ObjectOutputStream 639 objeto - 2 activo 850 alcanzable 578

asignacin de objetos por copia de referencias 45

bloqueo, para concurrencia 756

ndice 2601 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

ndice 2602 equivalente (=) 49 final 156 getClass () 354 hashCode() 541 informacin de tipos 351 interfaz de un 3

matrices son objetos de primera clase 485 miembro 6

proceso de creacin del 107 red de objetos 639 serializacin 639 wait) y notifyAll() -784 Objeto nulo, patrn de diseo -381 Octal 53

onda sinusoidal 896 OpenLaszlo, alternativa a F.ex 932 operacin atmica 760 operaciones no soportadas en contenedores 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 uno 55 de desplazamiento 55 de indcxacin [ ] 110 de proyeccin 60 errores comunes 60 lgicos 51

lgicos y cortocircuitos 52 matemticos 46, 633 precedencia 44 relacinales 49 sobrecarga de 59 sobrecarga de operador para String 318

ndice 2603 String, conversin con operador + 44, 59

temario - 58 uar i os 48, 55 OR: bit a bit 55, 60 lgico (||) 51 orden:

de inicializacin lo5, 162. 182 de llamadas a constructoras 176

ordenacin 504 alfabtica 260 lexicogrfica 260 y bsquedas en listas 575 ordinal(), para enum 660 organizacin del cdigo 128 OSExecute *615 OutpulSlream 596, 598 OutputStreamWriter 600, 601 P

paintComponentf) 896.900 paquete (package) 122 acceso de 221 acceso de, y protected 153 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

ndice 2604 basado en comandos 235, 384, 672, 734

basado en estados 184

basado en estrategia 196. 203.474.

494, 504, 588, 593, 676,811 cadena de responsabilidad 676 Decorador 461 Itcrador -214, 252 Iterador nulo 381 mtodo basado en adaptadores 270 mtodo de factora 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

ndice 2605 nulo 381 Peso mosca -519, 854 Proxy 378 Singleton 136 solitario 136 Visitante 706 pattem, expresiones regulares 333 persistencia 649 ligera 639 Peso mosca, patrn de diseo -519. 854 PhantomRefercnce 578 Piedra, papel, tijera 685 pila 255,256, 398, 582 PipedlnputStream - 597 PipedOutputStream 597, 598 PipedReader 601, 800 PipedWriter -601. 800 planificador de hebras 732 plantillas C++ 394, 417

Plaugcr, P.J. 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 postdecrcmento 48 postfija 48 post-incremento 48 pre-decrcmento 48

ndice 2606 predeterminado, constructor- 92, 144, 377 predeterminado, paquete - 122, 130 prediccin, operadores de. 60 preferences, API 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 operadores 62 printf( ) 324

printStackTrace( ) 287, 289 PrintStream 600 PrintWriter - 602, 605, 606 constructor en Java SE5 -610 prioridad, en concurrencia 738 PriorityBlockingQueuc, concurrencia

811

ndice 2607 PriorityQucue 264. 537 prvate 5, 121, 128, 130, 153, 159, 756 clases internas 232 interfaces anidadas 207 sustitucin de mtodos 173 proceso concurrente 729 ProcessBuilder -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).

Vese POO programacin visual -918 entornos de 858 programador de clientes 5 promocin a int 62, 70 PropertyChangeEvcnt 931 PropertyDescriptors 922 propiedades: hoja de propiedades personalizada 931 indexadas 931 restringidas 931 ProptertyVeloException 931 protected-5. 121, 128, 131, 153 y acceso de paquete 131, 153 protocol 192

Protocolo Java de inicio a travs de red (JNLP) 906 Proxy, patrn de diseo 378 proxy: y java.lang.ref.Reference 579 para mtodos no modificabas de la clase Collections 530 proyeccin 11 asSubclass() - 361 mediante una clase genrica 448 y tipos genricos 447 y tipos primitivos 70 public-5, 121, 128, 128 clase, y unidades de compilacin 122 e interface 192 puntero, exclusin de punteros en Java 229

ndice 2608 PushbacklnputStream 599 PushbackRcader 602 Python 1, 5,9, 18, 22, 464, 510, 729, 962 queue 241,255,263, 537, 796 rendimiento 561 R

RAD (Rapid Application Development) 375

random() 260

Random Access, interfaz de marcado para contenedores 275 RandoraAccessFile 602, 603,608, 616 read() 596 nio 616 readDouble() 608 Reader 596, 600, 601 rcadExtemal() 643 readLine() 305, 602, 606, 613 readObject) 639 con Serializable 647 ReadWriteLock 848 reanudacin en el tratamiento de excepciones 281 recuadros de mensaje en Swing 888 recuento de referencias, depurador de memoria - 100 recursin no intencionada con toString() 321

red de objetos - 639 red, E/S de 616

ndice 2609 redireccionamiento de la E/S estndar 613 rediseo 121

RcentrantLock 759, 781 Reference de java.lang.ref 578 referencia: asignacin de objetos por copia de referencias 45 equivalencia de referencias y equivalencia de objetos 50 final 156 hallar el tipo exacto de referencia base 352 nuil 26 referencias Class genricas 359 reflexin 375, 387, 870,920 diferencia entre RTTI 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 final 161 reset() 603

ndice 2610 respuesta rpida, interfaces de usuario de 750 resta 46

resume( ) e interbloqueos 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 finaily 299 reutilizacin 6 cdigo reutilizable -918 de cdigo 139 re\vind( ) - 620

RTTI (runtime type information) 186, 351 Class, objeto 353, 878 ClassCastException 362 Constructor, clase 375 Field 375

ndice 2611 getConstructor() 878 instanceof, palabra clave 362

islnstance() 368 Method 375 newlnstance<) 878 reflexin 375 shape, ejemplo 351 Rumbaugh, James 960 RuntimeException 294, 313 rutina de tratamiento de excepciones, 280 rvalor 44

salto incondicional 76 ScheduledExecutor, para concurrencia 814

seccin critica 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

ndice 2612 formacin proporcionada por MindView, Inc. 955 seales perdidas, concurrencia 788 separacin de la interfaz y la implementa- cin 5, 133, 869 SequencelnputStream 597, 602 Serializablc 639. 643, 646, 653. 926. Vase tambin serializacin readObject() 647 \vriteObject() 647 serializacin: control en la 642 defaultReadObject() 648 defaultWriteObject() 647 Versionado 649

y almacenamiento de objeto 649 y transient 6-16 Set-241, 244.258, 533 comparacin de rendimiento 567 relaciones matemticas 409 sctActionCommand() 894 setBordcr() 881 setErr(PrintStream) -614 setIcon() 879 setln(InputStream) 614 set.ayout() 866 setMnemonic() 894 sctOut(PrintStream) -614 setToolTipText() * 880 shufle{) 576 signatura del mtodo 30 Simula-67, lenguaje de programacin 3 simulacin 821 SingleTlireadExecutor 735 size( ), ArrayList 242 sizeofi[), no existe en Java 62 slccp(), en concurrencia 737 Smalltalk 2

sobrecarga: de constructores 87 de los valores de retomo 92 de mtodos - 87 de operadores 59

distincin entre mtodos sobrecargados -88

genricos 449

ndice 2613 ocultacin de nombres durante la herencia 151 operadores + y += para String 142, 318

SocketChannel 632 SoftReference - 578

Software Development Conference xxiii solicitud * 3

Solitario (singleton), patrn de diseo 136

SortedMap 544

SortedSet 536

ndice 2614 South. BorderLayout 866

split( ), String 196, 332

sprint {( ) 329

SQL generado mediante anotaciones 698 stateChanged() 897 static: palabra clave 32, 192 clases internas 224 inicializacin 163,354 mtodo 96, stop() e interbloqueos 775 StreamTokenizer 602 String:

CASEINSENS1TIVEORDER - 575 concatenacin con el operador +** 59 conversin con operador - 44,59 expresiones regulares 331 format() 329 indexOfl ) 377 inmutabilidad 317 mtodos 317, 322 operadores + y += para 142 ordenacin lexicogrfica y alfabtica 507

ndice 2615 ordenacin. CASE INSENSITIVE ORDER 588 ~ split(), mtodo 196 toString() 140 StringBufer 597 StringBufferlnputStream 597 StringBuilder, String y toString( ) 318 StringReader 601, 604 StringWriter 601 Stroustrup, Bjame 119 Stub 387 subobjeto 143 sucesos:

JavaBeans -918 modelo de Swing 869 multidifsin y JavaBeans 927 programacin dirigida por 862

respuesta a un suceso Swing 862 sistema dirigido por sucesos 231 y escuchas - 869 sugerencias 880 suma 46 super; palabra clave 143 y clases internas 236 superclase 143 limites 361 supertipo, comodines de 438 suplantacin en la POO 2 suspend() e interbloqueos 775 sustitucin 9: de mtodos prvate 173 y clases internas 236 y sobrecarga -151 sustitucin: herencia y extensin 184 principio de 9 pura -9, 185 SWF, formato de cdigo intermedio Flash 932 Swing 857 componentes 876 HTML en los componentes 902 modelo de sucesos 869 y concurrencia *910 switch: palabra clave 81 y cnum 662 synchromzed: contenedores 577

decidir qu mtodos sincronizar 929 esttico 757

Regla de Brian de la sincronizacin 757 seccin crtica y bloque 765 waitf) y notifyAll( ) 784 y herencia 929 SynchronousQueue, concurrencia 826 System.arraycopy() 502 System.err 282, 613 System.in 613 Systcm.out -613

ndice 2616 systemNodeForPackage(), API preferen- ces 657

tabla de base de datos, SQL generado mediante anotaciones 698 tamao de un HashMap o HashSet 571 tareas y hebras, terminologa - 748 teclado: navegacin y Swing 858 atajo de 894

Teora del compromiso delegado 751 terminacin: alta (big endian) 624 baja (little endian) 624 condicin de, y finalize() 98

en el tratamiento de excepciones 280 temario, operador 58 this, palabra clave- 93 ThreadFactory personalizada 741 throw. palabra clave- 280 Throwable. clase base para Exception 287 tiene-un, relacin - 6, 153 Timellnit 738, 811 tipos: base 7

ndice 2617 comprobacin de 309, 392 derivado 7 enumerados 117 estructurales - 464, 472 genricos y contenedores seguros respecto 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

ndice 2618 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 operadores * 62 toArray ) 571

TooManyListenersException 926 toString() 140 directrices para usar StringBuilder - 319 transferFrom() 618 transfcrTo() 618 transient, palabra clave* 646 TreeMap 542, 544, 571 TreeSet 258, 533, 536, 567 true 51

try, bloque 150, 280, 297 en excepciones 280 tryLock(), bloqueo de archivos 632 tupia - 396, 408,413

ndice 2619 TYPE, campo para literales de clases primitivas 357

UML(Unified Modeling Language) 4, 6, 960

unario, operador: ms (+) 48 menos (-) 48 UncaughtExceptionHandler, clase Thread 752 Unicode 601

unidad de compilacin 122 unidad de traduccin * 122 unidi fusin - 926

Unified Modeling Language (UML) 4, 960

ndice 2620 unmodiiableList(), Collections 530 UnsupportcdOperationException 530 upcasting. Vase generalizacin userNodeForPackage(), API preferences 657

Utilidades de java.util.Collections 572 V

values() para enumeraciones 659. 663 Varga, Ervin 7, 780 variable: definicin 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

ndice 2621 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 nio 618 writeBytes( ) 607 writeChars() 607 writeDouble() 607 writeExtemal() 643 writeObject() 639 con Serializable 646 Writer 596, 600, 601

XDoclet 693 XML-654

ndice 2622 XOM, biblioteca XML 654 XOR (Exclusive-OR) 55

ndice 2623

ZipEntry 637 ZipInputStrcam 634 ZipOutputStream 634Realmente, esto es poco restrictivo, ya que pueden existir objetos en diferentes mquinas y espacios de direcciones, y tambin se pueden almacenar en disco. En estos casos, debe determinarse la identidad del objeto mediante alguna otra cosa que la direccin de memoria. 3 Los mtodos de Upo static. do los que pronto hnbluremov pueden invocarse para la clase, ai lugar de para un objeto especifico. 1 C unsulle Rcfiu-taiin^ Improvlitg the k'Hian of Existlnx CIHJC. Uc Martin Fowlcr. ct til (Adison- VV'cslcy, 1999). Ocasionalmente. alyumu personas argumentaran en contra de las tarcas de rediseo, sugiriendo que un cdigo que ya funciona es perfectamente adecuado, por |a que resulta una pniidu de tiempo tratar de redtseflarlo K! problema con esta forma de pensar e* que la parte del len en lo que se refiere al tiempo y al dinero consumido* por un proyecto no cst* en la escritura inicial del cdigo, sino en iu mantenimiento Hacer el codigo ms fcil de entender permite ahorrar una gran caniitlad de dinero. 1 meios a Martin Damier por haccr una pregunta a este respecto en un semmano 1 rrniovet ) es un miento "opcional" (existen oros mtodos lambien opcionales), lo que significa que no odas las implemeniaciones de llcrntor deben implemeniurlo. Hsie tema se trota en et Capitulo 17. struillsh ttcal/ado >ic los contenedores. Los contenedores de la biblioteca estndar de Java si implemcniau el mtodo removet ). por lo que no es necesario preocuparse de este tema hasta que lleguemos a este capitulo. 4 Un destructor cs una funcin que siempre se invoca cuando un objeto deja de ser utilizado Siempre sabemos exactamente donde y cuando se invoca a! hltft. ,/hcuxs.devtiJop. ctuufatxbiwywa.AllindimilAJLL =DOTNET&P=Ri2$2H

9 t ipiiorr tlundlim; m ( Li . Liskov S SnyJcr i'Editimi /AdJison-WcsIcy. I997i. j>. 3?t*.

" Bjumc Sironstnip, 7ht> f 1 Projirttmnthij

LH^U^I-,

111 texto indiatili cs una cita ik un dc lax discurso* del Comandante Taggart cn Galaxy Quest 1 Quiem il.tr las grada* a Angeltka Langcr por .mi ha Li de preguntat fmucntei Jiivn Gvtcrivx FAQ ( scuse inni 'Mvtger.camefat./. *1 conio por su> otrov CM*rito\ (liechos ci colutxiracion con Klau- Krcltl EstM irubajo> han nriullado enormemente vallo ile cara u la preparacin ile c*tc tapinilo * O clu*c% que dispongan de un constructor privado Dado que se pueden utilizar proyecciones de tipo, que deshabiluan en la practica el sistema de npos. algunos autores argumentan qtc Cf* es un lenguaje dbilmente upado, pero creo que esa opinin es exagerada Probablemente sea mas justo decir que Ot es un lenguaje luenemenie tipado" con una puerta trasera. * i'.u realidad. un nmero primo hi e* en la practica el turnado ideal para lo segmento* laixh. y la* implementacioncs tuuh ms reviente* en Jova utilizan un tnmnflc tguul .i las potencias de do*, (despus de habet realizado pruebas exhaustiva). La divisin o el clculo del icvtu e% la operacin r.w* lenta en un procesador moderno C on una longitud de la tabla hash igual a una potencia de dos. -ve puede utilizar una operacin de enmascaramiento en lugar de la de divisin. Pliego que getl e>. con mucho, la operaaon mas comn. i represento una gran pane del coste y la solucin basada en una potencia de dos elimino este coste (aunque puede que tambin afecte a algunos mtodos hash< odc( )> I n el diserto original. se -vupona que close( ) deb. ser invocado cuando se ejecutara finali/e( i s tendr ocasin de *er en algunos textos que linali/t-( ) 1XML es otra forma de resolver el problema de trasmitir datos de una plataforma a otra, y no depende de si se dispone de Java en toda In* plataformas 4 Bic ejemplo hn estado utilizndose desde hace muchos aos tamo en C+-* como en Javo (en ThlnkJrig irt Paiicmx) en hw.kfimIView.net, ante* de aparecer. sin citar la fuente en un libro esento por otros autores 5 La palabra mirror significa espejo. 5 que se iraia de un juego de polubras de los diseadores Java para hacer referencia en realidad al mecanismo de reflexin. 10 En honor de Brian Goetz, autor dc Java Concurrency in Practice, por Brian Goetz, Tim Pcicrls, Joshua Bloch, Joseph Bowbeer, David Holmes y Doug Lea (Addison*Wesley, 2006). IS Vese Design Patients, por Gamma at al. (Addison-Wesley, 1995). 21 Tambin podemos lencr lo que se denomina bloqueo activo (livelock) cuando hay dos tareas

ndice 2624

que son capaces de cambiar su estado (no estn bloqueadas), pero nunca consiguen realizar ningn progreso til. 1 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 GUI de cdigo abierto para su editor Eclipse (ww. Eclipse,org). que podemos evaluar como alternativa de Swing. Presentaremos esta alternativa posteriormente en el captulo.

Vous aimerez peut-être aussi