Thinking in Java, 2nd Edition, Release 11

To be published by Prentice-Hall mid-June, 2000

Bruce Eckel, President,
MindView, Inc.

Planet PDF brings you the Portable Document Format (PDF) version of Thinking in Java (2nd Edition). Planet PDF is the premier PDF-related site on the web. There is news, software, white papers, interviews, product reviews, Web links, code samples, a forum, and regular articles by many of the most prominent and respected PDF experts in the world. Visit our sites for more detail:
http://www.planetpdf.com/ http://www.codecuts.com/ http://www.pdfforum.com/ http://www.pdfstore.com/

Thinking in Java
Second Edition

Bruce Eckel
President, MindView, Inc.

Traducido del inglés por Esteban Cabezudo (esteban@cabezudo.com.uy estebanc@adinet.com.uy) y Lorena Baldizoni de Thinking in Java, Second Edition, Bruce Eckel, Prentice Hall. 2003. Versión 1.00

Nota del traductor
Inicialmente comencé a leer este libro motivado por mi utópico afán de aprender todo acerca de las computadoras, la programación orientada a objetos y en especial Java. La primera vez que obtuve Thinking in Java en la Internet me dio mucha alegría saber que existía un libro de esta calidad al alcance de todos. Comencé sacando algunas notas en español y ya tarde, en el tercer capítulo me di cuenta que tratando de no saltarme las palabras que no conocía estaba traduciendo la totalidad del texto. Tengo que destacar que ni siquiera me acerco a ser un traductor profesional, no estudié formalmente inglés y la mayor parte de este, es producto de la lectura de libros y documentación. Por esta razón se puede notar una sintaxis a veces extraña que trataré de corregir a medida que lo note o me lo hagan notar. También se puede ver un cambio en la traducción a partir del tercer capítulo, tal vez para peor ya que comencé el trabajo ahí y lo terminé en el segundo, espero haber mejorado a medida que avanzaba. Tengo que agradecer a Bruce Eckel, primero por escribir este libro y segundo por dejarme publicar la traducción en mi sitio sin siquiera conocerme. Esta actitud maravillosa me dio mucha alegría y me hizo sentir muy útil. También quiero agradecer a mi compañera por soportarme estos siete meses sentado durante horas en la madrugada a la luz del monitor y por ayudarme incondicionalmente. Ella es la que realmente estudió inglés y si este trabajo tiene una garantía de calidad, es gracias a su aporte. Lorena aprendí mucho con tu ayuda y seguramente tu aprendiste mucho de Java. Pido perdón por la tortura. A mi hermano Facundo por pedirme la traducción cada vez que me veía, me hacía recordar que hay gente que no lee inglés y que también está interesado en Java. A Gabriel Claramunt por ayudarme con algunos conceptos, por darme algunas ideas de líneas a seguir y por prestarme el notebook para escribir esto. Quiero que este trabajo pueda ser aprovechado por las personas que no puedan leer inglés y me gustaría que las personas que si lo hacen y tienen comentarios o correcciones me lo hagan saber. Soy consciente de que hay algunas traducciones que tal vez no sean las mas acertadas. Espero que este trabajo les sea útil. Esteban Cabezudo

Prefacio
Le he sugerido a mi hermano Todd, que esta haciendo el salto desde el hardware a la programación, que la siguiente gran revolución será en ingeniería genética.
Tendremos microbios diseñados para hacer comida, combustible, y plástico; ellos limpiarán la polución y en general nos permitirán dominar la manipulación del mundo por una fracción de lo que cuesta ahora. Afirmo que harán que la revolución de las computadoras se vea pequeña en comparación. Entonces afirmo que estaba cometiendo un error común de los escritores de ciencia ficción: perdiéndome en la tecnología (lo cual es fácil de hacer en la ciencia ficción). Un escritor experimentado sabe que la historia nunca es acerca de las cosas; es acerca de las personas. La genética tendrá un gran impacto en nuestras vidas, pero no estoy seguro de que dejara pequeña la revolución de las computadoras (que habilita la revolución genética) -o al menos la revolución de la información. Información es acerca de hablar con cada uno de los otros: es cierto, los autos, zapatos y la cura genética especialmente son importantes, pero en el final estos son solo atavíos. Lo que realmente importa es como reaccionamos a el mundo. Y mucho de esto es acerca de comunicaciones. Este libro es un ejemplo que hace al caso. La mayor parte de las personas que piensan que es muy atrevido o un poco loco colocar la totalidad de las cosas en la Web. Se preguntan: “Por que alguien querría comprarlo?”. Si tuviera una naturaleza mas conservadora nunca lo habría terminado, pero en realidad nunca quise escribir otro libro de computadoras en la manera antigua. No sabía que sucedería, pero lo he convertido en algo mas inteligente que cualquier cosa que haya hecho con un libro. Por algo, las personas comenzaron a enviar sus correcciones. Esto ha sido un proceso asombroso, porque las personas han observado en cada esquina y grieta para ver errores técnicos y gramaticales, y han sido capaces de eliminar errores de todos los tipos que se que de otra forma los hubiera pasado por alto. Las personas han sido simplemente estupendas con esto, muy a menudo diciendo “Ahora, no trato de decir que es en una forma crítica...” y dan un grupo de errores que estoy seguro nunca hubiera encontrado. Siento como que esto ha sido un tipo de proceso en grupo y realmente han hecho este libro algo especial.

1

Pero luego que he comenzado a escuchar “OK, bien, es bueno que hayas puesto una versión electrónica, pero quiero una versión impresa y saltar a una copia publicada realmente por una editorial”. He tratado mucho de hacerlo fácil para que cualquiera pueda imprimirlo en un formato que se vea bien pero eso no ha disminuido la demanda del libro publicado en una editorial. Muchas personas no quieren leer un libro entero en la pantalla, y tiran con fuerza hacia el fajo de papeles, no importa si están impresos de forma bonita, no les interesa eso (Además, pienso que no es tan económico en términos de tinta para impresoras). Parece que la revolución de las computadoras no han colocados las editoriales fuera de sus negocios después de todo. Sin embargo, un estudiante sugirió que puede ser un modelo para futuras publicaciones: los libros pueden ser publicados en la Web primero, y solo si hay suficientes garantías de interés el libro se colocaría en papel. Actualmente la mayoría de los libros tienen problemas financieros, y tal vez esta nueva aproximación pueda hacer la industria de las editoriales mas provechosa. Este libro comienza una experiencia iluminadora para mi en otra forma. Originalmente he utilizado la estrategia de que Java es “solo otro lenguaje de programación”, lo que en muchos sentidos es cierto. Pero a medida que el tiempo ha pasado y lo he estudiado mas en profundidad, comienzo a ver que las intenciones fundamentales de este lenguaje es diferente de todos los otros lenguajes que he visto. Programar es acerca de manejar complejidad: la complejidad del problema que se intenta resolver, trazado sobre la complejidad de la máquina en la cual esta resuelto. Dado esta complejidad, muchos de los proyectos de programación fallan. Y con todo, de todos los lenguajes de programación de los cuales soy conciente, ninguno de ellos se ha escapado totalmente y decidido que su objetivo de diseño principal será conquistar la complejidad del desarrollo y el mantenimiento de programas1 . Claro, muchas decisiones de diseño de lenguajes fueron echas con la complejidad en mente, pero en algún punto otros temas siempre se consideran esenciales agregarlos dentro de la mezcla. Inevitablemente, estos otros temas son los que causan a los programadores eventualmente “intentarlo” con ese lenguaje. Por ejemplo, C++ cuenta con compatibilidad con sus versiones anteriores de C (para permitirles a los programadores C una fácil migración), al igual que la eficiencia. Estos son objetivos muy útiles y cuentan mucho para el éxito de C++, pero también exponen complejidad extra que impiden que algunos proyectos sean terminados (ciertamente, se puede culpar a los programadores y a la dirección, pero si un lenguaje puede ayudarlo a encontrar sus errores, por que no debería hacerlo?). Otro ejemplo, Visual Basic (VB) fue prendido a BASIC, que no fue diseñado para ser un lenguaje
1

He retomado esto en la 2da edición: Creo que el lenguaje Python de acerca mas ha hacer exactamente eso. Vea www.Python.org

2

Pensando en Java

www.BruceEckel.com

extensible, así es que todas las extensiones apiladas sobre VB han producido sintaxis verdaderamente horribles e imposibles de mantener. Perl es compatible con Awk, Sed, Grep y otras herramientas UNIX que intenta reemplazar, y como resultado es a menudo acusado de producir “código de solo escritura” (esto es, luego de un par de meses no se puede leer). Por el otro lado, C++, VB, Perl y otros lenguajes como Smalltalk tienen algo en sus esfuerzos de diseño enfocado en el tema de la complejidad y como resultado son notablemente útiles para solucionar ciertos tipos de problemas. Lo que mas me ha impresionado mas que comenzar a entender Java es que parece un objetivo resuelto a reducir la complejidad para el programador . Como si dijera “no nos importa nada excepto reducir el tiempo y la dificultad de producir un código robusto”. En los primeros días, este objetivo ha resultado en un código que no ejecuta muy rápido (a pesar de que tienen muchas promesas hechas acerca de como correrá de rápido Java algún día) pero ciertamente ha producido reducciones asombrosas en el tiempo de desarrollo; mas o menos el tiempo que toma crear un programa equivalente en C++. Este resultado aislado puede economizar cantidades increíbles de tiempo y dinero, pero Java no se detiene ahí. Continua envolviendo todas las tareas complejas que son importantes, como la multitarea y la programación en redes, en características o librerías que pueden en determinados momentos triviales algunas tareas. Y finalmente, aborda algunos problemas realmente complejos: programas de plataforma múltiples, cambios dinámico de código e inclusive seguridad, cada uno de los cuales pueden encajar dentro de un espectro de complejidad en cualquier parte desde el “impedimento” hasta el “éxito rotundo”. Así es que a pesar de los problemas de rendimiento que se ha visto, la promesa de Java es tremenda: puede hacer a los programadores significativamente mas productivos. Uno de los lugares en que he visto el gran impacto de esto es en la Web. La programación en la red ha sido siempre dura, y Java lo hace fácil (y los diseñadores de Java están trabajando para hacerlo aún mas fácil). La programación de redes es como hablamos con toros mas eficientemente y mas barato de lo que lo hacemos con teléfonos (el correo electrónico solo ha revolucionado muchos negocios). Como hablamos con otros mas, cosas asombrosas comienzan a suceder, posiblemente mas asombrosas que la promesa de la ingeniería genética. En todas las formas -la creación de programas, trabajo en equipo para crear los programas, el crear interfases de usuario así los programas pueden comunicarse con el usuario, ejecutar los programas en distintos tipos de máquinas, y fácilmente escribir programas que se comuniquen a través de la Internet-Java incrementa el ancho de banda de las comunicaciones entre las personas. Pienso que quizás los resultados de la revolución en las comunicaciones no serán vistos por los efectos de mover grandes cantidades

3

de bits alrededor; veremos que la verdadera revolución será porque seremos capaces de hablar con otros mas fácilmente: uno por uno, pero también en grupos y, como un planeta. He escuchado sugerencias que la siguiente revolución es la formación de un tipo de mente global que resulta de suficientes personas y suficientes interconexiones. Java puede o puede no ser la herramienta que fomente esa revolución, pero al menos la posibilidad me ha hecho sentir como que estoy haciendo algo significante tratando de enseñar el lenguaje.

Prefacio de la 2da edición
Las personas han hecho muchos, muchos maravillosos comentarios acerca de la primera edición de este libro, lo que naturalmente ha sido muy placentero para mi. Sin embargo, de vez en cuando alguien tiene quejas, y por alguna razón una queja que comienza a ser periódica es que “el libro es muy grande”. En mi mente los daños son pequeños si su queja es simplemente “son muchas páginas” (Uno es recordando del Emperador de Austria quejándose del trabajo de Mozart: “Demasiadas notas!”. De ninguna forma me estoy tratando de comparar a mi mismo con Mozart). Además, solo puedo asumirlo como una queja que viene de alguien que es todavía no se ha puesto al corriente con la inmensidad del lenguaje Java a si mismo, y no ha visto el resto de los libros acerca del tema -por ejemplo, mi referencia favorita es Cay Horstmann & Gary Cornell’s Core Java (Prentice-Hall), que creció tanto que tienen que dividirlo en dos volúmenes. A pesar de esto, una de las cosas que he intentado hacer en esta edición es cortar un poco las partes que se hacen obsoletas, o al menos no esenciales. Me siento confortable haciendo esto porque el material original queda en el sitio Web y el CD ROM que acompaña este libro, en la forma de descarga libre de la primera edición del libro (en www.BruceEckel.com). Si quiere las cosas viejas, seguirán allí, y esto es un gran alivio para el autor. Por ejemplo, puede verse que el último capítulo original, “Proyectos”, no esta mas aquí; dos de los proyectos han sido integrados en otros capítulos, y el resto no seguían siendo apropiados. También, el capítulo de “Patrones de diseños” se ha hecho muy grande y ha sido movido a un libro acerca de ellos mismos (también en forma de descarga libre en el sitio Web). Así es que, para que estén todos contentos el libro será mas pequeño. Pero que pena, esto no puede ser. El gran tema es el continuo desarrollo del propio lenguaje Java, y en particular las APIs expandidas que prometen proporcionar interfases estándar para a penas todo lo que se quiera hacer (y no me sorprenderé si vemos la API “JTostadora” eventualmente aparecer). Cubrir todas estas APIs es obvio ir mas allá del alcance de este libro y es una tarea delegada a otros autores, pero algunos temas no pueden ser ignorados. El mas grande

4

Pensando en Java

www.BruceEckel.com

de estos incluye Java del lado del servidor (en un inicio Servlets & Java Server pages, o JSPs), lo que es verdaderamente una excelente solución para el problema de la WWW, en donde descubrimos que las diferentes plataformas son solo no suficientemente consistentes para soportar programación. Además, esta es la totalidad del problema de crear fácilmente aplicaciones para interactuar con bases de datos, transacciones, seguridad, y parecidos, lo que le compete a Enterprise Java Beans (EJBs). Estos temas son envueltos en el capitulo antiguamente llamado “Programación de redes” y ahora lo llamamos “Computación distribuida” un tema que comienza a ser esencial para todos. Pero encontraremos también en este capítulo ha sido ampliado para incluir un vistazo de Jini (pronunciado como “genie”, y no es tan acrónimo, solo un nombre), que es una tecnología cutting-edge que nos permite cambiar la forma en que pensamos acerca de las aplicaciones interconectadas. Y por su puesto el libro ha sido cambiado para utilizar las librerías GUI Swing en su totalidad. Nuevamente, si se quiere las viejas cosas de Java 1.0 y 1.1 se pueden obtener descargando libremente el libro en www.BruceEckel.com (es también incluido en esta nueva edición del CD ROM, que se encuentra dentro del libro; mas de esto mas tarde). Dejando de lado las pequeñas características adicionales agregadas en Java 2 y las correcciones hechas en todo este libro, los cambios mayores son en el capítulo de las colecciones (9), que ahora se enfoca en las colecciones de Java 2 utilizadas a través de todo este libro. He mejorado también ese capítulo para ser mas profundo en algo tan importante como las colecciones, en particular como una función hash funciona (así es que se puede saber como crear una propiamente). Hay ahí otros movimientos y cambios, incluyendo el capitulo 1 que fue reescrito, y se ha quitado algunos apéndices y otro material que he considerado que ya no era necesario para la versión impresa del libro, pero aquellos que forman parte de la gran masa de ellos. En general, he experimentado ir por todo, quitando de la 2da edición lo que ya no era necesario (pero lo que sigue siendo existiendo en la primera edición electrónica), incluido cambios, y mejoras en todo lo que he podido. Mientras el lenguaje siga cambiando -a pesar de que no sea en el paso precipitado de antes- no hay duda de que habrá futuras ediciones de este libro. Para aquellos de ustedes que siguen no pudiendo soportar el tamaño de este libro. Me disculpo. Lo crea o no, he trabajado muy duro para mantenerlo pequeño. A pesar del tamaño, siento como que hay suficientes alternativas que pueden satisfacerlo. Por algo el libro esta disponible electrónicamente (desde el sitio Web, y también en el CD ROM que acompaña este libro), así es que si carga con su portátil puede cargar con este libro sin peso extra. Si realmente prefiere las cosas delgadas, actualmente hay versiones del libro para Palm Pilot por ahí (Una persona me dijo que leía el libro en la cama en su Palm con la luz de fondo encendida para no molestar a su esposa. Solo espero que lo ayude a enviarlo a el país de los sueños). Si lo necesita en

5

papel, conozco personas que imprimen un capítulo por vez y lo cargan en su portafolio para leerlo en el tren.

Java 2
En el momento que se escribió esto, la versión de Sun de Java Development Kit (JDK) 1.3 era inminente, y los cambios propuestos para JDK 1.4 han sido publicitados. A pesar de que los números de versión siguen siendo “unos”, la forma estándar de referirse a cualquier versión del lenguaje que sea mayor que 1.2 es “Java 2”. Esto indica lo significante de los cambios entre “el viejo Java” -que tiene muchas imperfecciones de las cuales me he quejado en la primer edición de este libro- y esta versión mas moderna y mejorada del lenguaje, que tiene menos imperfecciones y muchos agregados y buenos diseños. Este libro está escrito para Java 2. Tengo el gran placer de librarme de todas las cosas viejas y escribir solo lo nuevo, el lenguaje mejorado porque la vieja información sigue existiendo en la 1r a edición electrónica en la Web y en el CD ROM (que es a donde puede ir si esta atascado utilizando una versión previa a la versión 2 del lenguaje). También, dado que cualquiera puede libremente bajar la LDK de java.sun.com, significa que al escribir para Java 2 no estoy imponiendo una dificultad financiera para alguien forzándolo a realizar una actualización. Hay aquí un pequeño problema, sin embargo. JDK 1.3 tiene algunas mejoras que me gustaría realmente utilizar, pero la versión de Java que actualmente esta siendo liberada para Linux es JDK 1.2.2. Linux (vea www.linux.org) es un desarrollo verdaderamente importante en conjunto con Java, dado que se ha convertido rápidamente en la mas importante plataforma de servidores -rápido, confiable, robusta, segura, bien mantenida, y gratis, una verdadera revolución en la historia de la computación (No pienso que hayamos visto todas estas características en una herramienta antes). Y Java ha encontrado un importante nicho en la programación del lado del servidor en la forma de Servlets, una tecnología que es una mejora enorme sobre la programación tradicional CGI (esto es cubierto en el capítulo de “Programación Distribuida”). Así es que a pesar de que queremos utilizar solo las características mas nuevas, es crítico que todo compile bajo Linux, y de esta manera cuando descomprime el fuente y compila bajo ese SO (con el último JDK) descubrirá que todo compilará. Sin embargo, encontrará que he colocado notas acerca de las características en JDK 1.3 por aquí y allí.

6

Pensando en Java

www.BruceEckel.com

El CD ROM
Otra bonificación con esta edición es el CD ROM que es empaquetado atrás del libro. Me he resistido a colocar CD ROMs en la parte de atrás de mis libros porque sentido el cargo extra por unos pocos Kbytes de código fuente en ese enorme CD no se justificaba, prefiriendo en lugar de eso permitirle a la gente bajar ese tipo de cosas de mi sitio Web. Sin embargo, vera que este CD ROM es diferente. El CD no contiene el código fuente del libro, en lugar de eso contiene el libro en su totalidad, en varios formatos. Mi favorito de ellos es el formato HTML, porque es rápido y totalmente indexado -simplemente se hace un clic en una entrada en el índice o en la tabla de contenido y inmediatamente se esta en esa parte del libro. Los 300+ Megabytes del CD, sin embargo, es un curso multimedia completo llamado Thinking in C: Fundations for C++ & Java Originalmente he . enviado a CHuck Allison para crear este seminario en CD ROM como un producto por separado , pero he decidido incluirlo con las segundas ediciones de Thinking in C++ y Thinking in Java porque la experiencia consistente de tener personas que van a los seminarios sin una adecuada base de C. El pensamiento aparentemente se dirige a “son un programador inteligente y no quiero aprender C, pero en lugar de eso C++ o Java, así es que simplemente me salto C y voy directamente a C++/Java”. Luego llegan al seminario, y lentamente se dan cuenta que el requisito previo de entender la sintaxis de C esta ahí por una muy buena razón. Al incluir el CD ROM con el libro, nos aseguramos que todos asisten al seminario con una preparación adecuada. El CD también permite que el libro le agrade a una audiencia mas amplia. A pesar de que el Capítulo 3 (Controlando el flujo de programa) cubre lo fundamental de las partes de Java que vienen de C, el CD es una introducción amable, y asume cada vez menos acerca de la base de programación del estudiante que tienen el libro. Espero que incluyendo el CD mas personas serán capaces de entrar al grupo de programación Java.

7

Introducción
Como un lenguaje humano, Java proporciona una forma de expresar conceptos. Si es exitoso, este medio de expresión será significantemente mas fácil y mas flexible que las alternativas cuando los problemas se hagan mas grandes y complejos. Se puede ver a Java simplemente como una colección de características algunas de las características no tienen sentido de forma aislada. Se puede utilizar la suma de las partes solo si se esta pensando en diseño, no simplemente en codificación. Y para entender Java de esta forma, se debe entender los problemas con este y con la programación en general. Este libro discute problemas de programación, por que son problemas, y el enfoque que toma Java para solucionarlos. De esta manera, el grupo de características que explicaré en cada capítulo esta basado en la forma en que veo que un tipo particular de problema será solucionado con el lenguaje. De esta forma espero moverlos, de a poco, a el punto donde el pensamiento de Java comience a ser su lengua nativa. En todas partes, tomaré la actitud de que se quiere crear un modelo en la cabeza que permita desarrollar un profundo entendimiento del lenguaje; si uno se encuentra con un rompecabezas será capaz de proveer a su modelo y deducir la respuesta.

Requisitos previos
Este libro asume que tiene alguna familiarización con la programación: debe entender que un programa es un grupo de instrucciones, la idea de una subrutina/funcion/macro, instrucciones de control como “if” y constructores de bucles como “while”, etc. Sin embargo, se puede aprender esto en muchos sitios, como programar con un lenguaje de macros o trabajar con una herramienta como Perl. Siembre y cuando usted hay a programado hasta el punto don de usted se sienta confortable con las ideas básicas de la programación, se será capaz de ir a través de este libro. Claro, el libro va a ser mas fácil para lo programadores C y mucho mas para los programadores C++, pero no se sienta fuera si no tiene experiencia con estos lenguajes (pero se debe venir deseoso de trabajar duro; también, el CD multimedia que acompaña este libro dará la velocidad necesaria con la sintaxis básica de C para aprender Java). Voy a introducirlos en los conceptos de la programación orientada a objetos (OOP) y mecanismos de control básicos de Java, así es que seremos expuestos a ellos, y los primeros ejercicios involucran las instrucciones básicos de control de flujo.

8

Pensando en Java

www.BruceEckel.com

A pesar de que se harán a menudo referencias a características de los lenguajes C y C++, no se pretende que sean tomados como comentarios asociados, en lugar de eso ayudará a todos los programadores a colocar a Java en perspectiva con esos lenguajes, desde donde después de todo Java desciende. Intentaré hacer estas referencias simples y explicar todo lo que pienso que un programador C/C++ no este familiarizado.

Aprendiendo Java
En el mismo momento en que mi primer libro Using C++ (Osboprne/McGraw-Hill, 1989) se editó, comencé a enseñar el lenguaje. Enseñar lenguajes de programación se ha convertido mi profesión; he visto cabezas caer por el sueño, caras en blanco y expresiones de perplejidad en audiencias en todo el mundo desde 1989. He comenzado a dar entrenamiento en casa con grupos pequeños de personas, he descubierto algo durante los ejercicios. Aún esas personas que sonríen y cabecean cuando están confusas con tantas características. He encontrado, cargando con la pista de C++ en las Conferencia de Desarrollo de Software por muchos años (y luego con la pista de Java), que yo y otros oradores, tienden a dar a la audiencia muchos temas muy rápido. Así es que eventualmente, a través de la variedad de nivel en la audiencia y la forma en que he presentado el material, terminaré perdiendo alguna parte de la audiencia. Tal vez esté preguntando demasiado, pero dado que soy una de esas personas que se resisten a la lectura tradicional (y para muchas personas, creo, esa resistencia es producida por el aburrimiento), voy a tratar de mantener a todos muy acelerados. Por un tiempo, he creado una gran cantidad de diferentes presentaciones en medianamente poco tiempo. De esta manera, y terminando de aprender por experimentación y repetición (una técnica que trabaja bien también en diseño de programas en Java). Eventualmente he desarrollado un curso utilizando todo lo que he aprendido de mi experiencia enseñando -algo que felizmente he hecho por largo tiempo. Se acomete el problema de aprendizaje en una discreta, pasos fáciles de asimilar, y en un seminario práctico (la situación de aprendizaje ideal) hay ejercicios siguiendo cada uno de las cortas lecciones. Soy ahora este curso en seminarios de Java públicos, que se pueden encontrar en www.BruceEckel.com (El seminario de introducción ésta disponible también en CD ROM. La información esta disponible en el mismo sitio Web). La realimentación que he obtenido de cada seminario me ha ayudado a modificar y a cambiar el enfoque del material hasta que estoy convencido de que trabaja bien como medio de enseñanza. Pero este libro no es solo notas de seminario -He intentado armar un paquete con toda la información que pudiera colocar dentro de estas páginas, y estructurado y tratando de atraer

9

al lector al siguiente tema. Mas que todo, el libro esta diseñado para servir a los lectores solitarios que están luchando con un lenguaje de programación nuevo.

Objetivos
Como mi libro anterior Thinking in C++, este libro vuelve a ser estructurado acerca del proceso de aprendizaje del lenguaje. En particular, mi motivación es crear algo que me proporcione una forma de enseñar el lenguaje en mis propios seminarios. Cuando pienso en un capítulo en el libro, pienso en términos de que hace que una lección sea buena en un seminario. Mi meta es obtener pequeñas piezas que puedan ser enseñadas en cantidades razonables de tiempo, seguido por ejercicios que sean posibles realizarlos en una situación de salón de clase. Mis objetivos en este libro son: 1. 2. Presentar el material de una forma simple así fácilmente se pueda asimilar cada concepto antes de avanzar. Utilizar ejemplos que sean lo mas simples y cortos posibles. Esto a veces me previene de abordar problemas de el “mundo real”, pero he encontrado que las personas que recién comienzan usualmente están felices cuando pueden entender cada detalle de un ejemplo en lugar de impresionarse con el alcance del problema que resuelven. También, hay un limite severo de cantidad de código que pueda ser absorbido en una situación de salón de clase. Por esto no tengo duda de que recibiré críticas por utilizar “ejemplos de juguete”, pero estoy gustoso de aceptar eso en favor de producir algo útil pedagógicamente. Una cuidadosa secuencia de presentación de características así es que no se verán cosas que no se hayan expuesto. Claro que, esto no siempre es posible; en esas situaciones, una breve descripción es dada. Darle al lector lo que pienso que es importante para entender el lenguaje, antes que todo lo que conozco. Creo que es una organización importante de información, y hay algunos cosas que un 95 por ciento de los programadores nunca necesitaran saber y que solo confundirían a las personas y se agregarán a su percepción de la complejidad del lenguaje. Para tomar un ejemplo de C, si se memoriza la tabla de precedencia de operadores (yo nunca lo he hecho), se puede escribir código ingenioso. Pero si se necesita pensar en eso, esto también confundirá la lectura y el mantenimiento del código. Así es que hay que olvidar la precedencia y utilizar paréntesis cuando las cosas no son claras.

3.

4.

10

Pensando en Java

www.BruceEckel.com

5.

Mantener cada sección suficientemente enfocada en tiempos de lectura -y el tiempo entre cada período de ejercicios- pequeño. No solo hace la mente de la audiencia mas activa e involucrada durante el seminario, también da el lector un gran sentido de cumplimiento. Proporcionar fundamentos sólidos así se puede entender los temas lo necesario para moverse adelante en trabajos de clase y libros.

6.

Documentación en línea
El lenguaje Java y las librerías de Sun Microsystems (se pueden bajar libremente) vienen con documentación en forma electrónica, es posible leerla utilizando un navegador, y virtualmente cada tercero que realice una implementación de Java tiene este o un sistema equivalente de documentación. Al menos todos los libros publicados en Java tienen duplicada esta documentación. Así es que se puede tener ya o se puede bajar, y a no ser que sea necesario, este libro no repetirá esta documentación porque es mucho mas rápido si se encuentra las descripciones de las clases con su navegador que si se busca adentro de un libro (y es probable que la documentación sea mas nueva). Este libro proporcionará descripciones extra de las clases solo cuando sea necesario suplementar la documentación así se puede entender un ejemplo en particular.

Capítulos
Este libro fue diseñando con una sola cosa en mente: la forma en que las personas aprenden el lenguaje Java. La realimentación de la audiencia de los seminarios me ayudaron a entender las partes difíciles que necesitan iluminación. En las áreas donde me pongo ambicioso e incluyo muchas características de una vez, me doy cuenta -a través del proceso de presentar el material- que si se incluye un montón de nuevas características, se necesita explicarlas a todas, y esto fácilmente genera la confusión en el estudiante. Como resultado, he tenido una gran cantidad de problemas para introducir estas características de a pocas cuando el tiempo lo permitía. El objetivo, entonces, es enseñar en cada capítulo una sola característica, o un pequeño grupo de características asociadas, sin basarme en características adicionales. Esta forma se pude asimilar cada parte en el contexto del propio conocimiento antes de seguir. He aquí una breve descripción de los capítulos contenidos en este libro, que corresponden a las lecturas y períodos de ejercicio de mis seminarios prácticos.

11

Capítulo 1: Introducción a los objetos Este capítulo es una visión general de todo lo referido a programación orientada a objetos, incluyendo la respuesta a la pregunta básica: “¿Que es un objeto?”, interfase versus implementación, abstracción y encapsulación, mensajes y funciones, herencia y composición y el tan importante polimorfismo. Se tendrá también un vistazo de los temas relacionados con la creación como constructores, donde los objetos existen, donde se colocan una vez creados, y el mágico recolector de basura que limpia los objetos que no se necesitan mas. Otros temas serán introducidos, incluyendo el manejo de error con excepciones, múltiples hilos para interfases con respuesta y trabajo en redes y en la Internet. Así es que se aprenderá lo que hace a Java especial, por que es tan exitoso, y acerca del análisis y diseño orientado a objetos. Capítulo 2: Todo es un objeto Este capítulo se sitúa en el punto en el cual se puede escribir el primer programa Java, así es que da una visión general de lo esencial, incluyendo el concepto de referencia a un objeto; como crear un objeto; una introducción a los tipos primitivos y arreglos; alcance y la forma en que los objetos son destruidos por el recolector de basura; como todo en Java es un nuevo tipo de datos (clase) y como crear clases propias; argumentos de funciones, y valores de retorno; visibilidad de nombres y utilización de componentes de otras librerías; la palabra clave static; y los comentarios y la documentación incrustada. Capítulo 3: Controlando el flujo del programa Este capítulo comienza con todos los operadores que vienen con Java desde C y C++. Además, se descubrirán dificultadas comunes con operadores, conversiones, promociones y precedencia. Luego es seguido, por el control de flujo básico y operaciones de selección que se saben de virtualmente cualquier lenguaje de programación: decisión con if-else; bucles con for y while; salida de un bucle con break y continue de la misma forma que quiebres con etiquetado y continuación con etiquetado (que explica la “perdida del goto” en Java); y selección utilizando switch. A pesar de que mucho de este material tiene cosas en común con el código C y C++, hay algunas diferencia. Además, todos los ejemplos son enteramente en Java así es que el lector se sentirá mas confortable con como se ve Java. Capítulo 4: Inicialización y limpieza

12

Pensando en Java

www.BruceEckel.com

Este capítulo comienza introduciendo el constructor, que garantiza la inicialización apropiada. La definición del constructor cae dentro del concepto de sobrecarga de función (dado que se pueden tener varios constructores). Esto es seguido por una discusión sobre el proceso de limpieza, que no siempre es tan simple como se ve. Normalmente, solo se tira un objeto cuando se ha terminado con el y el recolector de basura eventualmente comienza solo y libera la memoria. Esta porción explora el recolector de basura y alguna de sus idiosincrasia. El capítulo concluye con una mirada cerrada de como las cosas son inicializadas: inicialización automática de miembros, inicialización específica de miembros, el orden de inicialización, inicialización estática y inicialización de arreglos. Capítulo 5: Escondiendo la implementación Este capítulo cubre la forma que el código es empaquetado junto, y porque algunas partes de una librería son expuestas cuando otras partes son ocultadas. Esto comienza viendo las palabras clave package e import, que realizan empaquetado a nivel de ficheros y permiten crear librerías de clases. Luego se examina el tema de las rutas a directorios y nombres de ficheros. El resto del capítulo trata de las palabras clave public , private, y protected, el concepto de acceso “amigable”, y de que significan los diferentes niveles de control de acceso cuando se utilizan en varios contextos. Capítulo 6: Reutilizando clases El concepto de herencia es estándar en virtualmente todos los lenguajes de POO. Esta es la forma en que se toma una clase existente y se agregan cosas a su funcionalidad (de la misma forma que se puede cambiar, el tema del Capítulo 7). Herencia es a menudo la forma de reutilizar código dejando la misma “clase base”, y se corrigen cosas aquí y allí para producir lo que se desea. Sin embargo, herencia no solo es la única forma de crear nuevas clases de clases existentes. Se puede también incrustar un objeto dentro de una nueva clase mediante composición. En este capítulo se aprenderá acerca de estas dos formas de reutilizar código en Java y como aplicarlo. Capítulo 7: Polimorfismo Por cuenta propia, toma nueve meses descubrir y entender polimorfismo, un principio básico de la POO. A través de pequeños y simples ejemplos se verá como crear una familia de tipos con herencia y manipulando objetos de esa familia a través de su clase base. El polimorfismo de Java permite tratar todos los objetos de esa familia de forma genérica, lo que significa que el grueso de su código no confía en un tipo específico de información. Esto hace

13

El inicio. Este capítulo examina como las palabras clave try . en cualquier momento.BruceEckel. se necesita crear cualquier número de objetos. en cualquier lugar. Tanto como sea posible. Capítulo 8: Interfases y clases internas Java proporciona una tercer forma de configurar una relación de reutilización a través de la interfase. Para solución este problema general de programación. lo que es una abstracción pura de la interfases de un objeto. catch. el compilador encuentra problemas. Capítulo 9: Conteniendo los objetos Es un programa bastante simple el que tiene solo una cantidad fija de objetos con tiempos de vida conocidos.sus programas extensibles. La interfase es mas que simplemente una clase abstracta tomada al extremo. throw . Java tiene manejo de excepciones para tratar con cualquier problema que se origine cuando el programa se esté ejecutando. así es que se pueden crear programas y mantener código de forma mas fácil y barata. Este capítulo explora en profundidad la librería de contenedores que Java 2 proporciona para contener objetos mientras se esta trabajando con ellos: los arreglos simples y los mas sofisticados contenedores (estructuras de datos) como ArrayList y HashMap. y finally trabajan 14 Pensando en Java www. que las clases internas hacen mas que eso -ellas saben acerca de la clase que la rodea y pueden comunicarse con ella.pueden ser detectados y tratados solo en tiempo de ejecución. Capítulo 10: Manejo de errores con excepciones La filosofía básica de Java es que un código mal formado no se ejecutará. throws. pero algunos de los problemas -errores del programador o condiciones naturales que se suceden como parte de la ejecución normal del programa. de la misma forma que es un concepto nuevo para la mayoría y toma algún tiempo sentirse confortable con el diseño utilizando clases interna. En general. sus programas crearán siempre nuevos objetos en todo momento y estos serán conocidos solamente en tiempo de ejecución o inclusive la cantidad exacta de objetos que se necesitan.y el tipo de código que se puede escribir con una clase interna es mas elegante y claro. las clases internas se ven simplemente como un mecanismo de ocultar código: se colocan clases dentro de otras clases.com . creando una clase a la que se le pueda realizar una conversión ascendente a mas de un tipo base. dado que permite realizar una variación de “herencia múltiple” de C++. Se aprenderá sin embargo.

proceso y salida. que son utilizadas en el formato para archivar ficheros de java (JAR Java ARchive). cuando se suele lanzar excepciones y cuando capturarlas.en Java. como crear excepciones propias. A menudo esta información ayuda a realizar una operación especial de conversión mas eficientemente. Además se verán las excepciones estándar de Java. este capítulo examina el proceso de tomar un objeto. Esto es fundamental para la creación de herramientas para crear programas de Desarrollo Rápido de Aplicaciones (RAD Rapid-Application Development). Las diferencias entre el “viejo” y el “nuevo” sistema de E/S de java será mostrado. entrada. Este capítulo explica para que es RTTI. Estos programas para armar ventanas pueden ser applets o aplicaciones que funcionen solas. Este capítulo es una introducción a Swing y a la creación de applets para la Web. como utilizarla. y como deshacerse de ella cuando no pertenece ahí. son examinados) Capitulo 12: Identificación de tipo en tiempo de ejecución La identificación de tipos en tiempo de ejecución de Java (RTTI run-time type identification) posibilita encontrar el tipo exacto de un objeto cuando solo se tiene referencia al tipo base. Capítulo 11: El sistema de E/S de Java Teóricamente. Normalmente. se quiere intencionalmente ignorar el tipo exacto de un objeto y se deja que el mecanismo de enlace dinámico (polimorfismo) implementen el comportamiento correcto para ese tipo. “hacerlo fluir” (para ponerlo en un disco o enviarlo a través de la red) y reconstruirlo. En este capítulo se aprenderá acerca de las diferentes clases que java proporciona para leer y escribir ficheros. que es un grupo de clases que manejan ventanas de una forma portátil. que sucede con las excepciones en el constructor y como los manejadores de excepción son localizados. Capítulo 13: Creando Ventanas y Applets Java se distribuye con la librería GUI “Swing”. este capítulo introduce el mecanismo de reflexión de Java. Pero ocasionalmente ayuda mucho saber el tipo exacto de cada objeto para el cual solo se tiene una referencia a la base. Capítulo 14: Múltiples hilos 15 . La importante tecnología “JavaBeans” es introducida. se puede dividir cualquier programa en tres partes. Además. bloques de memoria y la consola. Esto implica que E/S (entrada/salida) es una parte importante de la ecuación. También las librerías de compresión de Java. lo que es manejado por la serialización de objetos de Java. Además.

Este capítulo explora la comunicación a través de las redes y de la Internet. Este apéndice da suficiente introducción para esta característica para ser capaz de crear ejemplos simples de la interfase con código que no sea Java. y Enterprise JavaBeans(EJBs). que utiliza una estrategia diferente para el problema.BruceEckel. solo C/C++ son soportados). el concepto de pasar un objeto a una función y retornar un objeto de una función tiene consecuencias interesantes. hay una introducción a las nueva tecnología de JINI . Cuando se sabe la plataforma que se está ejecutando. Este capítulo hecha una mirada en la sintaxis y la semántica de la multitarea en Java. esto es solo una apariencia de múltiples tareas). los hilos son mas visibles cuando se trata de crear una interfase de usuario con respuesta así es que. llamadas hilos. y también mostrar la clase String. Finalmente . por ejemplo. que son funciones que son escritas en otros lenguajes de programación (actualmente. Capítulo 15: Computación distribuida Todas las características y librerías de Java parecen realmente venir juntas cuando se comienza a escribir a través de redes.Java proporciona una facilidad incluida para soportar múltiples tareas concurrentes. A pesar de que esto puede ser utilizado en cualquier parte. JavaSpaces. un usuario no es impedido de presionar un botón o de entrar datos cuando algún proceso se ejecuta. que se ejecutan con un solo programa (A no ser que tenga múltiples procesadores en su máquina. e Invocación de Remota de Métodos (RMI Remote Method Invocation). y las clases que Java proporciona para hacer esto fácil. Apéndice B: La interfase nativa de Java (JNI) Un programa totalmente portátil en Java tiene serios inconvenientes: velocidad y la inhabilidad de acceder a servicios específicos de la plataforma. Apéndice A: Pasando y retornando objetos Dado que la única forma de comunicarse con objetos en Java es a través de las referencias.com . Apéndice C: Guía de programación Java 16 Pensando en Java www. es posible aumentar dramáticamente la velocidad de ciertas operaciones haciéndolas métodos nativos. Este introduce a los muy importantes conceptos de Servlets y JSPs (Para programación del lado del servidor). Este apéndice explica que se necesita conocer para manejar objetos cuando se esta moviendo adentro y afuera de funciones. mediante Java Conectividad a bases de datos de Java (JDBC Java DataBase Connectivity).

El primero esta asociado con este libro. La solución de los ejercicios pueden ser encontrarse en el documento electrónico The Thinking in Java Annotated Solution Guide disponible por . es un acompañamiento ideal.BruceEckel. así es que encontrara un grupo en el final de cada capítulo. pero no presenta grandes desafíos (Presumiblemente el lector puede encontrar el suyo propio -o mejor. El primero se encuentra en el mismo libro: Thinking in C. El CD ROM contiene todas las lecturas (con la importante excepción de la atención personalizada!) de los cinco días totalmente inmerso de 17 . Esto es mas de 15 horas de lecturas que he grabado. el problema lo encontrara a el). sincronizado con cientos de transparencias con información. descrito en el final del prefacio. La mayoría están diseñados para ser solucionados en un corto tiempo y probados y puliendo su conocimiento. una pequeña propina en www. Este CD ROM es un producto separado y incluye la totalidad de el seminario de entrenamiento “Hands-On Java”. CD ROM multimedia Hay dos CD ROMs multimedia asociados con este libro. Dado que el seminario esta basado en este libro. asegurándose que todos los estudiantes se concentran en el material. Apéndic e D: Lectura recomendada Una lista de los libros de Java que he encontrado particularmente útiles. Algunos ejercicios son mas avanzados para prevenir el aburrimiento para los estudiantes experimentados.com.Este apéndice contiene sugerencias que ayudarán cuando se ejecute un programa con diseño y código de bajo nivel. el cual esta basado en el contendido de este libro. el que prepara para el libro dando la velocidad necesaria con la sintaxis C que se necesita para ser capaz de entender Java. Algunos son mas desafiantes. Ejercicios He descubierto que los ejercicios simples son excepcionalmente útiles para completar el entendimiento de los estudiantes durante un seminario. Un segundo CD ROM multimedia esta disponible. La mayoría de los ejercicios están diseñados para ser lo suficientemente fáciles que ellos puedan ser terminados en un tiempo razonable en una situación de salón de clase mientras el instructor observa.

utilizando ejemplos del libro en la mayoría de los medios generalmente no es un problema).BruceEckel.txt Copyright ©2000 Bruce Eckel Fichero de código fuente de la 2da edición del libro “Thiking en Java”. pero se debería verificar en el sitio oficial para asegurarse que la versión en el espejo es actualmente la edición mas reciente. Bruce Eckel no realiza representaciones acerca de la aplicabilidad de este software para ningún propósito. incluyendo modificaciones en forma ejecutable solamente.entrenamiento de seminarios. a no ser que el punto de partida de distribución sea http://www.com . La meta primaria de los derechos de autor es asegurar que los fuentes del código es adecuadamente citado.BruceEckel. No se puede distribuir versiones del código fuente modificada en este paquete.com (y los espejos oficiales) en donde esta disponible libremente.BruceEckel. Todos los derechos reservados EXCEPTO de la forma en descripta en las siguientes instrucciones: Usted puede libremente utilizar este fichero para su propio trabajo (personal o comercial). Esto es proporcionado “como esta” sin garantías explícitas o implícitas de cualquier tipo. Para asegurarse de que se tiene la versión mas actual.com.com. Se puede distribuir el código en el salón de clase y en otras situaciones educacionales. visitando el sitio Web www. Código fuente Todo el código fuente para este libro esta disponible bajo la licencia de libre acceso.BruceEckel. incluyendo no se puede copiar y distribuir este código. Se pueden encontrar versiones en servidores espejo versiones electrónicas del libro en otros sitios (algunos de estos sitios se pueden encontrar en www. El CD ROM con el seminario Hands-On esta disponible solo ordenándolo directamente del sitio Web www. este el sitio oficial para la distribución del código y la versión electrónica del libro. Creo que he ajustado un nuevo estándar de calidad. incluyendo el uso en materiales de presentación. No se puede utilizar este fichero en medios impresos sin el permiso expreso del autor. En cada fichero de código fuente encontrará una referencia a la siguiente advertencia de derechos de autor: //:! :CopyRight. distribuido como un paquete por separado.com). siempre y cuando el libro “Thinking en Java” sea citado como la fuente. Exceptuando las situaciones de salón de clase. y para prevenir que no se puede publicar el código en un medio impreso sin permiso (siempre y cuando la fuente sea citado.BruceEckel. incluyendo y implícitamente garantías 18 Pensando en Java www. Se otorga permiso para utilizar este fichero en situaciones de salón de clase.

(Por favor utilice la misma forma para errores que no sean de código encontrados en el libro). Utilizo un estilo de código particular en los ejemplos en este libro..com/doc/codeconv/index. así es que solo diré que no estoy tratando de indicar un estilo correcto mediante mis ejemplos.BruceEckel. Los errores descubiertos y reportados al autor aparecerán 19 . En ningún evento Bruce Eckel o el editor son responsables por ninguna pérdida de renta. variables. y parece ser ) soportado por la mayoría de los ambientes de desarrollo en Java. por favor sométalo a consideración utilizando el formulario que encontrará en www. incidental. adaptabilidad para cualquier propósito o infracción. Este estilo sigue el estilo que Sun utiliza en virtualmente todo el código que encontrará en este sitio (vea java. Estándares de código En el texto de este libro. Dado que Java es una forma libre de programación. los identificadores (funciones. los ficheros de código impreso en este libro pueden todos trabajar sin errores de compilación. como lo es “class”.Debe el software ser probado de cualquier defecto. usted asume el costo de todos los servicios necesarios. Si piensa que ha encontrado un error. o correcciones. se pueden levantar por el uso o la inhabilidad de utilizar el software incluso si Bruce Eckel y el editor tienen un aviso de la posibilidad de estos daños. Los errores que suelen causar mensajes de error en tiempo de compilación son comentados mediante //! de esta forma pueden ser fácilmente descubiertos y probados utilizando medios automáticos. o datos. tengo mis propias motivaciones par utilizar el estilo que utilizo. especial. ///:~ Se puede utilizar el código en sus proyectos y en el salón de clase (incluyendo en la presentación de materiales) siempre y cuando el aviso de derecho de autor sea mantenido.sun. o por directa. La totalidad del riesgo así como la calidad y rendimiento del software es suyo. indirecta. La mayoría de las palabras clave son colocadas en negrita. Bruce Eckel y el editor no serán responsables por cualquier daño sufrido por usted o cualquier tercera parte como resultado de utilizar o distribuir sofware. o daños punitivos.de comerciabilidad. directamente de ficheros compilados. sin embargo causado e independientemente de la teoría de responsabilidad. reparaciones. consecuente.com. puede utilizar el estilo que quiera si está confortable con el. De esta manera. deberá también advertir de que el estilo de codificación de Sun coincide con el mío -esto me satisface. Los programas en este libro son ficheros que están incluidos por el procesador de palabras en el texto. El tema del formateo de estilo es bueno para momentos de fuertes debates. excepto aquellas palabras clave que son utilizadas mucho y la negrita se vuelva tediosa. beneficio. y nombres de clases) son colocados en negrita. a pesar de que no tengo nada que ver son esto.html . Si ha leído los otros trabajos.

primero en el código fuente distribuido y luego en las actualizaciones del libro (que aparecerán también en el sitio Web www.com y también esta en el CD que se encuentra dentro de este libro. Una cosa que debe advertir es que.BruceEckel. de seminarios de entrenamiento en la mano.0. 1.0. Sun ha liberado tres versiones principales de Java: 1.3. Versiones de Java Generalmente confío en la implementación de Java de Sun como una referencia cuando determino si un comportamiento es correcto. 1. Hasta ahora.com. públicos y para su hogar basados en el material de este libro. cuando necesito mencionar versiones anteriores del lenguaje.2. Por mas información. Seminarios y consejos Mi empresa proporciona cinco días.1 y 2 (que es llamada versión 2 a pesar de que las versiones de JDK de Sun continúan utilizando el esquema de numeración 1.4. Las lecturas de audio y transparencias para el seminario de introducción es también capturado en CD ROM para proporcionar al menos alguna de la experiencia del seminario sin viajes ni gastos. no utilizo los números de revisión secundarios.com . 1. tutores y servicios de chequeos simples de programas para ayudarlo en su proyecto en el ciclo de desarrollo en especial con su primer proyecto Java de su empresa. 20 Pensando en Java www. Este libro se enfoca en Java 2 y esta probado con Java 2 así es que el código compilará sobre Linux (mediante el Linux JDK que está disponible en el momento en que se escribió esto). la primera edición de este libro se puede bajar libremente en www. El material seleccionado de cada capítulo representa una lección.1 y 2 solamente. etc). En este libro me referiré a Java 1. Mi empresa también proporciona consultoría. que es seguida por un período de ejercicio monitoreado así es que cada estudiante recibe atención personal. vaya a www.BruceEckel. Parece que la versión 2 finalmente trae a Java a los primeros puestos.BruceEckel. en particular en lo que concierne a herramientas de interfase de usuario. 1. Si necesita aprender acerca de versiones anteriores del lenguaje que no están cubiertas en esta edición.com).BruceEckel. para cuidarme de los errores topográficos producidos por futuras revisiones secundarias de estos productos.

Si descubre algo que cree que es un error.Errores No importa cuantos trucos utilice un escritor para detectar errores. siempre hay alguno que se arrastra adentro y a menudo brinca en la pagina en busca de un lector tierno. y el énfasis en la mano de obra artesanal del software en lugar de solo crear código. Hay demasiados ecos con la situación que tenemos ahora: el cambio de siglo. Veo a Java de esta misma forma: como un intento de elevar a el programador fuera de un sistema operativo mecánico y lanzarse hacia un “software artesanal”. Art & Crafts enfatiza el diseño frugal. incluya el fichero fuente original e indique cualquier modificación sugerida. Su ayuda será apreciada. Hay un enlace para someter a consideración los errores en el comienzo de cada capítulo en la versión HTML de este libro (y en el CD ROM que se encuentra en la parte de atrás de este libro. realizaciones a mano y la importancia de los artesanos. Comienza en Inglaterra como reacción a la producción mecánica y la Revolución Industrial y el estilo altamente ornamentado de la era Victoriana.BruceEckel. y a pesar de eso sin evadir el uso de herramientas modernas. que son colocados dentro de la caja de objetos. Estos insectos son objetos. El otro tema de esta cubierta sugiere una caja de coleccionista que un naturalista puede utilizar para mostrar los especimenes de insectos que el o ella han preservado. por favor utilice este formulario para poner a consideración el error con su corrección sugerida. en la página de este libro. que además se puede bajar libremente desde www. lámparas y otras piezas son originales o inspiradas en este período. las formas de la naturaleza son vistas en el movimiento de art nouveau. Si es necesario. La caja de objetos es colocada dentro de la “funda” . Nota en el diseño de la cubierta La cubierta de Thinking in Javaesta inspirada en el movimiento Americano Art & Crafts.com) y también en el sitio Web. que 21 . y sus muebles. El autor y el diseñador de la cubierta del libro (los cuales son amigos desde pequeños) encontraron inspiración en este movimiento. la evolución de los toscos principiantes de la revolución de las computadoras a algo mas refinado y significativo para las personas individuales. que comienza cerca de fin siglo y alcanza su punto mas alto entre 1900 y 1920.

por ayudarme a aclarar conceptos en el lenguaje. Muchas gracias a Kevin y Sonda Donovan por subarrendarme su gran lugar en el bellísimo Crested Butte. and Larry O’Brien. mostrar y doblegar errores (lo cual es de verdad uno de sus atributos mas poderosos). Mis primero dos libros fueron publicados con Jeff Pepper como editor en Osborne/McGraw-Hill. y finalmente confinados dentro de una caja para mostrarlos. Gracias a la comunidad de la calles Street Cohousing por molestarme los dos años que me insistieron que escribiera la primera edición de este libro (y por involucrarse conmigo con todo). y aquí los insectos son capturados y presumiblemente matados en una jaula de especimenes. Bill Venners. Reconocimientos Primero. grupo blanco. .ilustra el concepto fundamental de la acumulación en la programación orientada a objetos. Claro.BruceEckel. Gracias a Cay Horstmann (co-autor de Core Java Prentice-Hall. Martin Byer. proporcionando consultoría y desarrollando proyectos educativos: Andrea Provaglio. Gracias también a los amigables residentes de Crested Butte y el Laboratorio Biológico de las Montañas Rocosas quienes me hacen sentir tan bienvenido. D’Arcy Smith (Symantec). Gracias. quien con gentileza proporcionó mi servidor Web por los primeros años de mi presencia en la Web.com . Dave Bartlett (quien contribuyó significativamente a el capítulo 15). Estoy especialmente endeudado con Gen Kiyooka y su empresa Digigami. Jeff—esto significa mucho para mi. Jeff apareció en el lugar correcto en el momento correcto en Prentice-Hall y ha aclarado el camino y hecho todas las cosas bien para hacer esto una experiencia de publicación placentera. Martin la carga. Gracias a Rolf André Klaedtke (Suiza). que un programador no puede ayudar pero hacer la asociación con “insectos”. 2000). Vlada & Pavel Lahoda. Esto fue una invaluable ayuda externa. 1996). and Marco Cantu (Italia) por hospedarme en el viaje de excursión de mi primer seminario en Europa organizado por mi mismo. gracias a los asociados que han trabajado conmigo dando seminarios. Aprecio tu paciencia cuando trataba de desarrollar el mejor modelo de compañeros como nosotros para trabajar juntos. Gracias a Claudette Moore y a la agencia literaria Moore por su tremenda paciencia y perseverancia captando exactamente lo que yo quería. Colorado por el verano mientras trabajaba en la primer edición de este libro. de la misma forma alude a la habilidad de Java para encontrar. y Paul Tyma (co-author de Java Primer Plus El . Martin Vlcek. y Hanka (Praga). 22 Pensando en Java www.

Mis amigos de Delphi proporcionaron asistencia ayudándome a ganar comprensión en ese maravilloso ambiente de trabajo. dado que hay muchos conceptos y decisiones de diseño en común. en el cual he encontrado bastante inspiración e instrucción. Lee. Steve Wilkinson. y por su puesto a Zack Urlocker. Joe Dante. Eric Faurot. Gracias también a KoAnn Vikoren. también). Robert Stephenson. Neil Ruybenking (que ha usado para hacer el yoga. quien ha me ha inspirado nuevamente acerca de las posibilidades de las conferencias. pero un especial agradecimiento para (por l a primera edición): Kevin Raulerson (encontró toneladas de errores grandes). Marco Pardi. Gen Kiyooka. y Andrea Provaglio (quienes ayudaron en el entendimiento de Java y la programación en general en Italia. Ellos son Kraig Brockschmidt. vegetarianismo y Zen hasta que descubrió las computadoras).Gracias a las personas que han hablado en mi área de Java en la Conferencia de Desarrollo de Software. Mi amigo Richard Hale Shaw’s cuyo entendimiento y soporte ha sido muy útil (y a Kim. David Karr. 23 . Steve Clark. y un huésped de los otros. Zev Griner. y ahora en los Estados Unidos como una asociación en el equipo MindView). un amigo de mucho tiempo con quien he recorrido el mundo.BruceEckel. y el resto de la gente y tripulación en MFL. David Combs (por muchas correcciones gramaticales y correcciones en la claridad). Un especial agradecimiento a Larry y Tina O’Brien. Stroschein. Ir. Ha habido una avalancha de técnicos inteligentes en mi vida que se han convertido en amigos y han tenido también influencia de forma no usual ya que ellos hacen toga y practican otras formas de incremento espiritual. que preguntan las preguntas que necesito escuchar en orden de hacer el material mas claro. y los estudiantes en mis seminarios. John Cook. Richars y yo hemos perdido muchos meses dando seminarios juntos y tratando de trabajar con la experiencia del aprendizaje perfecto con los asistentes. Malkovsky. John Pinto. Douglas Dunn. Marc Meurrens quien realizó un gran esfuerzo para publicar y crear la versión electrónica de la primer edición de el libro disponible en Europa. Gracias especialmente a Tara Arrowood. Leander A. David B. No es tanto la sorpresa para que entender Delphi me ayudó a entender Java. Dennis P.com). Joe Sharp (todos ellos son fabulosos). Austin Maher. Roque Oliveira. Dr. Neil Galarneau. que ayudaron a poner el seminario en el original Hands-On Java CD ROM (Se puede encontrar mas en www. Roth. Charles A. Dejan Ristic. Franklin Chen. Bob Resendes (simplemente increíble). Ellos son Marco Cantu (otro Italiano -tal vez el estar empapado en Latín le da una aptitud para los lenguajes de programación?). Muchas personas enviaron correcciones y estoy en deuda con ellos. Prof.

pero no esta limitado a: Andrew Binstock. así es que los errores de escritura son míos. the Cranstons. célebre autor y diseñador (www. Larry Fogg. Rahim Adatia.WillHarris. Claire Sawyers.com . y sus familias. Patty. Dave y Brenda Bartlett. Contribuyeron a través de la Internet Gracias a aquellos que me ayudaron a escribir nuevamente los ejemplos para utilizar la librería Swing. Mike y Karen Sequeira. y la foto de tapa fueron creados por mi amigo Daniel Will-Harris. el de la cubierta. los Sudeks. Andrea Rosenfield. Michael Wilk. los Pollocks. Ravi Manthena.com). Tom Keffer. Lynn y Todd. El grupo de amigos de soporte incluye. y por otras asistencias: Jon Shvarts. Brinkley Barr. Chester y Shannon Andersen. en Sudáfrica y la segunda edición fue en Praga). Dan Putterman. Laurie Adams. Peter Vinci. Gary Entsminger y Allison Brody. Chris and Laura Strand.El diseño del libro. quién ha utilizado para jugar con casi escritos en el comienzo del colegio donde esperaba la invención de las computadoras y sus escritorios para publicaciones. David Intersimone. Steve Sinofsky.BruceEckel. Y por su puesto. Sun. Thomas Kirsch. Gene Wang. la familia Robbins. Greg Perry. Marilyn Cvitanic. Dave Mayer. Ilsa. mamá y papá. Gracias a los proveedores que crearon los compiladores: Borland. David Lee. Microsoft® Word 97 para Windows fue utilizado para escribir el libro y para crear las páginas listas en Adobe Acrobat. Un agradecimiento especial a todos mis profesores y a mis estudiantes (que también son mis profesores). Joe Lordi. los alquimistas. atesoré esa fantástica semana en Esalen. the Mabrys. Rajesh Jain. mas italianos (Laura Fallai. Siempre . Kevin Donovan y Sonda Eastlack. los Haflingers. Corrado. y por su puesto. 1983). el grupo Blackdown (por Linux). Brad Jerbic. y Cristina Giustozzi). ha sucedido que las dos veces que el libro fue producido fue en el extranjero -la primera edición fue en Capetown. los Rentschlers. el libro fue creado directamente de los ficheros PDF de Acrobat (como un tributo a la era electrónica. Bill Gates en Midnight Engineering Magazine Larry . Jens 24 Pensando en Java www. y quejándose de mi entre dientes con mis problemas de álgebra. la familia Moelter (y la McMillans). Brian McElhinney. El profesor de redacción mas divertido fue Gabrielle Rico (autor de Writing the Natural Way Putnam. JD Hildebrandt. Banu Rajamani. La fuente del cuerpo de Georgia y los títulos son en Verdana. y Lee Eckel. El tipo de la cubierta es ITC Rennie Mackintosh. Sin embargo he presentado las páginas listas yo solo. Christi Westphal. Dick. Constantine y Lucy Lockwood. Dave Stoner.

Brandt. y todos los que han expresado apoyo. Malcolm Davis. Esto realmente me ha ayudado lograr el proyecto. Nitin Shivaram. 25 .

Este capítulo. Si se piensa que se necesita mas preparación en programación y en la sintaxis de C antes de abordar este libro. pintura.1: Introducción a los objetos La génesis de la revolución de las computadoras fue en una máquina. las herramientas comienzan a verse menos como máquinas y mas como parte de muestras mentes. se debería trabajar con el CD ROM de entrenamiento Thinking in C: Fundation for C++ y Javaque se . y también como otras formas de expresar cosas como escritura. 26 Pensando en Java www. estas personas pueden retrasarse y perderse sin que hayan intentado con algo de código. hay muchos conceptos que son introducidos aquí para darle un vistazo general sólido de la POO.com . La génesis de nuestros lenguajes de programación de esta forma tienden a verse como esta máquina. Pero las computadoras no son solo maquinas tanto como herramientas de amplificación de la mente (“bicicletas para la mente”. escultura. Muchas personas no se sienten confortables arremetiendo con la programación orientada a objetos sin entender una imagen general primero. asume que se tiene experiencia en lenguajes de programación procesales. animación y producciones de cine. siéntase libre de saltar este capítulo -saltarse el capítulo en este punto no impedirá que se escriban programas o que se aprenda el lenguaje. De esta forma. Como resultado. incluyendo un vistazo general de los métodos de desarrollo.com. y este libro. Este capítulo introduce a el concepto de POO. Este capítulo es un material de fondo y suplementario.BruceEckel. La programación orientada a objetos (POO) es parte de este movimiento hacia la utilización de las computadoras como un medio de expresión.BruceEckel. Si se es parte de este último grupo y se esta ansioso de obtener cosas específicas del lenguaje. como Steve Jobs las llamaba cariñosamente) y un tipo diferente de medio de expresión. a pesar de que no sea necesario C. Sin embargo muchas otras personas no tienen los conceptos generales hasta que hayan visto algunos de los mecanismos primero. encuentra incluido en este libro y disponible en www. se puede querer regresar aquí para completar las el conocimiento y así se puede entender por que los objetos son importantes y como diseñar con ellos. Sin embargo.

que es el lugar donde se esta modelando ese problema. Esta representación es suficientemente general para que el programador no este restringido a un tipo de problema en particular. El esfuerzo requerido para realizar este mapeo. pero cuando se da un paso fuera de los dominios de solución se convierte en torpes. pero sus abstracciones primarias siguen requiriendo que se piense en términos de la estructura de la computadora en lugar de la estructura del problema que se está tratando de resolver. respectivamente). Se puede debatir que la complejidad de los problemas que son capaces de resolver esta directamente relacionado con el tipo y la calidad de la abstracción. Cada una de estas aproximaciones es una buena solución para una clase de problema particular para el cual esta diseñado. El lenguaje ensamblador es una pequeña abstracción de las capas mas bajas de la máquina. necesitará también otros objetos que no tienen problema de espacios análogos). BASIC. que es el lugar donde el problema existe). La estrategia de la programación orientada a objetos da un paso mas adelante proporcionando herramientas para que el programador represente elementos en el espacio del problema. y C) que son abstracciones del lenguaje ensamblador. La alternativa para modelar esta máquina es modelar el problema que esta tratando de resolver. Los lenguajes han sido creados basados en restricciones de programación y para programar exclusivamente manipulando símbolos gráficos (Actualmente ha probado ser muy restrictivo). y como un efecto secundario creado la industria entera de “métodos de programación”. estará leyendo palabras que también expresan el problema. eligen puntos de vista particulares del mundo (“Todos los problemas son finalmente listas” o “Todos los problemas son algoritmos”. así es que cuando lea el código describiendo la solución. como una computadora) y el modelo del problema que esta siendo solucionado (en el “espacio de problema”. Estos lenguajes son grandes mejoras sobre el lenguaje ensamblador. Esto es la abstracción de un lenguaje mas 27 . PROLOG convierte todos los problemas en cadena de decisiones. La idea es que al programa se le esta permitido adaptarse solo a el idioma del problema para agregar nuevos tipos de objetos. Por “tipo” quiero decir. produce que los programas sean difíciles de escribir y caros de mantener.. “¿Que es lo que se esta abstrayendo?”. El programador debe establecer la asociación entre el modelo de la máquina (el “espacio de solución”. Nos referimos a los elementos en el espacio del problema y su representación en el espacio de la solución como “objetos” (Claro. Muchos lenguajes llamados “imperativos” que lo siguen (como lo es Fortran. y el hecho de que esto es extrínseco a el lenguaje de programación.El progreso de abstracción Todos los lenguajes de programación proporcionan abstracciones. Los primeros lenguajes como lo es LISP y APL.

tiene un estado. servicios. se puede crear complejidad en un programa ocultándola detrás de la simplicidad de los objetos. 1 Vea Multiparadifm Programming in Leda by Timothy Budd (Addison-Wesley 1995). Esto es actualmente una afirmación falsa. como se verá mas adelante. De esta manera. En teoría. esto no parece una mala analogía para los objetos en el mundo real -ellos tienen características y comportamientos. Utilizando la forma de hablar. Un programa es un conjunto de objetos indicándose entre se que hacer mediante mensajes. Cada objeto se ve bastante como una pequeña computadora. Todo es un objeto. Estas características representas una estrategia pura de programación orientada a objetos. Mas concretamente. se puede tomar cualquier componente conceptual en el problema que se esta tratando de resolver (perros. Alan Kay resume cinco características de Smalltalk. en lugar de hacerlo en términos de la computadora donde la solución va a ser ejecutada. y defienden la combinación de varias estrategias en un multi paradigmade lenguajes de programación1 . esta almacena datos. Algunos diseñadores de lenguajes han decidido que la programación orientada a objetos por si misma no es adecuada para solucionar fácilmente todos los problemas de programación. Cada objeto tiene un tipo. La mas importante característica distintiva de una clase es “¿Que mensajes se les pueden enviar?”. 1.) y representarlos como un objeto en su programa. Dado que un objeto del tipo “círculo” es también un objeto 2. la POO permite describir el problema en términos del problema. pero se le puede “hacer pedidos” a ese objeto. se “envía un mensaje” a ese objeto. Sin embargo sigue habiendo una conexión de fondo con la computadora. Todos los objetos de un tipo particular pueden recibir los mismos mensajes. Hay que pensar que un objeto como una variable de figurativa. De esta forma. cada objeto es una instancia de una claseen la cual “clase” es sinónimo de “tipo”. 4. 3. Para realizar un pedido a un objeto.flexible y poderosa que hemos tenido. se puede pensar en un mensaje como una petición para llamar a una función que pertenece a un objeto particular.com . 5. etc. el primer lenguaje orientado a objetos exitoso y uno de los lenguajes sobre el cual esta basado Java. y operaciones que se pueden pedir que se realicen. Puesto de otra forma. pidiéndole que realice operaciones el mismo. edificios. Sin embargo. 28 Pensando en Java www. se puede crear un nuevo tipo de objeto haciendo un paquete que contenga objetos existentes.BruceEckel. Cada objeto tiene su propia memoria compuesta por otros objetos.

a pesar de que lo que realmente con la programación orientada a objetos es crear nuevos tipos de datos. aún siendo únicos. Simula. donde es fundamental la palabra clave class que introduce un nuevo tipo en un programa. cuentas. Esta sustituibilidad es uno de los conceptos mas poderosos en la POO. cuentas. Esta entidad es el objeto. esta garantizado que un círculo pueda recibir mensajes del tipo forma. transacciones. cada miembro tiene su propio estado. los cajeros. virtualmente todos los objetos de los 29 . Objetos que son idénticos excepto por como se manifiestan durante la ejecución de un programa donde son agrupados juntos en “clases de objetos” y ahí es donde la palabra clave class aparece. La idea de todos los objetos. son también parte de una clase de objetos que tienen características y comportamientos en común fue utilizada directamente en el primer lenguaje orientado a objetos. se envía un mensaje y el objeto se figura que hacer con el). Los tipos de datos abstractos trabajan al menos exactamente como los tipos incluidos: Se pueden crear variables de un tipo (llamadas objetos o instancias en el lenguaje de la orientación a objetos) y manipular esas variables (llamados mensajes enviados o peticiones.del tipo “forma”. cada cajero tiene un nombre. etc. pueden ser representados mediante una única entidad en el programa de computadoras. Los miembros (elementos) de cada clase comparten algunas cosas en común: cada cuenta tiene un balance. etc. cada cajero puede aceptar un depósito. De esta manera. clientes.. fue creado para desarrollar simulaciones como el clásico “problema del cajero del banco”. clientes. y unidades de moneda -un montón de objetos”. se tiene un grupo de cajeros. como su nombre implica. Así es que. En este. y cada objeto pertenece a una clase en particular que define sus características y comportamientos. cada cuenta tiene diferente balance. En el mismo momento. Esto significa que se puede escribir código que se comunique con formas y automáticamente manejar cualquier cosas que encaje en la descripción de forma. El crear tipos de datos abstractos (clases) es el concepto fundamental en la programación orientada a objetos. el hablaba de “la clase de peces y la clase de pájaros”. Un objeto tiene una interfase Aristóteles fue probablemente el primero que comenzó a estudiar cuidadosamente el concepto de tipo. Simula-67. transacciones.

como completar una transacción.BruceEckel. por ejemplo. La estrategia de la orientación a objetos es no limitarse a construir simulaciones. y el tipo es lo que determina la interfase. Se extiende el lenguaje de programación agregando nuevos tipos de datos específicos a sus necesidades. La diferencia es que el programador define una clase para encajar en un problema en lugar de comenzar a forzar el uso de un tipo de datos existente que fue diseñado para representar una unidad de almacenamiento en una máquina. 30 Pensando en Java www. uno de los retos de la programación orientada a objetos es crear una función uno a uno entre los elementos del espacio del problema y los objetos del espacio solución. el uso de técnicas de POO pueden reducir un gran grupo de problemas a una solución simple. o mover una llave. 2 Algunas personas hacen una distinción.com . Los pedidos que se le pueden hacer a un objeto determinan su interfase. Un ejemplo simple puede ser la representación de una bombita de luz: Light lt = new Light(). Una ves que una clase es establecida. se puede crear tantos objetos de esa clase como se quiera. ¿Pero. Claro que. Dado que una clase describe un grupo de objetos en particular que tienen idénticas características (elementos datos) y comportamientos (funcionalidad). también tiene un grupo de características y comportamientos. declarar un tipo determina la interfase en donde la clase es una implementación particular de esa interfase.lenguajes de programación orientada a objetos utilizan la palabra clave “class”. dibujar algo en la pantalla. como obtenemos un objetos que haga algo útil? Debe haber por ahí una forma de realizar una petición a un objeto de tal manera que haga algo. El sistema de programación le da la bienvenida a la nueva clase y les da toda la atención y chequeo de tipo que le da a los tipos incluidos. una clase es realmente un tipo de datos porque un número de punto flotante. Se este o no de acuerdo con que cualquier programa es una simulación del sistema que se esta diseñando. Cuando ve la palabra “tipo” se piensa en “clase” y viceversa 2. Y cada objeto puede satisfacer solo ciertos pedidos. y luego manipular esos objetos como si fueran los elementos que existen en el problema que esta tratando de solucionar.

La interfase establece que peticiones se pueden realizar a un objeto particular. el nombre de este objeto Light en particular es lt. esto es mas o menos todo lo que a programación con objetos se refiere.lt. darle brillo u oscurecerla. La meta del programador cliente es recolectar una caja de herramientas llena de clases para utilizar en un desarrollo rápido de una aplicación. así es que la porción del medio no es mostrada. comprenden la implementación. que reciben los mensajes que se envían al objeto) en la parte inferior de la caja. la función es llamada. Desde un punto de vista de programación procesal. debe haber código en algún lugar para satisfacer esa petición. 31 . Esto. entonces la porción de abajo no es necesario que se muestre tampoco. y el objeto resuelve que hacer con el mensaje (el ejecuta el código). apagar. Este proceso es usualmente resumido diciendo que se “envía un mensaje” (se realiza una petición) a un objeto. Para enviar un mensaje a el objeto. el nombre del tipo/clase es Light. A menudo. solo el nombre de la clase y las funciones miembro públicas son mostradas en los diagramas de diseño UML. El diagrama mostrado mas arriba sigue el formato de Unified Modeling Languaje (UML). Cada clase es representada mediante una caja. no es complicado. Sin embargo. Aquí. lo que significa que el creador de la clase puede cambiar la porción oculta a 3 Estoy en deuda con mi amigo Scott Meyers por este término.on(). Desde el punto de vista del usuario de una clase predefinida. y las funciones miembro (las funciones que pertenecen a ese objeto. y miembros datos que se escriben en el medio de la caja. junto con los datos ocultos. Un tipo tiene una función asociada con cada posible petición. con un nombre de tipo en la parte superior de la caja. y cuando se realiza una petición particular a un objeto. ¿Por que? Porque si es oculta. el cliente programador no puede utilizarla. Se crea un objeto Light definiendo una “referencia” (lt) para un objeto y utilizando new para pedir un nuevo objeto de ese tipo. La meta del creador de la clase es armar una clase que solo exponga lo necesario para el cliente programador y mantenga todo lo demás oculto. La implementación oculta Es útil dividir los campos en juego en creadores de clases (aquellos que 3 crean nuevos tipos de datos) y en programadores clientes (los consumidores de la clase que utilizan los tipos de datos en sus aplicaciones). se establece el nombre del objeto y se conecta con la petición del mensaje mediante un período (punto). Si esta interesado solo en el nombre. y las peticiones que se pueden hacer a un objeto Light son encender.

dentro de las funciones miembro de ese tipo. con la excepción que las clases heredadas tienen acceso a miembros 32 Pensando en Java www. private es un muro de ladrillo entre el creador y el programador cliente. Estos especificadores de acceso determina quien puede utilizar las definiciones que siguen.com . pero uno que esta juntando todo en una aplicación utilizando librerías que no son de el. sin control de acceso no hay forma de prevenirlo. mas tarde descubrir que se necesita volver a escribir para hacerla correr mas rápido. Si la interfase y la implementación son claramente separadas y protegidas. se debe implementar una clase particular en una forma elegante par un desarrollo cómodo. se establece una relación con el cliente programador. Por ejemplo. Su uso y significado es muy directo. posiblemente para crear una librería mas grande. Así es que la primer razón para realizar un control de acceso es para mantener las manos de los clientes programadores fuera de las partes que no deben tocar -partes que son necesarias para las maquinaciones internas de los tipos de datos pero que no son parte de la interfase que el usuario necesita para solucionar su problema en particular. así es que ocultar la implementación reduce los errores de programación. private y protected. public significa que las siguientes definiciones están disponibles para cualquiera. entonces el cliente programador puede hacer cualquier cosa con la clase y no hay forma de establecer reglas. La segunda razón para controlar el acceso es permitir a los diseñadores de la librería cambiar el funcionamiento interno de la clase sin preocuparse acerca de como afectará a el cliente programador. La palabra clave private. Todo esta desnudo para el mundo. quien es también un programador. Cuando se crea una librería. Aun cuando realmente se prefiera que el cliente programador no manipule directamente algunos de los miembros de su clase. La porción oculta usualmente representa la parte delicada dentro de un objeto que puede ser fácilmente corrompido por un descuido o un programador cliente desinformado. Si todos los miembros de una clase están disponibles para todos. Si alguien trata de acceder a un miembro private. Al concepto de implementación oculta no se le puede dar demasiada importancia. protected funciona como private. Esto es actualmente un servicio a los usuario porque ellos pueden fácilmente ver que es importante para ellos y que deben ignorar. se puede lograr esto fácilmente. tendrá un error en tiempo de compilación.voluntad sin preocuparse acerca del impacto en cualquier otro. por el otro lado. significa que nadie puede acceder a estas definiciones excepto el creador del tipo. Java utiliza tres palabras explícitas para marcar los límites en una clase: public .BruceEckel. y luego. En una relación es importante tener límites que sean respetados por todas las partes involucradas.

sin el diamante para indicar una asociación4). Pero una vez que se tenga el diseño.protected. La herencia será introducida en breve. que comienza a jugar si no se utiliza uno de los especificadores mencionados anteriormente. acumulación). Java tiene también un accedo por “defecto”. toma experiencia y intuición producir un buen diseño. y no se necesita especificar acerca de si se está utilizando acumulación o composición. La reutilización de código es una de las mas grandes ventajas que los lenguajes de programación orientada a objetos proporciona. (El diagrama UML indica composición con el diamante relleno que afirma que hay un auto. 4 Esto es usualmente suficiente detalle para la mayoría de los diagramas. debería (idealmente) representar una unidad de código útil. pero solo se puede colocar un objeto de esa clase dentro de una nueva clase. Los objetos miembros de su nueva clase son usualmente privados. La forma mas simple de reutilizar una clase es utilizando un objeto de esa clase directamente. haciéndolos inaccesibles a el cliente programador que esta utilizando la clase. La composición acarrea una gran ventaja de flexibilidad. Esto permite cambiar aquellos miembros si molestar el código cliente existente. La nueva clase puede ser creada con cualquier cantidad y tipos de otros objetos. el concepto es llamado composición (o mas generalmente. La composición es a menudo referida como una relación “tiene un”. Reutilizando la implementación Una vez que una clase ha sido creada y probada. ruega por ser utilizado. Típicamente se utilizara una forma simple: solo una línea. como en “un auto tiene un motor”. 33 . Llamamos a esto “crear un objeto miembro”. Dado que se esta componiendo una nueva clase de una clase existente. pero fuera del paquete estos miembros aparentan ser private. pero no a miembros private. Esto es a veces llamado acceso “amigable” porque las clases pueden acceder a los miembros amigables de otras clases en el mismo paquete. La reutilización no es tan fácil de alcanzar como muchos esperarían. en cualquier combinación que se necesite para alcanzar la funcionalidad deseada en la nueva clase.

Se puede también cambiar los objetos miembros en tiempo de ejecución. sin embargo. Una vez que se tenga alguna experiencia. Dado que la herencia es tan importante en la programación orientada a objetos es muchas veces muy enfatizada. que es lo siguiente que se describe. Esto permite empaquetar datos y funcionalidad juntos en un concepto. no tiene esta flexibilidad dado que el compilador debe colocar las restricciones en las clases creadas con herencias. clonarla. y el programador nuevo puede tener la idea que la herencia se debe utilizar en todas partes. o clase padre) sea cambiada. o clase super. Sería bueno si pudiéramos tomar la clase existente. para cambiar dinámicamente el comportamiento del programa. Si se toma esta estrategia. Esto puede resultar torpe y en diseños demasiados complicados. se debería primero considerar la composición cuando se crean nuevas clases. Es una lástima. dado que es muy simple y mas flexible. será razonablemente obvio cuando se necesite herencia. y luego realizar agregados y modificaciones a el clon. o clase hija) también refleja estos cambios. así es que se puede representar un problema apropiadamente -el espacio de la idea en lugar de ser forzado a utilizar los idiomas de las capas mas bajas de la máquina. Esto es efectivamente lo que tenemos con herencia. o clase sub. Estos conceptos son expresados como unidades fundamentales en el lenguaje de programación utilizando la palabra clave class. la idea de un objeto es una herramienta conveniente. tener todos los problemas de crear una clase y luego estar forzado a crear una completamente nueva que pueda tener la misma funcionalidad.BruceEckel. Herencia. con la excepción de que la clase original (llamada la clase base.com . 34 Pensando en Java www. En lugar de eso. sus diseños serán mas claros. el “clon” modificado (llamada la clase derivadao clase heredada. Herencia: reutilización de la interfase Por si solo.

el acero es magnético). Ciertas curvas pueden ser invertidas. se puede derivar otros tipos para expresar las diferentes maneras que el corazón puede ser materializado. cuadrado. La herencia expresa esta similitud entre tipos utilizando conceptos de los tipos base y de los tipos derivados. 35 . también su relación con otros tipos de objetos. fundidas.(La flecha en el diagrama UML mas arriba apunta de la clase derivada a la clase base.) Un tipo hace mas que describir las restricciones de un grupo de objetos. De esto. posición y otras cosas. tipos mas específicos de basura son derivados que pueden tener características adicionales (una botella tiene color) o comportamientos (el aluminio puede ser comprimido. algunos comportamientos pueden ser diferentes (el valor del papel depende de su tipo y condición). Como se va a ver mas adelante. De esto. y cada forma tiene tamaño. Del tipo base. movida. borrada. El tipo base es una “forma”. Utilizando herencia. El tipo base es “basura” y cada pieza de basura tiene un peso. etc. y pueden ser desmenuzadas. una máquina para reciclar basura ordena piezas de basura. coloreada. tal vez utilizado en un sistema asistido o en un juego de simulación. Un tipo base contiene todas las características y comportamientos que comparte el tipo derivado de este. El tipo de jerarquía envuelve las similitudes y diferencias entre las formas. tipos específicos de formas son derivados (heredados): círculo. triángulo y otros mas. Cada forma puede ser dibujada. Si se crea un tipo base para representar el corazón de sus ideas acerca de algunos objetos en su sistema. Por ejemplo. o descompuestas. cada uno de los cuales tiene características y comportamientos adicionales. y otras cosas. Dos tipos pueden tener características y comportamientos en común. se puede crear un tipo de jerarquía que expresa el problema que se está tratando de resolver en términos de sus tipos. pero un tipo puede contener mas características que otros y tal vez pueden también manejar mas mensajes (o manejarlos de forma diferentes). por ejemplo. Además. pueden haber mas de una clase derivada. como cuando se quiere calcular el área de una curva. color. un valor. Algunos comportamientos pueden ser diferentes. Un segundo ejemplo es el de la clásico ejemplo de la “forma”.

36 Pensando en Java www. Este nuevo tipo contiene no solo todos los miembros del tipo existente (a pesar de que los privados son ocultos para los de afuera e inaccesibles). Si simplemente se hereda una clase y no se hace nada mas. debe haber alguna implementación para ir a través de su interfase. lo que no es particularmente interesante. así es que se va directamente de la descripción del sistema en el mundo real a la descripción del sistema en el código. Cuando se hereda de un tipo existente. esto dignifica que la clase derivada es del mismo tipo que su clase base. Por supuesto. y mas importante. En el ejemplo previo.com . Dado que la clase base y la clase derivada tiene la misma interfase. también tienen el mismo comportamiento. una de las dificultades que las personas tienen con el diseño orientado a objetos es lo que es muy simple obtener partiendo desde el principio a el fin.Convertir la solución en los mismos términos del problema es tremendamente beneficioso porque no necesita un montón de modelos intermedios para obtener de una descripción de un problema a la descripción de la solución. Esto es. todos los mensajes que se puede enviar a los objetos de la clase base pueden ser enviados a los objetos de la clase derivada. se crea un nuevo tipo. los métodos de la interfase de la clase base estarán presentes también en la derivada. “un circulo es una forma”. el tipo de jerarquía es el modelo primario. Este tipo de equivalencia mediante herencia es una de los puertas de enlace fundamentales para el entendimiento del significado de la programación orientada a objetos. Eso significa que los objetos de la clase derivada no solo tienen el mismo tipo . debe haber algún código para ejecutar cuando un objeto recibe un mensaje en particular. Dado que conocemos el tipo de la clase por los mensajes que se le pueden enviar. Con los objetos. Esto es. se duplica la interfase de la clase base. Una mente entrenada a ver soluciones complejas queda a menudo perplejo por la simplicidad al comienzo.BruceEckel.

por momentos. la solución perfecta a su problema. 37 . no son parte de la interfase de la clase base. lo que no es necesariamente cierto. Este proceso de descubrimiento y iteración de su diseño sucede regularmente en la programación orientada a objetos. Sin embargo. se debe ver mas de cerca la posibilidad de que su clase base pueda también necesitar de estas funciones adicionales. Esto es referido como sobrescribir la función. Estas funciones nuevas. Este uso simple y primitivo de la herencia es. La segunda y mas importante forma de diferenciar la nueva clase es cambiar el comportamiento de uno o mas funciones de la clase base. La primera es bastante directa: simplemente se agregan funciones completamente nuevas a la clase derivada.Hay dos maneras de diferenciar la clase derivada nueva de la clase base original. Esto significa que la clase base simplemente no hace mucho de lo que se quiere que haga. así es que se agregan mas funciones. A pesar de que la herencia puede a veces implicar (especialmente en Java. donde la palabra clave que indica herencia es extends) que se esta agregando nuevas funciones a la interfase.

BruceEckel. se puede sustituir un objeto de la clase derivada por un objeto de la clase base. “Estoy utilizando la misma función de interfase aquí. pero la sustitución no es perfecta porque las nuevas funciones no son accesibles 38 Pensando en Java www. Esto se puede realizar a través de sustitución pura. A menudo nos referimos a la relación entre la clase base y las clases derivadas en este caso como una relación es-una. simplemente se crea una nueva definición para la función en la clase derivada. pero quiero hacer algo diferente con mi nuevo tipo”. porque se puede decir “un circulo es una forma”. de esta manera se extiende la interfase y se crea un nuevo tipo. El nuevo tipo puede seguir siendo sustituido por el tipo base. En cierto sentido.Para sobrescribir una función. Una prueba para la herencia es determinar cuando se puede afirmar la relación es una acerca de las clases y hace que tenga sentido. esta es la forma ideal de tratar la herencia. Relación es-una contra es-como-una Hay un fuerte debate que sucede con la herencia: Debe la herencia sobreescribir solo las funciones de la clase base (y no agregar nuevas funciones miembro que no están en la clase base)? Esto significaría que el tipo derivado es exactamente el mismo tipo que la clase base dado que es exactamente la misma interfase. Hay veces en que se debe agregar elementos a la nueva interfase para un tipo derivado. Se puede decir.com . Como resultado. y a menudo es nombrada como ley fundamental de sustitución.

pero puede hacer mas. Dado que el sistema de control de la casa está diseñado solamente para controlar la refrigeración. esto es. Cuando se ve la ley fundamental de sustitución es fácil sentir como que esta estrategia (sustitución pura) es la única forma de hacer las cosas. Claro. Este es nuevo acondicionador es-como el viejo aire acondicionado. y debería ser renombrado a “sistema de control de temperatura” así es que se puede incluir el calor -en el punto en que el la ley fundamental de sustitución trabajara. tiene una interfase que permite controlarlo y refrigerar cuando es necesario. Imaginemos que el aire acondicionado se rompe y lo remplaza con un equipo de aire acondicionado que puede calentar y refrigerar. y que de echo es bonito si su diseño trabaja de esta forma. así es que se puede realmente decir que es exactamente lo mismo. considere un aire acondicionado. y el sistema existente no conoce nada acerca de esta exceptuando la interfase original. Con análisis ambas clases suelen ser razonablemente obvias. 5 Mi término 39 . La interfase del nuevo objeto ha sido extendida. esta restringido a comunicar la parte para refrigerar del nuevo objeto. Por ejemplo. el diagrama anterior es un ejemplo de que puede suceder en el diseño y en el mundo real. Pero encontrará que hay veces donde es igualmente claro que se agreguen nuevas funciones a la interfase de una clase derivada. una vez que se ve que el diseño comienza a aclarar que la clase base “sistema de refrigeración” no es suficientemente general.desde el tipo base. Esto puede ser descrito como una relación es-como-una5. Sin embargo. Supongamos una casa que esta cableada con todos los controles para el aire acondicionado. el nuevo tipo tiene la interfase del viejo pero también contiene otras funciones.

el programador no necesita saber que pieza de código será ejecutada. borradas y movidas. entonces cuando se agregue un nuevo tipo. que es lo que hace? Por ejemplo. Esto permite escribir código que no depende de tipos específicos. así es que esas funciones simplemente enviaban un mensajes a el objeto forma. Por ejemplo. ¿Así es que como sucede esto. etc. teníamos funciones que manipulaban formas genéricas sin considerar si eran círculos. en el siguiente diagrama el objeto ControladorDePajaros solo trabaja con objetos Pa jaros genéricos. a un cuadrado o a un triángulo y el objeto ejecutará el código apropiado dependiendo de su tipo específico. no importaba acerca de como el objeto hacía frente con el mensaje. Esta habilidad de extender un programa fácilmente derivando nuevos tipos es importante porque mejora enormemente los diseños reduciendo el costo del mantenimiento de software. y agregar nuevos tipos es la forma mas común de extender un programa orientado a objetos para manejar nuevas situaciones. si una función va a indicarle a una forma genérica que se dibuje sola. ¿Por consiguiente.Objetos intercambiables con polimorfismo Cuando se trata con jerarquía de tipos. En el ejemplo de las formas. Sin embargo. cuando se trata con objetos derivados como tipos base genéricos (círculos como formas. triángulos o alguna forma que no había sido incluso definida todavía. todas las formas podían ser dibujadas. cormoranes como pájaros. cuadrados. Si no se tiene que saber que pieza de código será ejecutada. el compilador puede saber precisamente que parte del código es ejecutada.). Tal código es no puede ser afectado por los nuevos tipos adicionales. en lugar de eso se quiere tratar con su tipo base. cuando llamamos a mover() a pesar de que ignoramos el tipo específico de Pajaro. se puede derivar un nuevo tipo de una forma llamada pentágono sin modificar las funciones que tratan solo con las formas genéricas. hay un problema aquí. el compilador no podrá saber en tiempo de compilación que pieza de código precisamente será ejecutada. a menudo se quiere tratar un objeto no como el tipo específico que es. entonces. Esto es conveniente de la perspectiva del ControladorDePajaros porque no se tiene que escribir código especial para determinar el tipo exacto de Pajaro con el cual se esta trabajando.com . bicicletas como vehículos. o el comportamiento del Pajaro. la función dibujar puede ser aplicada igualmente a un círculo. Este el punto integral . o un pájaro genérico que se mueva. el correcto 40 Pensando en Java www.cuando un mensaje es enviado.BruceEckel. o dirigir un vehículo genérico. y no conoce que hace el tipo exactamente. el código que se ejecute puede ser diferente sin requerir cambios en la función de llamada.

el programa no puede determinar la dirección del código hasta el tiempo de ejecución. Cuando se envía un mensaje a un objeto. En la POO. El compilador no esta seguro de que la función exista y realiza un chequeo de tipo con los argumentos y retorna un valor (un lenguaje en el cual esto no es verdad es llamado weakly typedque significa que son poco restrictivos con los tipos). Esto significa que el compilador genera una llamada a un nombre de función específica. Para realizar enlazado diferido.comportamiento ocurrirá (Un Ganzo corre. el objeto verdaderamente resuelve que hacer con ese mensaje. La llamada a función generada por un compilador no orientado a objetos produce lo que es llamado enlazado temprano un . Para solucionar este problema. Cuando se envía un mensaje a un objeto. y el enlazador resuelve esta llamada a la dirección absoluta del código que será ejecutado. De esta manera. Java utiliza un pequeño trozo de código en lugar de la llamada absoluta. término que tal vez no se ha escuchado antes porque nunca se ha hecho de otra forma. Esto causó problemas. En algunos lenguajes (C++. y no conoce el código exacto a ejecutar. vuela o nada y un Pinguino corre o nada)? La respuesta es la peculiaridad primaria de la programación orientada a objetos: el compilador no puede hacer una llamada a una función en el sentido tradicional. así es que en Java el enlazado 41 . por defecto. el código que será llamado no se determina hasta el tiempo de ejecución. En estos lenguajes. utilizando información almacenada en el objeto (este proceso esta cubierto en mayor detalle en el Capítulo 7). cada objeto puede comportarse diferente de acuerdo a el contenido de esa porción de código. así es que otro esquema es necesario cuando un mensaje es enviado a un objeto genérico. Este código calcula la dirección del cuerpo de la función. los lenguajes orientados a objetos utilizan el concepto de enlazado diferido. en particular) se debe explícitamente indicar que se quiere tener la flexibilidad de las propiedades del enlazado diferido. las funciones miembro no son saltan de forma dinámica.

un Circulo puede aceptarla.BruceEckel.borrar().dinámico se realiza por defecto y no necesita recordad palabras clave extras para obtener polimorfismo. Si se escribe un método en Java (como se verá mas adelante que se hace): void hasCosas(Curva s) { s. Considere el ejemplo de la forma. así es que independientemente de el tipo de objeto que se esta dibujando o borrando.es agregado mediante herencia. La llamada a hasCosas() automáticamente trabaja correctamente. Considerando la línea: hasCosas(c). De esta forma el programa es extensible. hasCosas (c). hasCosas (t). 42 Pensando en Java www. el código que se escribirá trabajara exactamente igual de bien para el nuevo tipo de Curva como lo hace con los tipos existentes. Triangulo t = new Triangulo(). Que sucede aquí si es un Circulo es pasado en una función que espera una Curva. // . Este código es desacoplado de la información específica del tipo. con el tipo base en la parte superior y la clase derivada de acomoda hacia abajo. s.com . hasCosas (l). Dado que Circulo es una Curva puede ser tratado como una por hasCosas(). Llamamos a este proceso de tratar el tipo derivado como si fuera su tipo base conversión ascendente o upcasting. Si alguna otra parte del programa utilizará la función hasCosas(): Circulo c = new Circulo(). Esto es realmente un truco maravillosamente bonito. Y. Así es que es algo completamente seguro y lógico de hacer.. El nombre cast es utilizado en el sentido de conversión y el up viene de la forma en que el diagrama de herencia es típicamente arreglado.dibujar(). convertir a un tipo base es moverse arriba en el diagrama de herencia: “upcasting”. De esta forma. si un nuevo tipo -Un Hexagono. sin importar el tipo exacto del objeto. La familia de clases (todas basadas en la misma interfase uniforme) fue diagramada antes en este capítulo. Esto es.. Para demostrar polimorfismo queremos escribir una sola parte de código que ignore los detalles específicos del tipo y se comunique solo con la clase base. por ejemplo. cualquier mensaje que hasCosas() pueda enviar a una Curva. y de esta manera es mas simple de escribir y mas fácil de entender. } Esta función conversa con cualquier Curva. Linea l = new Linea().

Si se esta escribiendo ese tipo de código. se esperaría que se terminara llamando a la versión de borrar() y dibujar() para la clase base Curva. y ten cuidado de los detalles correctamente”. // . no se puede conocer exactamente que tipo esta manejando. s. y mas importante aún es saber como diseñar con el. Y todavía las cosas correctas suceden gracias a el polimorfismo. solo se necesita decir “Eres una forma.Un programa orientado a objetos contiene alguna conversión ascendente en alguna parte. Debe percibirse que no dice “Si eres un Circulo. es confuso y se necesitará cambiarlo cada vez que se agrega un nuevo tipo de Curva. Cuando se envía un mensaje a un objeto. El compilador y el sistema en tiempo de ejecución maneja los detallas. Aquí. el comportamiento correcto se sucede basado en el tipo actual de Curva. cuando el compilador de Java esta compilando el código para hasCosas(). de algún modo las cosas correctas se suceden.. que verifica todos los tipos que una Curva puede actualmente ser. como se ha mencionado antes. 43 . has esto. Lo que es impresionante acerca del código de hasCosas() es que. Esto es asombroso porque.. y no para la clase Circulo. Cuadrado o Linea . aún cuando una conversión ascendente esté involucrada.draw(). dado que es como se desacopla uno del conocimiento del tipo exacto con el que se esta trabajando. Así es que comúnmente. pero cuando el mensaje dibujar() es enviado a una Curva anónima. se que te puedes borrar() y dibujar() sola.erase(). has aquello. si eres un Cuadrado . todo lo que se necesita saber es que sucede. etc”. Vea el código en hasCosas(): s. Llamando a dibujar() para un Circulo produce que se ejecute un código diferente cuando se llama a draw() para un Cuadrado o una Linea . el objeto hará las cosas correctas. hazlo.

BruceEckel. este método debe ser implementado. herencia y polimorfismo. Esto es una herramienta para forzar un diseño en particular. solo realizando una conversión ascendente de esta se puede utilizar la interfase. así es que le da al programador la elección. Un método abstracto puede ser creado solo dentro de una clase abstracta. Cuando la clase es heredada. se quiere que la clase base presente solo la interfase para las clases derivadas. ¿Donde están los datos de un objeto y como es el tiempo de vida del objeto controlado? Hay diferentes filosofías de trabajo aquí. Uno de los factores mas importantes es la forma en que los objetos son creados y destruidos. ya que proporciona la perfecta separación entre interfase e implementación. Además. Arquitectura de los objetos y tiempos de vida Técnicamente. Esto es.com .Clases bases abstractas e interfases A menudo en un diseño. En busca de las máximas velocidades de ejecución. Si alguien trata de crear un objeto de una clase abstracta . si se quiere. colocando los objetos en la pila (esto es a veces llamado variables automáticas o scoped) o en el 44 Pensando en Java www. La palabra clave interface toma el concepto de la clase abstracta un paso mas adelante para prevenir del todo cualquier definición de función. Crear un método abstracto permite colocar un método en una interfase sin ser forzado a proporcionar un cuerpo de código posiblemente sin sentido para ese método. La interfase es una herramienta muy conveniente y es comúnmente utilizada. se pueden combinar muchas interfases juntas. Se puede utilizar también la palabra clave abstract para describir un método que no ha sido implementado todavía -como un cabo indicando “ha aquí una función de la interfase para todos los tipos que hereden de esta clase. pero otros temas pueden ser al menos tan importantes. El reato de esta sección cubre estos temas. mientras que se heredar de muchas clases regulares o abstractas no es posible. POO es solo acerca de clasificar datos abstractos. el compilador lo previene. pero en este punto no tiene ninguna implementación todavía”. C++ toma la estrategia de que el control y la eficiencia es el tema mas importante. el almacenamiento y el tiempo de vida puede ser determinado cuando el programa es escrito. o la clase heredada se convierte también en abstracta. si no se quiere que cualquiera cree un objeto de la clase base. Esto se logra haciendo la clase abstracta utilizando la palabra clave abstract.

La segunda estrategia es crear objetos dinámicamente en un fondo común de memoria llamada el heap. y el tipo de los objetos con el que se está escribiendo el programa. y otro para regresarlo). el manejo de un almacén. y un control que puede ser muy valioso en algunas situaciones. La estrategia dinámica asume lógicamente que los objetos tiende a ser complicados.área de almacenamiento estático. exlusivamente6. y es el tiempo de vida de un objeto. Hay otro tema. Sin embargo. esto es muy restrictivo. Sin embargo. En un lenguaje como C++. el compilador determina cuanto tiempo el objeto dura y puede automáticamente destruirlos. Java proporciona una característica llamada recolector de basura que automáticamente descubre cuando un objeto no esta mas en uso y lo destruye. el recolector de basura proporciona un nivel mucho mas alto de seguridad contra el 6 Los tipos primitivos. cuales son sus tiempos de vida o el tipo exacto que es. lo que puede conducir a lagos de memoria si no se hace correctamente (y esto es un problema común en programas C++). así es que una sobrecarga extra para encontrar espació y liberarlo no tendrá un impacto importante en la creación de un objeto. Cada vez que se quiere crear un objeto. se sacrifica flexibilidad porque se debe saber exactamente la cantidad. Un recolector de basura automáticamente descubre cuando un objeto no se utiliza mas y lo destruye. Esto es determinado con el estímulo del momento en que el programa se esta ejecutando. simplemente se crea uno en el heap en el momento que se necesita. sin embargo. Dado que el almacenamiento es manejado dinámicamente. como se aprenderá mas adelante. son un caso especial. el tiempo de vida. si se crea en el heap el compilador no tiene conocimiento de este tiempo de vida. no se necesita saber hasta en tiempo de ejecución cuantos objetos se necesitan. Si se necesita un nuevo objeto. Java utiliza una segunda estrategia. o el control de tráfico aéreo. Mas importante. la cantidad de tiempo requerido para asignar el espacio en el heap es significativamente mayor que el tiempo para crear un espacio en la pila (Crear espacio en la pila es a menudo una simple instrucción de Ensamblador para mover el puntero de la pila abajo. Un recolector de basura es mucho mas conveniente porque reduce el número de temas que se deben tener presentes y el código que se debe escribir. se debe determinar mediante un programa cuando un objeto es destruido. Además. la gran flexibilidad es esencial para solucionar el problema general de programación. se utiliza la palabra clave new para crear una instancia dinámica del objeto. 45 . Si se esta tratando de solucionar un problema mas general como un diseño asistido por computadoras. Con los lenguajes que se permite crear objetos en la pila. en tiempo de ejecución. En esta estrategia. Esto coloca una prioridad en la velocidad con que el espacio es asignado y liberado.

¿Como se puede saber cuanto espacio crear para estos objetos? No se puede. colas. La solución a la mayoría de los problemas en diseños orientados a objetos parecen frívolos: se crea otro tipo de objeto. y en otras (Java.BruceEckel. Pero traer los elementos puede ser mas problemático. Pero hay mas. y otras para traer estos elementos afuera. En C++.com . Afortunadamente. usualmente hay funciones para agregar elementos a un contenedor. un lenguaje de POO bueno viene con un grupo de contenedores como parte del paquete. generalmente llamado contenedor (también llamado una colección. Claro. Así es que no se necesita saber cuantos objetos se van a almacenar en un contenedor. Pascal orientado a objetos tiene contenedores en su Librería de Componentes Visuales (VCL Visual Component Library). Java también tiene contenedores en su librería estándar. así es que se puede elegir el tipo particular que entre en sus necesidades. y una lista enlazada permite insertar en todos los elementos. El nuevo tipo de objeto que soluciona este problema en particular contiene referencias a otros objetos. un contenedor genérico es considerado suficiente para todas las necesidades. árboles. Todos los contenedores tienen alguna forma de poner y de sacar las cosas.problema insidioso de los lagos de memoria (que ha tirado a muchos proyectos de C++ de rodillas). se expandirá lo que sea necesario para acomodar todo lo que se coloque dentro de el. Colecciones e iteractores Si no se tiene conocimiento de cuantos objetos se necesitan para resolver un problema en particular. es parte de la librería estándar de C++ y es a veces llamado la Librería Estándar de Plantillas (STL Standar Template Library). dado que una única función de selección puede ser restrictiva. Smalltalk tiene un grupo completo de contenedores. 46 Pensando en Java www. por ejemplo) la librería tiene diferentes tipos de contenedores para diferentes necesidades: se tiene un vector (llamado un ArrayList en Java) para acceder continuamente a todos los elementos. Este nuevo objeto. o cuanto tiempo van a durar. pilas. Las librerías de contenedores pueden incluir grupos. etc. tablas hash. tampoco conoce como almacenar esos objetos. Solo se crea un objeto contenedor y se deja que el se encargue de los detalles. se puede hacer la misma cosa con un arreglo. el que está disponible en la mayoría de los lenguajes. En algunas librerías. dado que esa información no se conoces hasta la ejecución del programa. por ejemplo. El resto de esta sección da un vistazo en los factores adicionales concernientes a los tiempos de vida y arquitecturas. pero la librería de Java utiliza el término con un sentido diferente así es que utilizaremos “contenedores”).

cambiar a una ArrayList. es abstraído para ser simplemente una secuencia. Si un solo tipo de secuencia satisface todas las necesidades. Esto da una flexibilidad de cambiar fácilmente las capas mas bajas de la estructura de datos sin molestar el código del programa. Primero. mediante el iterator. El iterator permite atravesar esa secuencia sin preocuparse acerca de la estructura de las capas mas bajas -esto es. Como una clase. se debe comenzar con una LinkedList y cuando se ajusta el rendimiento.0 y 1. en una LinkedList es mucho mas costoso moverse a través de la lista para seleccionar de forma aleatoria un elemento. Una de estas proporciona una solución mas flexible a el problema que las otras. Pero ciertas operaciones pueden tener costos radicalmente diferentes costos. El acceso aleatorio a elementos en una ArrayList es una operación de tiempo constante. toma la misma cantidad de tiempo ver el elemento que se ha seleccionado. Dado que la abstracción mediante un iterator. todo lo que se quiere realmente es una secuencia que puede ser manipulada para solucionar su problema.¿Que si se quiere manipular o comparar un grupo de elementos en un contenedor en lugar de solo uno? La solución es un iterator. es mucho mas barato en una LinkedList que en un ArrayList. contenedores diferentes tienen diferentes eficiencias para ciertas operaciones. Una pila tiene una interfase y un comportamiento distintos a una cola. y toma mucho mas tiempo encontrar un elemento que este mas adelante en la lista que un elemento que se encuentra en el medio de la secuencia. que es una forma diferente de grupo o lista.1) con un iterator estándar. Desde un punto de vista del diseño. Java 2 tiene además muchas librerías de contenedores mucho mas completas que contienen un iterator llamado Iterator que hace mucho mas que el viejo Enumeration. no importa si sea una ArrayList. Ambos son simples secuencias que pueden tener interfases y comportamientos externos idénticos. Sin embargo. los contendores proporcionan distintos tipos de interfases y de comportamientos externos. El contenedor. para todas las clases contenedoras. si se quiere insertar un elemento en el medio de una secuencia. Segundo. llamado Enumeration. Por el otro lado. una Stack o cualquier otra. Estas y otras operaciones tienen diferentes eficiencias dependiendo de la estructura de capas inferiores de la secuencia. proporciona un nivel de abstracción. Hay dos razones para tener la necesidad realizar una elección de contenedores. El mejor ejemplo es una ArrayList y una LinkedList. 47 . una LinkedList. En la fase de diseño. se puede cambiar de uno al otro con un mínimo impacto en el código. no hay razón para tener diferentes tipos. Esta abstracción puede ser utilizada para separar los detalles del contenedor del código que accede a ese contenedor. Java comienza (en la versión 1. que es un objetos cuyo trabajo es seleccionar los elementos dentro de un contenedor y presentarlos a el usuario de ese iterator.

Si se conoces que se puede realizar ciertas operaciones básicas en cada objeto de su sistema. pero cuando se quiere realizar una programación totalmente orientada a objetos se debe crear su propia jerarquía para proporcionar la misma comodidad que se obtiene con otros lenguajes de POO. y el recolector de basura puede de esta manera enviar los mensajes apropiados a cada objeto en el sistema. Una jerarquía de raíz única hace mucho mas fácil implementar un recolector de basura (lo que es convenientemente creado en Java). La alternativa (proporcionada por C++) es que no se necesita saber que todo es del mismo tipo fundamental. Se puede necesitar solo un tipo de secuencia.com . Si se esta comenzando de la nada.BruceEckel. Si una 48 Pensando en Java www. no importa como está implementado (un concepto básico con muchos tipos de objetos). Se puede aún imaginar la abstracción del contenedor “perfecto”. que puede automáticamente cambiar su implementación de capas inferiores de acuerdo a la forma en que sea utilizado. junto con la creación de todos los objetos en el heap. ¿Vale la pena la “flexibilidad” extra de C++? Si se necesita -si se tiene un gran inversión en C. alguna interfase incompatible será utilizada. Todos los objetos en una jerarquía de raíz única (como la que proporciona Java) pueden garantizarse cierta funcionalidad. Esto deja claro que los beneficios de la jerarquías de raíz única son muchas. Y en cualquier nueva librería que se obtenga. Desde un punto de vista de compatibilidad regresiva esto encaja en el modelo de C mejor y puede reflexionarse como menos restrictivo. entonces la diferencia de costos entre una ArrayList y una LinkedList puede no importar. Una jerarquía de raíz única.Al final . recuerde que un contenedor es solo un gabinete para almacenar objetos en el. Si se esta trabajando en un ambiente de programación que tiene costos operativos en el armado debido a otros factores. La jerarquía de raíz única Uno de los temas de la POO que especialmente sobresale dado la introducción de C++ es si todas las clases deben ser heredadas de una sola clase base. así es que de última todas tienen el mismo tipo. otras alternativas como Java a menudo son mas productivas. El soporte necesario puede ser instalado en la clase base. Esto requiere esfuerzo (y probablemente herencia múltiple) para trabajar con la nueva interfase en su diseño.es bastante valioso. realmente simplifica el pasaje de argumentos (uno de los temas mas complejos de C++). En Java (como en todos los otros lenguajes orientados a objetos) la respuesta es “si” y el nombre de esta última clase base es simplemente Object. Todos los objetos en una jerarquía de raíz única tienen una interfase en común. Si ese gabinete soluciona todas sus necesidades.

Java proporciona esta librería. Esto hace a los contenedores fáciles de reutilizar. que suele satisfacer la mayoría de las necesidades. tiene sentido tener una librería de contendores creadas de una forma reutilizable. se esta realizando una conversión descendente en la jerarquía hacia un tipo mas específico. y para permitir una mayor flexibilidad en la programación. así es que un contenedor que almacena Objects puede almacenar cualquier cosa. Dado que la información de tipo en tiempo de ejecución esta garantido de estar en todos los objetos. nunca se terminará con un objetos de un tipo que no se pueda determinar. Librerías de colecciones y soporte para su fácil utilización Dado que un contenedor es una herramienta que se utilizará frecuentemente. es dificultoso implementar un recolector de basura. Conversión descendiente y plantillas genéricas Para hacer estos contenedores reutilizables. así es que se puede tomar una del estante y colocarla en el programa. almacenan un tipo universal en Java que ha sido previamente mencionado: Object. 49 . ¿Así es que como se vuelve a algo con una interfase útil para el objeto que se colocó en el contenedor? Aquí es cuando las conversiones se utilizan nuevamente. Esta jerarquía de raíz única significan que todo es un objeto.jerarquía de raíz única y un sistema para manipular un objeto mediante su referencia. Esta manera de convertir es llamada conversión descendente o downcasting. por ejemplo. y mas tarde se pregunta por ellas nuevamente. como lo es el manejo de excepciones. dado que el contendor almacena solo Objetos. simplemente se agrega referencias a objetos a el. de esta forma pierde su identidad. Para utilizar tal contenedor. Esto es especialmente importante con operaciones a nivel de sistemas. pero no se sabe que un Object es necesariamente un Circulo o una Curva así es que es difícilmente seguro realizar una conversión descendente a no ser que se conozca exactamente con lo que se esta tratando. Cuando se trae de vuelta se obtiene una referencia a un Object. se sabe. pero esta vez no se esta convirtiendo hacia arriba en la jerarquía de herencia a un tipo mas general. cuando se agrega su referencia a objetos en el contendor se le realiza una conversión ascendente a Object. Pero. que un Circulo es una Curva así es que es fácil realizar una conversión ascendentes. Con la conversión ascendente. y no una referencia al tipo que se colocó.

com . sin embargo. Una situación simple en programación es la pregunta de como es limpiado un objeto sin que sea un desafío: se crea un objeto. el compilador puede adaptar ese contenedor para que acepte solo Curvas y traiga solo Curvas. Esto no es difícil. el que describirá brevemente. y un esfuerzo extra del programador. se encuentran situaciones en las cuales la situación es mas compleja. dado que si se realiza una conversión descendente a algo equivocado se obtendrá un error en tiempo de ejecución llamado una excepción. en parte porque C++ no tiene una jerarquía de raíz única. que son clases que el compilador puede automáticamente adaptar para trabajar con tipos particulares. se utiliza todo lo necesario. eliminando la necesidad de realizar una conversión descendente y un posible error? La solución son los tipos parametrizados. con un contenedor parametrizado. Supongamos. La conversión descendente y las pruebas en tiempo de ejecución requiere tiempo extra para ejecutar el programa. o un sistema de control de rentas o una casa para alojar animales). sin embargo. 50 Pensando en Java www. se debe tener alguna forma de recordar exactamente que son antes de realizar una conversión descendiente apropiada. que se está diseñando un sistema para manejar el tráfico aéreo para un aeropuerto (El mismo modelo puede trabajar también para manejar cajas de mercadería en un almacén. El dilema del cuidado de la casa: ¿Quien debería limpiar? Cada objeto requiere recursos para existir. Al comienzo parece simple: se crea un contenedor para contener aeroplanos. ¿No tendría sentido para alguien crear un contendor de tal forma que conozca los tipos que almacena. Sin embargo. la palabra clave que implementa los tipos parametrizados es “template”. el mas notable es la memoria. Por ejemplo. y entonces debe ser destruido.utilizando la jerarquía de raíz única. simplemente se borra el aeroplano adecuado cuando un aeroplano deja la zona. Los tipos parametrizados son una importante parte de C++. luego se crea un nuevo aeroplano y se coloca en el contenedor para cada aeroplano que ingresa en la zona de control de tráfico aéreo. Para limpiarlo. Cuando un objeto no se necesita mas debe ser limpiado para que esos recursos sean liberados para su reutilización. sin embargo. por ejemplo. Java actualmente no tiene tipos parametrizados dado que es posible obtenerlo mediante torpemente sin embargo.No es completamente peligroso. un propósito actual de los tipos parametrizados utilizan una sintaxis que es sorprendentemente similar a plantillas de C++. En C++. Cuando se obtienen referencias a objetos de un contenedor.BruceEckel.

Crear objetos en el heap puede ser mucho mas costoso. Recolectores de basura versus eficiencia y flexibilidad ¿Si todo esto es una buena idea. cuando se crea un objeto aeroplano se coloca en este segundo contenedor se es un aeroplano pequeño. tal vez lo datos no necesitan atención inmediato como el controlador de la función principal. y este libera automáticamente la memoria de ese objeto. Ahora el problema es más difícil: ¿Como es posible saber cuando destruir los objetos? Cuando se termina con el objeto. por que no si hizo la misma cosa en C++? Ciertamente hay un precio a pagar por toda esta conveniencia en la programación. así es que no se puede utilizar en ciertas situaciones. Este mismo problema se origina en un gran número de otras situaciones. y en sistemas de programación (como es C++) en donde se debe explícitamente borrar un objeto cuando se ha terminado con el puede resultar bastante complejo.Pero tal vez se tenga algún otro sistema de almacenar datos acerca de los aeroplano. Crear objetos en la pila es la forma mas eficiente de asignar espacio para los objetos y para liberar ese espacio. alguna otra parte del sistema podría no estar. El recolector de basura “sabe” cuando un objeto no se encuentra mas en uso. como cuando la taza de ejecución de un programa es uniformemente crítica (Estos son llamados generalmente programas en 51 . Esto (combinado con el hecho de que todos los objetos son heredados de una única clase raíz Object y que se crean objetos solamente de una forma. Luego un proceso de fondo realiza operaciones en los objetos en este contenedor durante los momentos de inactividad. siempre heredando de la clase base y creando todas las llamadas a funciones polimórficas también tiene un pequeño costo. Con Java. y es este caso son automáticamente limpiados (pero no tiene la flexibilidad de crear tantos como se quiera en tiempo de ejecución). Así es que se tiene un segundo contenedor de pequeños aeroplanos. Tal vez es un registro de los planes de vuelo de todos los pequeños aeroplanos que dejan el aeropuerto. en el heap) hace que el proceso de programación en Java mucho mas simple que la programación en C++. en C++ se pueden crear objetos en la pila. y este precio es son en costos operativos en tiempo de ejecución. Pero el recolector de basura es un problema particular porque nunca se sabe realmente cuando va a iniciarse o cuanto tiempo tardará en realizar la operación. Esto significa que hay una inconsistencia en la taza de ejecución de un programa en Java. Se tiene pocas decisiones para tomar y cientos para vencer. Como se ha mencionado antes. el recolector de basura esta diseñado para encargarse del problema de liberar memoria (a pesar de que no incluye otros aspectos de la limpieza de un objeto).

Esta meta fue alcanzada.tiempo real. Una excepción es un objeto que es “lanzado” desde el sitio del error y puede ser capturado por un manejador de excepciones adecuado diseñado para manejar ese tipo particular de error. en ese punto). Manejo de excepciones: tratando con errores. trataron de cortejar a los programadores C (y con mucho éxito. no necesita interferir con la ejecución normal de su código. Una excepción no puede ser ignorada. Finalmente. Esto hace la escritura de código simple dado que no esta constantemente forzado a verificar errores. Además. pero a cambio de eficiencia y a veces de aplicabilidad. no quisieron agregar características a el lenguaje que impactaran en la velocidad o en el uso de C++ en cualquier situación donde de otra forma los programadores puedan elegir C. Java es mas simple que C++. Y dado que se usa un camino de ejecución separado. así es que es garantido tratarla en algún punto. pero al precio de una gran complejidad cuando se programa en C++. paralelo de ejecución que puede ser tomado cuando las cosas van mal.estos esquemas pueden ser fácilmente olvidados. una excepción que es lanzado no es como un valor de error que es retornado de una función o una bandera que es configurada por una función para indicar una condición de error -estas pueden ser ignoradas. Para una significante porción de problemas de programación sin embargo. Dado que es muy duro diseñar un buen esquema de manejo de errores. Los diseñadores del lenguaje C++. las excepciones proporcionan una forma de recuperarse confiablemente de una mala situación. Un problema mayor con la mayoría de los esquemas de manejo de errores es que confían en que el programador este alerta a seguir una convención acordada que no es forzada por el lenguaje. En lugar se simplemente salir se es a menudo capaz de hacer las cosas correctas y de 52 Pensando en Java www. Esto es como si el manejador de excepciones fuera un camino diferente. muchos lenguajes simplemente ignoran este tema. Desde el comienzo de los lenguajes de programación. pasándole el problema a los diseñadores de la librería que toman medidas a medias que funcionan en muchas situaciones pero que pueden ser fácilmente evadidas. El manejo de excepciones inserta el manejo de excepciones en el lenguaje de programación y a veces incluso en el sistema operativo. generalmente ignorándolas. Java el la elección superior. el manejo de errores ha sido uno de los temas mas complicados.com . a pesar de que no todos los problemas de programación en tiempo real son tan rigurosos).BruceEckel. Si el programador no es despierto -a menudo es el caso si están apurados.

y el concepto general es llamado hilado múltiple. un usuario puede presionar un botón y obtener una respuesta rápida en lugar de ser forzado a esperar hasta que el programa termine la tarea actual. y tratar con otro problema. Dentro de un programa. el manejo de excepciones fue introducido desde el comienzo y se esta forzado a utilizarlo. Muchos problemas de programación requieren que el programa sea capaz de parar lo que esta haciendo. Pero si el sistema operativo soporta múltiples procesadores. porque en Java. Un ejemplo común es la interfase de usuario. estas piezas que se ejecutan separadamente son llamadas hilos. El manejo de excepciones existe antes que los lenguajes orientados a objetos. A pesar de que esto trabajaba bien. así es que mover un programa a un nuevo tipo de máquina era lento y caro. lo que produce programas mucho mas robustos. Inicialmente. fue difícil y no era portátil. El programa es lógicamente dividido en hilos y si la máquina 53 . Hilado múltiple Un concepto fundamental en la programación de computadoras es la idea de manejar mas de una tarea a la vez. El manejo de excepciones en Java sobresale de entre medio de los lenguajes de programación. cada hilo puede ser asignado a un procesador diferentes y ellos pueden verdaderamente correr en paralelo. Si no escribe el código para que maneje excepciones correctamente. a pesar de que en los lenguajes orientados a objetos la excepción es normalmente representada con un objetos. La solución ha sido acometida de distintas maneras. y luego retornar al proceso principal. pero hay una clase grande de problemas en los cuales se trata simplemente de partir en piezas separadas así es que la totalidad del programa puede responder de mejor manera. los hilos son solo una forma de hacer un uso del tiempo de un procesador solo. Utilizando hilos. Esta coherencia garantiza que en el manejo de errores sea mucho mas fácil.restaurar la ejecución de un programa. Una de las características mas convenientes del hilado múltiple a nivel del lenguaje es que el programador no necesita preocuparse acerca de si hay muchos procesadores o solo uno. Normalmente. Es de valor notar que el manejo de errores no es una característica de los lenguajes orientados a objetos. A veces las interrupciones eran necesarias para el manejo de tareas críticas. los programadores con conocimientos de bajo nivel de la máquina escribieron rutinas de servicio de interrupciones y la suspensión del proceso principal fue inicialmente a través de interrupciones hardware. se obtendrá un mensaje de error de compilación.

tiene mas de un procesador entonces el programa se ejecuta mas rápidamente. El hilado en Java esta realizado dentro del lenguaje. debe ser bloqueado hasta que sea utilizado. el objeto estará ahí y tendrá la misma información que en un momento antes que el programa se ejecutara. los recursos pueden ser compartidos. Entonces la siguiente vez que se ejecuta el programa.BruceEckel. Por ejemplo. lo que hace un tema complicado mucho mas simple. pero bajo ninguna circunstancia este existe cuando el programa termina. JavaSpaces (descrito en el Capítulo 15) proporciona un tipo de almacenamiento persistente de objetos. completa su tarea. Todo esto hace que los hilos parezcan bastante simples. Si se tiene mas de un hilo ejecutándose que espera acceder a el mismo recurso se tiene un problema. Persistencia Cuando se crea un objeto. He aquí un truco: compartir recursos. Así es que un hilo bloquea un recurso. después de todo. 54 Pensando en Java www. Además. si ajustes especiales. Se puede bloquear la memoria de un objeto (lo que es. Para solucionar el problema.com . este existe hasta que se lo necesite. Java proporciona soporte para “persistencia ligera”. Esto tiene sentido al principio. La razón de ser persistencia ligera es que se sigue estando forzado a realizar llamadas explícitas para realizar el almacenamiento y la recuperación. pero en el espíritu de hacer todo un objetos puede ser bastante conveniente ser capaz de declarar un objeto persistente y todos los detalles cubiertos. un recurso compartido) así es que solo un hilo puede utilizarla a la vez. Claro se puede obtener un efecto similar escribiendo la información en un fichero o en una base de datos. así es que un hilo de ejecución es representado por un objetos. como la impresora. y luego libera el bloqueo así es que alguien mas puede utilizar el recurso. dos procesos pueden simultáneamente enviar información a la impresora. hay situaciones en donde puede ser increíblemente útil si un objeto puede existir y mantener su información aún cuando el programa no esta ejecutándose. Esto se logra con la palabra clave synchronized. que significa que fácilmente se pueden almacenar objetos en el disco y luego recuperarlos. El hilado es soportado a nivel de objeto. Otros tipos de recursos deben ser bloqueados explícitamente por el programador. Java también proporciona bloqueo de recursos limitado. típicamente creando un objeto para representar el bloqueo que todos los hilos deben verificar antes de acceder a ese recurso. En algunas futuras versiones puede aparecer un soporte mas completo para persistencia.

el software que distribuye la información y las máquinas donde la información y el software residen es llamado el servidor. Una clave para el concepto cliente/servidor es que el depósito de información está ubicado centralmente así es que se puede cambiar así es que esos cambios se propagarán fuera para informar a los consumidores. Esto significa que se debe asegurar que un dato nuevo de un cliente no pasa por arriba un dato de otro cliente. a menudo en una base de datosque se quiere distribuir en demanda de un grupo de personas o máquinas. Computación cliente/servidor La idea inicial de un sistema cliente/servidor es que se tiene un depósito central de información -algún tipo de dato. La respuesta no es inmediatamente obvia si se procede de la perspectiva de la programación tradicional. y luego la muestra en la máquina remota es llamada el cliente. Tomándolo junto. solo otra lenguaje de programación de computadora. es importante porque soluciona problemas en la Web. se comunica con el servidor y toma la información. Es muy útil dar un paso atrás y ver que es realmente. se debe hacer la pregunta de por que es tan importante y por que es promovido como un paso revolucionario en la programación de computadoras. los sistemas a menudo permiten a un cliente insertar información nueva en un servidor. debe ser armado.Java y la Internet Si Java es. El concepto básico de computación cliente/servidor. “presencia” y “paginas de inicio”. Además. el almacén de información. Los problemas se levantan porque no se tiene un solo servidor tratando de servir muchos clientes de una vez. con toda esa charla de “navegar”. Generalmente. para hacer esto se debe entender los sistemas cliente/servidor. ¿Que es la Web? La Web puede parecer un poco como un misterio al principio. la procesa. e 55 . Cuando el software del cliente cambia. entonces. se cuestionan el valor y el resultado de un movimiento arrollador. depurado. de hecho. A pesar de que Java es muy útil para solucionar problemas de programación que trabajan solos. no es muy complicado. El software que reside en la máquina remota. Hay incluso crecido un reacción contra la “manía de la Internet”. un manejo de sistemas de bases de datos esta involucrado así es que el diseño “balancea” la capa de datos en las tablas para un uso óptimo. otro aspecto de la computación que esta lleno de temas confusos. o que ese dato no se pierde en el proceso de agregarlo en la base de datos (Esto es llamado proceso de transacción).

los programadores trabajan duro para descargar tareas de procesamiento. dado que tiene todos los servidores y clientes coexistiendo en una sola red de una sola vez. Pero en poco tiempo las personas comienzan a esperar mas que solo distribuir páginas desde un servidor. Este es duro de crear y difícil de utilizar. La Web como un servidor gigante La Web es actualmente un sistema gigante cliente/servidor. a menudo en la máquina del cliente. Estos son los cambios que estamos viendo en los desarrollos en la Web. el cliente) interpretará y le dará formato en la máquina local. para hacer que una base de datos busque en el servidor. y el usuario tiene que aprender una nueva interfase para cada uno. Se realiza una petición a un servidor. y cualquier atraso puede ser crucial. No necesita saber que. este pasa un fichero. o para colocar una orden (lo que requiere mucha mas seguridad que la que los sistemas iniciales ofrecen). que el navegador en la máquina (i. utilizando el llamado middleware (Middleware es utilizado también para mejorar el mantenimiento.. Y aún es crucial: las cuentas de computación cliente/servidor es aproximadamente la mitad de las actividades de programación. dado que todo lo que le importa acerca de la conexión y la interacción con un servidor a la vez (a no ser que esté saltando alrededor del mundo buscando el servidor correcto). La simple idea de distribuir información a personas tiene tantas capas de complejidad en implementación que la totalidad del problema puede ser desesperadamente enigmático. por ejemplo. su nombre. Es especialmente problemático soportar múltiples tipos de computadoras y sistemas operativos. El navegador Web fue un gran paso adelante: el concepto de que un pedazo de información pueda ser desplegado en cualquier tipo de computadora sin cambios. ciencia. El problema cliente/servidor entero necesita solucionarse de una gran forma. gobierno. para agregar mas información al servidor. inventando una nueva solución cada vez. lo que puede tornarse mas complicado y caro de lo que se puede pensar.e. pero a veces en otras máquinas en el sitio del servidor. Finalmente. Esto es un poco peor que eso. Sin embargo. hay un tema importante de rendimiento: se debe tener cientos de clientes realizando peticiones a su servidor a la vez.com . Lo que hemos hecho en el pasado es armar soluciones individuales para problemas individuales. Es responsable de todo desde la toma de órdenes y transacciones de tarjetas de crédito a la distribución de algún tipo de datos stock de un mercado. Para minimizar la latencia.BruceEckel. Inicialmente fue un proceso simple de una vía.instalado en la máquina del cliente. Ellos esperan capacidades totales de cliente/servidor así es que el cliente puede alimentar el servidor. los navegadores a pesar de eso son primitivos y 56 Pensando en Java www.

los estándares gráficos fueron mejorados para permitir mejores animaciones y video dentro de los navegadores. La respuesta de un programa CGI depende de cuantos datos deba enviar. y tendían a atascar el servidor y la Internet a causa de que en cualquier momento se necesitaba hacer algo que requería programación se tenía que enviar información al servidor para ser procesada. Sin embargo. iniciar un programa CGI tiende a ser lento). de la misma forma que del tiempo de carga en el servidor y de la Internet (Por encima de esto. esto era seguro. Este sometimiento pasa a través de la Common Gateway Interfase (CGI) proporcionada por todos los servidores. pero la interactividad era completada por el servidor. Para comenzar con algo. Dado que el navegador era solo un visor no podía realizar inclusive las tareas mas simples de computación (Por el otro lado. Muchos sitios Web poderosos hoy están armados estrictamente en CGI. Estos programas pueden ser escritos en muchos lenguajes. La acción mas común es ejecutar un programa ubicado en el servidor en un directorio comúnmente llamado “cgi-bin” (si se mira la dirección en la barra de título de la ventana de su navegador cuando se presiona el botón en la página Web. los sitios Web creados con programas CGI pueden rápidamente volverse demasiado complicados de mantener. Ellos no eran particularmente interactivos. Programación del lado del cliente El diseño servidor navegador inicial de la Web proporcionaba contenido interactivo. diferentes estrategias fueron tomadas. así es que puede ser instalado en cualquier servidor sin pensar en el procesador o en el sistema operativo. y se puede de hecho hacer casi todo. Para solucionar este problema. y también esta el problema del tiempo de respuesta. El servidor producía páginas estáticas para el navegador cliente. El HTML básico contiene mecanismos simples para reunir datos: cajas para la entrada de texto. El resto de los problemas pueden ser resueltos solo incorporando la habilidad de ejecutar programas en el cliente. Perl es una elección común porque esta diseñado para manipular texto y es interpretado. Esto podía tomar varios segundos o minutos para encontrar algo mal escrito en su petición. Esto es llamado programación del lado del cliente. bajo el navegador. listas y listas desplegables. El texto pasado le dice a la CGI que hacer con el. casillas de verificación. se puede a veces ver “cgi-bin” con todas las galimatías ahí).rápidamente se retrasaron por las demandas efectuadas. botones de radio. dado que no se podía ejecutar ningún programa en la computadora local que pudiera contener errores o virus). Los diseñadores iniciales 57 . así como botones que solo pueden ser programados para reiniciar los datos en los formularios o para “someter” los datos del formulario en el servidor. que simplemente las interpretaba y desplegaba.

y con la estrategia del HTML original están sentadas ahí. Este le indica al navegador “desde ahora puede realizar esta nueva actividad” (Se necesita bajar el plugin solo una vez). De esta manera. se debe seguir programando. simplemente esperando que el servidor entregue la siguiente página. arma una página HTML. Los parámetros son al menos los mismos. El resto de esta sección se proporciona una vista general de los temas y estrategias en la programación del lado del cliente. pero escribir un plug-in no es una tarea trivial. Al final. Programación del lado del cliente significa que el navegador Web es aparejado a hacer cualquier trabajo que pueda. Algunos comportamientos rápidos y poderosos son agregados a los navegadores mediante plug-ins.com . los plug-ins proporciona una “puerta trasera” que permite la creación de nuevos lenguajes de 58 Pensando en Java www. Plug-ins Uno se los pasos adelante mas significantes en la programación es el desarrollo de plug-in. se debe entonces guardar la página e intentarlo nuevamente. pero la plataforma es diferente: un navegador Web es como un sistema operativo limitado. es poco elegante.BruceEckel. El valor de los plug-in para la programación del lado del cliente es que permite a un experto programador desarrollar un nuevo lenguaje y agregar ese lenguaje a el navegador sin el permiso del fabricante del navegador. y el resultado para el usuario es mucha mas velocidad y una experiencia mas interactiva en su sitio Web. el servidor comienza un programa CGI que descubre un error. y no es algo que se quiera hacer como parte del proceso de crear un sitio en particular. La solución es la programación del lado del cliente. Esta es una forma de que un programador agregue nueva funcionalidad a el navegador bajando una pieza de código que coloca solo en una ubicación apropiada en el navegador. Esto no es solo lento. la página le informa de este error y luego envía la página otra vez hacia usted. Se presiona el botón para enviarlo en una página. y estas historias de grupos desequilibrantes de problemas y soluciones producida por la programación del lado del cliente. Por ejemplo. El problema que se discute acerca de la programación del lado del cliente es que no son muy diferentes de las discusiones de la programación en general. Muchas máquinas que ejecutan navegadores Web tiene poderosos motores capaces de hacer enormes trabajos. Y no se tiene duda de que se ha tenido experiencia directa con algo tan simple como una validación de datos en un formulario de entrada. cualquier tipo de gráfico dinámico en prácticamente imposible de realizar con consistencia porque un fichero GIF debe ser creado y movido del servidor al cliente para cada versión del gráfico.de la Web no visualizaron cuan rápido este ancho de banda puede ser gastado por el tipo de aplicaciones que las personas desarrollan. los datos son enviados de vuelta al servidor.

El problema es que se debe renunciar a que el código sea expuesto a que cualquiera lo vea (y hurte). un lenguaje de guiones puede solucionar el 80 por ciento de los problemas encontrados en la programación del lado del cliente. si se es elocuente con Visual Basic o Tcl/Tk. Los problemas pueden encajar muy bien en ese 80 por ciento. y algunas de las herramientas automáticamente crean páginas utilizando JavaScript. y Tcl/Tk. y dado que los lenguajes de guiones pueden permitir un desarrollo fácil y rápido. Generalmente. Sin embargo. hay probablemente mas libros de JavaScript disponibles que para los demás lenguajes de navegadores. VBScript (que se ve como Visual Basic). Además. JavaScript es probablemente el mas comúnmente soportado.programación del lado del cliente (a pesar de que no todos los lenguajes son implementados como plug-ins). debería probablemente considerar un lenguaje de guiones antes de verse con una solución mas compleja como lo es la programación Java o ActiveX. El lenguaje de guiones mas abordado por los navegadores es JavaScript (que no tiene nada que ver con Java. Los lenguajes de guiones tienden a ser razonablemente fáciles de entender y. probablemente se será mas productivo utilizando estos lenguajes de guiones que aprender uno nuevo (ya podría tener las manos tratando enteramente con los temas de la Web). Lenguajes de guiones Los plug-ins resultaron en una explosión de los lenguajes de guiones. y no hay duda que hay mas en desarrollo. Esto apunta a que los lenguajes de guiones utilizados dentro de los navegadores Web son realmente un intento de solucionar un tipo específico de problema. inicialmente en la creación de interfases de usuario (GUI) mas ricas e interactivas. Con lenguajes de guiones se empotra el código fuente del programa del lado del servidor directamente en la página HTML. Hay otros ahí afuera. Sin embargo. Viene incluido con el Netscape Navigator y con el Microsoft Internet Explorer (IE). se cargan muy rápido como parte del simple envío del servidor requerido para procurar esa página. y el plug-ins que interpreta el lenguaje es automáticamente activado cuando la página HTML se despliega. 59 . sin embargo. no se esta haciendo alguna cosa asombrosamente sofisticada con lenguajes de guiones así es que esto no es mucho problema. fue nombrado de esta forma solo para arrebatar algo del momento de marketing que tuvo Java). que se origina en la popular lenguaje de plataforma cruzada GUI-building. dado que son simplemente texto que es parte de la página HTML.

El applet es bajado automáticamente como parte de la página Web (exactamente como. Como se verá mas adelante en este libro. un applet Java puede ser descompilado sin mucho trabajo. por ejemplo. multiplataforma e internacional. Esto es parte de su belleza -proporciona una forma de distribuir automáticamente el software cliente del servidor en el momento que el usuario necesita el software. y no antes. así es que el código fuente no esta disponible para el cliente. y la computadora cliente puede rápidamente realizar el trabajo de dibujar los datos en lugar de tener que esperar al servidor para que dibuje los datos y los envíe de vuelta en forma de una imagen gráfica. Un programa de guiones solo puede ser integrado en la página Web como parte de su texto (y generalmente es mas 60 Pensando en Java www. un applet Java compilado puede comprender muchos módulos y tomar muchos peticiones del servidor (entradas) a bajar (En Java 1. acceso a bases de datos. Java permite programación del lado del cliente mediante el applet. y el programa automáticamente trabaja con todas las computadoras que tienen intérpretes Java insertados (Esta seguramente incluye a mayoría de todas las máquinas). Otros dos factores pueden ser importantes. Dado por la forma en que Java es diseñado. que permiten que todos los módulos requeridos sean empaquetados juntos y comprimidos para bajara todo de una sola vez). un gráfico es automáticamente bajado). que sucede con el otro 20 por ciento las “cosas verdaderamente duras”? La solución mas popular hoy en día es Java. Cuando el applet es activado se ejecuta un programa. Un applet es un pequeño programa que se ejecuta solo en un navegador Web. evitando que la Internet entera se ponga lenta. Otra ventaja de los applets de Java tienen sobre los lenguajes de script es que está compilado. No solo se obtiene una ganancia inmediata en velocidad y respuesta.com . en tráfico general de la red y la carga de los servidores puede ser reducida. El usuario obtiene la última versión del software cliente sin fallar y sin dificultades de reinstalación. no necesita enviar un formulario de pedido a través de la Internet para descubrir que ha puesto la fecha o algún otro parámetro mal. pero ocultar su código no es un tema muy importante. Por el otro lado. el programador necesita crear un solo programa. No solo es un poderoso lenguaje de programación creado para ser seguro. se puede hacer todo el trabajo que el cliente permita antes y después de realizar peticiones al servidor.Java ¿Si un lenguaje de guiones puede resolver un 80 por ciento de los problemas de programación del lado del cliente. llamados ficheros JAR. Java también es continuamente extendido para proporcionar características y librerías que elegantemente maneja problemas que son difíciles en lenguajes de programación. como lo es la multitarea.1 y superiores esto es minimizado por los archivos. Por ejemplo. programación en redes y computación distribuida.BruceEckel. Dado que Java es un lenguaje de programación totalmente maduro.

Java no es un lenguaje trivial de aprender. ActiveX no se limita a un lenguaje en particular. Algunos de ellos son benignos: los ficheros GIF no pueden hacer ningún daño. Otro factor es la curva de aprendizaje. los programas que se cargan en la computadoras no están restringidos a 61 . código de guiones. ActiveX especialmente trae el espinoso tema de la seguridad de la programación del lado del cliente. y los lenguajes de guiones están limitados en lo que pueden hacer. ActiveX En algún grado. Si tiene experiencia con un lenguaje de guiones estará naturalmente beneficiado al ver JavaScript o VBScript antes de comprometerse con Java. Independientemente de lo que se ha dicho. se puede crear componentes ActiveX sin cargo para su conocimiento en programación. que previene de escribir el disco o de acceder a la memoria fuera de la jaula. o Delphi de Borland. moverse a VBScirpt puede ser la solución mas rápida. Visual Basic. a pesar de que toman una estrategia totalmente distinta. Esto puede ser importante para la reacción de su sitio Web. a pesar de que ahora esta siendo desarrollado por un consorcio independiente para convertirlo en multiplataforma. Si se es un programador de Visual Basic. código Java compilado. dado que ellos pueden encajar en sus necesidades diestramente y será mas productivo mas adelante. se debe automáticamente bajar cualquier cantidad de cosas junto con la página HTML: ficheros GIF. y componentes ActiveX. y dado que se solucionan los problemas cliente/servidor mas típicos estará duramente presionado a justificar aprender Java. Así es que si se hace un clic en una página que baja un componente ActiveX. ese componente puede causar daño en los ficheros de su disco. Programar con ActiveX es como programar Windows -se puede hacer todo lo que se quiera. el competido de Java es ActiveX de Microsoft. si. Claro. ActiveX fue originalmente una solución solo para Windows. ActiveX dice “si su programa se conecta a su ambiente exactamente de esta forma. por ejemplo. se puede dejar caer en una pagina Web y ejecutar bajo un navegador que soporte ActiveX” (IE directamente soporta ActiveX y Netscape lo hace utilizando un plug-in). Java también fue diseñado para ejecutar en applets dentro de una jaula de seguridad. De esta forma. Efectivamente. ActiveX también proporciona un camino para el uso de código legado en las paginas Web. ActiveX es el extremo opuesto del espectro. Si se accede a un sitio Web. Seguridad Bajar y ejecutar automáticamente programas a través de la Internet puede sonar como un sueño para los creadores de virus.pequeño y reduce los pedidos al servidor). ya se ha tenido una experiencia programando bajo Windows utilizando un lenguaje como C++.

¿Para entonces. Esto suena como un buen plan porque esto permite a los programadores ser mucho mas funcional.com . se puede querer crear una base de datos local o guardar datos para su uso posterior fuera de línea. Si se baja un programa con errores y hace algo adverso. Se puede ser escéptico de esta restricción draconiana contra escribir ficheros en su disco local. que es la velocidad con que las personas se mueven a través de la Internet. por lo cual el código es verificado para mostrar quién es el autor. como se rastreará el programa que lo hizo? ¿Y que cosa buena hará para solucionar el daño en este punto? 62 Pensando en Java www. fue rápidamente impráctico (a pesar que los “dispositivos de Internet” de bajo costo algún día satisfacerán las necesidades de un segmento significante de usuarios). así es que si se le quita el anonimato individual se es forzado a ser responsable por sus acciones. y dado que esto es esencial para sistemas cliente/servidor confiables. sin embargo. La solución parece ser las “firmas digitales”. El intérprete Java que se encuentra en su navegador Web local examina el applet en busca de instrucciones adversas cuando el applet es cargado. La solución es el “applet firmado” que utiliza encriptación de clave pública para verificar que un applet viene ciertamente de donde dice que viene.ejecutarse dentro de un navegador Web pueden hacer lo mismo. y algunos navegadores permiten seleccionar diferentes niveles de seguridad para proporcionar varios niveles de acceso a su sistema). En particular. Los applets son generalmente considerados seguros. Los virus bajados de sistemas de mensajería electrónica (Bulletin Board Systems BBSs) fueron un gran problema. pero la velocidad de la Internet amplifica esta dificultad. Esto esta basado en la idea de que los virus trabajan porque el creador puede ser anónimo. pero la teoría es que se puede ahora apoyarse en la responsabilidad del creador que no hace cosas viciosas. y sospecho que esto eliminará las malas conductas. y los errores en el lenguaje Java que permiten virus son rápidamente reparados (es importante notar que el software de los navegadores actualmente implementa restricciones de seguridad.BruceEckel. La visión inicial parece ser que eventualmente cualquiera puede obtener fuera de línea hacer cualquier cosa importante. La estrategia de Java es prevenir estos problemas de que ocurran. el applet no puede escribir ficheros en el disco o borrar ficheros (uno de los motivos principales de los virus). pero esto. Java proporciona un marco de trabajo para firmas digitales así es que eventualmente puede permitir que un applet salga fuera de la jaula de seguridad si es necesario. ¿Cuanto tiempo pasará para que se descubra el daño? Pueden ser días o tal vez semanas. un programa tiene un error destructivo no intencional este causará problemas. Por ejemplo. Pero si. Un applet firmado puede seguir podar su disco. Las firmas digitales se olvidan de un tema importante. mediante la jaula de seguridad.

esto es referido como una intranet. Considere las limitaciones de su problema y cual sería el camino mas corto para su solución. En términos de entrenamiento. es siempre una buena idea tomar la estrategia mas rápida para su situación en 63 . En una intranet. Cuando la tecnología Web es utilizada para una red de información está restringida a una empresa en particular.Internet versus intranet La Web es la solución mas general para el problema de cliente/servidor. No es común que las máquinas sean de plataforma Intel bajo Windows. en lugar de tratar de escribir todo el código nuevamente en un nuevo lenguaje. Si se esta involucrado en algo así como una intranet. debe tener un grupo diferente de limitaciones. por lo cual se deben instalar programas cliente cada vez que se realiza una actualización. Se necesita algo multiplataforma y seguro. Las intranets proporcionan mucha mas seguridad que la Internet. en particular los problemas clásicos cliente/servidor dentro de una empresa. se puede tener un código legado que se estará utilizando con una estrategia cliente/servidor mas tradicional. ambos cosas que pueden ser diestramente solucionadas con navegadores Web y programación del lado del cliente. así es que tiene sentido que se pueda utilizar la misma tecnología para solucionar un grupo de problemas. Cuando se encara con esta desconcertante colección de soluciones para el problema de programación del lado del cliente. la estrategia mas sensata a tomar es el camino mas corto que permita utilizar su código base existente. el mejor plan de ataque es el análisis de los costos y los beneficios. Si se esta ejecutando en una intranet. El tiempo gastado en instalar actualizaciones es la razón mas convincente para migrar a los navegadores. parece ser que una vez que las personas entienden el concepto general de un navegador es mucho mas fácil para ellos tratar con las diferencias en que se ven las páginas Web y los applets. Dado que la programación del lado del cliente sigue siento programación. Los problemas de seguridad nos traen a una de las divisiones que parecen formarse automáticamente en el mundo de la programación del lado del cliente. así es que la curva de aprendizaje para los nuevos sistemas parecen ser reducidas. Si su programa se esta ejecutando en la Internet. se es responsable por la calidad de su propio código y se pueden reparar errores cuando son descubiertos. porque las actualizaciones son invisibles y automáticas. como la dificultad de la instalación de un nuevo software cliente. y se quiere ser cuidadoso de mas y no diseminar código con errores. no se sabrá bajo que plataforma trabajará. Además. Con la estrategia tradicional cliente/servidor se tiene el problema de múltiples tipos de computadoras cliente. dado que se puede controlar físicamente el acceso a los servidores dentro de su empresa. como un lenguaje de guiones o Java.

el crudo de los datos pueden ser enviados y puede dársele un formato en el cliente. Cuando nos movemos fuera del arena del applet (y simultáneamente liberamos las restricciones. que será mas rápido y tardará menos en cargar en el servidor). Una petición mas complicada a un servidor generalmente involucra una petición para una transacción en una base de datos.BruceEckel.particular. Las peticiones a la base de datos deben ser procesadas mediante algún código en el lado del servidor. exactamente como un programa común hace. un applet de Java. como la en contra de la escritura del disco) se entra en un mundo de aplicaciones de propósito general que se ejecutan solas.com . la fortaleza de Java no solo es solo en su portabilidad. Tradicionalmente. lo que involucra cambios en la base de datos. especialmente porque elimina los problemas de tratar con diferentes capacidades de los navegadores. El navegador interpreta el fichero en una forma apropiada: como una página HTML. son dos de las razones mas convincentes para las empresas que desarrollan sitios Web para moverse a Java. la programación del lado del servidor ha sido realizada utilizando Perl y guiones CDU. Esto es una postura agresiva para prepararse para los inevitables encuentros con los problemas del desarrollo de programas. Como se verá a través de este libro. si el cliente es mas inteligente mediante Java o un lenguaje de guiones. sin el navegador. que es generalmente referido como programación del lado del servidor. también en su facilidad para programarse. a la cual el servidor debe luego darle un formato un una página HTML y enviar el resultado (Claro que. Y como se ha dicho anteriormente. puede ser la forma mas efectiva de solucionar los problemas cliente/servidor. Java tiene muchas características que permiten crear 64 Pensando en Java www. una imagen gráfica. Una arena separada: aplicaciones Mucho del lo que se ha hablado sobre Java ha sido sobre los applets. ¿Que sucede cuando se hace una petición a un servidor? La mayoría del tiempo es simple “envíeme este fichero”. O se puede querer registrar el nombre de alguien en la base de datos cuando acceda a un grupo o coloque una orden. un programa de guiones. pero sistemas mas sofisticados han aparecido. Servlets y su familia JSPs. Programación del lado del servidor La totalidad del debate ha ignorado el tema de la programación del lado del servidor. Aquí. Java es actualmente un lenguaje de programación de propósito general que puede solucionar cualquier tipo de problema -al menos en teoría. Estos incluyen servidores Web basados en Java escribiendo lo que se denomina servlets. etc. Un escenario común involucra una petición para una búsqueda compleja en una base de datos.

Java es un lenguaje que evoluciona rápidamente. Muchos métodos de programación orientada a objetos han sido formulados desde el despertar de la programación orientada a objetos. Una vez que se sabe que todo se supone es un objeto.programas robustos en un período mas corto que los lenguajes de programación anteriores. así es que es importante entender que problema el método esta tratando de resolver antes de adoptar uno. y cada vez que una nueva versión sale es mas y mas atractiva para solucionar un gran grupo de problemas.3. Java tiene limitaciones que pueden hacerlo inapropiado para solucionar ciertos tipos de problemas de programación. Muchos amigos tienen problemas al comienzo para saber como enfocar un proyecto orientado a objetos. Especialmente en POO. Un método (a menudo llamado metodología) es un grupo de procesos y heurística utilizados para disminuir la complejidad de un problema de programación. en particular. en donde el lenguaje de programación pretende reducir la complejidad (comparado con C) involucrada en expresar un programa. solo se necesitará un pequeño ajuste para trabajar en Java. Es importante de darse cuenta que el término “metodología” es a menudo muy grande y promete mucho. Si no se esta satisfecho con su productividad y la forma en que los programas se forman. Se paga por las mejoras mediante una disminución en la velocidad de ejecución (a pesar de que se está haciendo un trabajo significante en ese área -JDK 1. Esto puede de echo aliviar la necesidad de metodologías mas complejas. y se puede no ser conciente de hacerlo. Si es un proceso efectivo. Esta sección dará un sentimiento de que se estará tratando de lograr cuando utilice un método. la metodología es un campo de muchos experimentes. Lo que sea que se haga ahora con un diseño y escritura de un programa es un método. sin embargo. En lugar de eso. Como cualquier lenguaje. introduce lo que han llamado mejoras “hotspot” en rendimiento). se debe 65 . metodologías simples pueden ser satisfactorias en Java para una clase mucho mas grande de problemas de las que se pueden manejar utilizando metodologías simples que con lenguajes procesales. Análisis y diseño El paradigma de la orientación a objetos es una forma nueva y diferente de pensar acerca de programar. Esto es en particular verdadero con Java. y tanto como se aprenda a pensar mas en un estilo orientado a objetos se puede comenzar a crear “buenos” diseños que tomen ventaja de todos los beneficios que la POO puede ofrecer. Hay que ser conciente de que esto es una ventaja a medias. pero es un proceso por el cual se camina mientras se crea. Este puede ser un método propio.

de Martin Fowler (AddisonWesley 2000). Se debe recordar. El punto vale la pena enfatizarlo. que reduce el a veces abrumador proceso UML a un grupo pequeño manejable.BruceEckel. se paga por entender las necesidades del cliente a fondo. Recuerde que muchos proyectos no encajan en esa categoría. es crucial moverse relativamente rápido a través del análisis y el diseño. Es fácil hacerlo. Ciertamente. Hay aquí muchos grados de libertad. hay algunas cosas acerca de un sistema que no revelan solas hasta el momento del diseño.considerar adoptar un método formal. El intento de analizar a fondo un problema de carácter general antes de moverse en el diseño e implementación termina en una parálisis de análisis ya que no se tiene suficiente información para solucionar este tipo de problema durante la fase de análisis. que no importa cuanto análisis haga. Mucho de los métodos de análisis y diseño pretenden solucionar la mayoría de los problemas. o elegir piezas de una cantidad de métodos formales. y mas cosas que no se revelaran solas hasta que se encuentre codificando. y esto requiere tomar un comportamiento de riesgo (lo que tiene sentido. Pero alguna clase de procesos. Es fácil también atascarse. Pero DBMS es una clase de programas. 66 Pensando en Java www. el tema mas importante es este: No se pierda. en donde la solución no es simplemente formar nuevamente una solución bien conocida.com . no intente planear cuanto tiempo va a tomar o cuanto va a costar hasta que se haya creado un prototipo que trabaje. Lo que se esta haciendo a través del proceso de desarrollo. 8 Mi principio para copiar y lograr estimar estos proyectos: Si hay mas de un carácter general. La clase de problema de programación discutido en este capítulo es el de la variedad de “carácter general” (mi término). Esto 7 Un excelente ejemplo de esto es UML Distilled 2da edición. o inclusive hasta que un programa este ejecutándose. así es que se puede usualmente tener éxito en el análisis y el diseño con un relativamente pequeño grupo de métodos recomendados7 . la estructura de la base de datos es el problema a ser abordado. cuando creamos una DBMS. no importa que limitado se esté generalmente en su camino es una forma mucho mejor que simplemente empezar a codificar. dado que se esta tratando de hacer algo nuevo y la recompensa potencial es alta). pero en lugar de eso involucrar una o mas “factores de carácter general” -elementos por los cuales no es entendida correctamente una solución previa. Dada la historia que se ha tenido con los lenguajes procesales. es admirable que un equipo quiera proceder cuidadosamente y entender cada detalle antes de moverse a diseñar e implementar. y por el cual la investigación es necesaria 8. Dado esto. implementar una prueba del sistema propuesto. para caer en una “parálisis de análisis”. donde se siente como que no se puede mover hacia adelante porque no se tiene asegurado cada pequeño detalle en la etapa actual. Solucionar problemas como estos requiere iteración con el ciclo completo.

Esto puede ser atractivo por un momento. y a pesar de eso las personas a menudo no hacen esta decisión antes de comenzar a codificar. Esto dice. bien (A veces esto es apropiado cuando se tiene bien entendido el problema). durante la primer aprobación se produce algún diseño de clase útil y desarrolla alguna idea importante acerca del diseño que no necesita ser tirado. Esto suena simple (de echo. De debe también decidir en esta fase que alguna estructura de proceso adicional es necesaria. pero no se puede tener menos.puede parecer como que el riesgo esta sintetizado en “apresurar” una implementación preliminar. pero no la totalidad de las cosas. 67 . 2. la aprobación rápida de un problema no solo produce información crítica para el siguiente análisis. El desarrollo de productos es un manejo de riesgos. Además. Al menos está de acuerdo que ese es el plan. pero dado que el código esta encapsulado en clases. Con la POO. en el cual ninguna estructura es impuesta en el proceso de desarrollo de su trabajo. Tenga en mente que se está tratando de descubrir: 1. De esta forma. Se entiende que a algunos programadores les gusta trabajar en el “modo vacaciones”. ¿Que son los objetos? (¿Como se divide el proyecto en sus partes componentes?) ¿Que son sus interfases? (¿Que mensajes necesitará enviar a cada objeto?) Si comienza con nada mas que los objetos y sus interfases. Por varias razones se debe pensar en necesitar mas descripciones y documentos que estos. puede ser difícil saber cuando parar. que si se esta viendo una metodología que contiene tremendos detalles y sugiere muchos pasos y documentos. Si los planes son “entre y comience a codificar”. y una fase 0 que es simplemente el compromiso inicial de utilizar algún tipo de estructura. también crea una base de código. Fase 0: Haga un plan Se debe primero decidir que pasos se harán para estar adentro de su proceso. El proceso puede ser abordado en cinco fases. A menudo se propone que se “construya uno para lanzarlo”. se puede seguir lanzando parte de este. pero se puede reducir el riesgo en un proyecto de carácter general dado que se esta encontrando antes una estrategia para ver si el problema es viable. pero se encontrará que tener algunas metas en el camino puede ayudar a enfocar y galvanizar sus esfuerzos alrededor de estas metas en lugar de atarse a una sola meta de “terminar el proyecto”. entonces se puede escribir un programa. diseño e implementación. “Eso puede hacerse cuando este terminado”. todo esto suena simple).

Cuando comienzo a estudiar la estructura de una historia (así es que algún día escribiré una novela) inicialmente me resistí a la idea de la estructura. o ninguna. No se está necesariamente en lo correcto la primera vez (se debe estar en una fase mas adelantada del proyecto antes de estar completamente seguro). Si se puede pasar por alto la interfase de usuario.com . tal vez solo hay una sola persona controlando.se divide el proyecto en piezas mas digeribles y hace que parezcan menos amenazantes (además las metas ofrecen mas posibilidades de celebrar). Aún cuando se piense que sus planes son solo comenzar a codificar. descargar. “El programa de la torre le lleva el rastro del avión”. las necesidades que satisface. sin embargo. los detalles específicos de hardware -o sistema-. Pero considere que sucede cuando reduce el sistema a un pequeño aeropuerto. llega. El objetivo de la misión Cualquier sistema que se este armando. Pero sigo estructurando mi trabajo. eventualmente se encontrará la forma de comenzarlo -simple y directo. los algoritmos de código y los problemas de eficiencia. sintiendo que escribo mejor cuando simplemente dejo que fluya en la página. Pero mas tarde me he dado cuenta que cuando escribo acerca de computadoras la estructura es suficientemente clara para mi que no tengo que pensar mucho acerca de esta. Un modelo mas útil no le interesa la solución que se esta creando tanto como la descripción del problema: “El avión. pero se sigue tratando hasta que se esta seguro. claro. Por ejemplo. tiene un propósito fundamental. Como el tantas veces llamado concepto elevado de una película de Hollywood. Su intención es buena. esto es el objetivo de la misión. no importa que tan complicado sea. esto es llamado “creación de análisis de requerimientos y especificación de sistema”.BruceEckel. El gran concepto es bastante importante porque le da el tono a el proyecto. Esta descripción pura es el punto de inicio. El análisis de requerimientos dice “Haga una lista de las directivas que se utilizarán cuando el trabajo esté 68 Pensando en Java www. el tema involucrado. de algún modo sigue atravesando las fases subsecuentes de hacer y contestar ciertas preguntas. nombres intimidantes de documentos que pueden convertirse en grandes proyectos en todo su derecho. se puede describir en una o dos sentencias. donde las apuestas se pierden. Fase 1: ¿Que estamos haciendo? En la generación previa del diseño de programa (llamada diseño procesal). servicio y carga. Esto. en un sistema de control de tráfico aéreo se puede comenzar con un concepto elevado enfocado en el sistema que se esta armando. a pesar de que solo es semiconsciente en mi cabeza. luego emprende el viaje”.

para ahorrar tiempo. La especificación de sistema es una exploración de nivel superior en el problema y en cierto sentido un descubrimiento de que puede ser realizado y cuanto tiempo tomará. Jarrett.terminado y el cliente esté satisfecho”.. Cada una de estas “situaciones” esta referida como un escenario.?”. Por ejemplo. Esto no solo implica la entrada de cada uno.. y un uso de caso puede ser considerado como una colección de escenarios. pero manteniendo el documento inicial pequeño y conciso. La especificación del sistemas dice “He aquí una descripción de que hará el programa (no como) para satisfacer los requerimientos”. o si el mismo actor tiene un objetivo diferente?” (para revelar variaciones) “¿Que problemas se pueden suceder cuando se está haciendo eso con el sistema?” (para revelar excepciones) * Si se está diseñando un cajero automático. puede ser creado en algunas pocas sesiones de un grupo mediante tormenta de ideas con un líder que dinámicamente cree la descripción. El análisis de requerimientos es realmente un contrato entre el programador y el cliente (aún si el cliente trabaja con su empresa. Se puede pensar en un escenario como una pregunta que comienza con: “¿Que hace el sistema si. también fomenta la aceptación inicial -de los que tienen el mando y el acuerdo de todos en el equipo. Dado que ambos requieren consenso entre personas (y dado que generalmente cambia con el tiempo). la listas y el diagrama básico. Tal vez mas importante. o si es otro objeto o sistema). Los casos de uso identifican las características clave en el sistema que dan a conocer algunas de las clases fundamentales que se estarán utilizando. creo que es mejor mantenerlo al descubierto tanto como sea posible idealmente. Es necesario estar enfocado en el corazón de lo que se está tratando de lograr en esta fase: determinar que supuestamente el sistema hará. Estos son esencialmente respuestas descriptivas a preguntas como9: * * * * “¿Quien utilizará este sistema?” “¿Que pueden hacer estos actores con este sistema?” “¿Como harán estos actores eso con este sistema?” “¿De que otra forma se puede hacer este trabajo si alguien mas lo hace. Se debe tener otras restricciones que exijan que se expanda en grandes documentos. el caso de uso para un aspecto particular de funcionalidad del sistema es capas de describir que hará el cajero en una posible situación. se puede iniciar un proyecto con mucho entusiasmo. 69 . La herramienta mas valiosa para esto es un grupo de lo que es llamado “casos de uso”. por ejemplo. “¿Que hace el cajero automático si un cliente deposita un cheque dentro de 9 Gracias por la ayuda de James H.

que es típicamente un humano u otro tipo de agente libre (Este puede ser otros sistemas de computadoras. Un caso de uso no necesita una horrorosa complejidad. y no hay suficiente en la cuenta sin que el cheque haya sido aclarado para proporcionar el retiro deseado?”. como en el caso de “ATM”). Lo bueno acerca de enfocase en los casos de uso es que ellos siempre traen lo esencial y mantienen de no quedar a la 70 Pensando en Java www. Esto es solo se pretende mostrar el sistema como aparece para el usuario. Cuando se trata de descubrir un grupo completo de casos de uso para su sistema. o como se vea esta para el usuario. No importa como esta el sistema actualmente implementado. aún si las capas bajas del sistema son complejas. Las líneas entre los actores y los casos de uso representan las interacciones. Las elipses representas los casos de uso.BruceEckel.com .las 24 horas. que son descripciones de los trabajos valiosos que pueden realizarse con el sistema. La caja representa los límites de su sistemas. y una vez que se haya terminado lo que se tiene como que el corazón del sistema supuestamente tiene que hacer. Por ejemplo: Los casos de uso producen las especificaciones determinando todas las interacciones que un usuario puede tener con el sistema. Los diagramas de casos de uso son intencionalmente simples para prevenir que se atasque en los detalles de la implementación del sistema prematuramente: Cada persona que entra representa un “actor”.

se puede arrancar esta fase utilizando una herramienta grosera de aproximación: describiendo el sistema en algunos párrafos y luego buscar nombres y verbos. (Addison-Wesley Longman. entornos de usos de caso (por ejemplo “vestíbulo”). O un director puede haber decidido ya cuanto tiempo el proyecto debe tomar y tratará de influenciar la estimación. Probablemente no se obtenga todo resulto perfectamente en el primer intento. así es que probablemente se sea capaz de tener alguna idea de cuanto tiempo tomará. Esto es. Si se queda atascado. pero probablemente la mejor estrategia es confiar en su experiencia e intuición. Doblarlo y agregarle el 10 por ciento dara una estimación acertada (asumiendo que no hay muchos factores de carácter 71 . El “doblarlo” puede convertirse en algo decente. Los nombres sugieren actores. Para un proceso de definición y creación de interfases. Ha habido un montón de intentos de comenzar con técnicas exactas para organizar una agenda (muchas de las técnicas son como las utilizadas para predecir stock de mercaderías). 1999) o vaya a www. y especifican pasos dentro el uso de caso. Pero es mejor tener una agenda honesta desde el comienzo y tratar con las decisiones agobiantes temprano. Un presentimiento es probablemente correcto. Los verbos pueden sugerir interacciones entre actores y casos de uso.com . se puede obtener algo trabajando en ese tiempo. por Rosenberg (Addison-Wesley 1999 11 Mi concepto personal de esto ha cambiado mas tarde. en este punto algún tipo de organización es importante. e 10 Mas información de los casos de uso pueden ser encontrados en Applying Use Cases por Schneider & Winters (Addison-Wesley 1998) y Use Case Driven Object Modeling with UML.ForUse. Se descubrirá también que los nombres y los verbos producen objetos y mensajes durante la fase de diseño (y debe notarse que los casos de usos describen interacciones entre subsistemas. véase Sofware for U por Larry se Constantine y Lucy Lockwood.deriva entre temas que no son críticos para obtener el trabajo terminado. y el 1o por ciento puede tratar con el acabado final y los detalles1 1 . pero esto esta bien. y si se demanda una especificación perfecta en este punto se quedará atascado. o artefactos manipulados en los usos de caso. Un montón de factores comienzan a jugar aquí. si se tiene un grupo completo de casos de uso. Si se estima una larga agenda entonces la empresa puede decidir no armarlo (y de esta forma utilizar sus recursos en algo mas razonable -lo cual es una cosa buena). A pesar de que esto es magia negra. Sin embargo si quiere explicarlo. así es que la técnica del “nombre y el verbo” puede ser utilizado solo como una herramienta del tipo de tormenta de ideas así es que no generan casos de uso)1 0. Todo quedará revelado solo en tiempo. Se tiene ahora una visión general de que es lo que se esta armando. Los límites entre un caso de uso y un actor pueden apuntar a la existencia de una interfase. pero no define una interfase de usuario. se puede describir su sistema y moverse en el siguiente paso.

Si no se puede meter todo lo que se necesita saber acerca de la clase en la pequeña tarjeta. es justo trabajar de esa forma. general). Las “responsabilidades” de la clase: que debería hacer. o se debe crear mas de una clase).com . pero si a pesar de eso tiene que trabajar bastante diligentemente para terminar en ese tiempo.independientemente de los lamentos y manipulaciones que suceden cuando se da a conocer semejante agenda. Esto puede típicamente ser resumido simplemente definiendo los nombres de las funciones miembro (dado que esto nombres deben ser descriptivos en un buen diseño). y en la tarjeta se escribirá: 1. y luego como responderán a los colores y las formas cuando explote. Si necesita definir el proceso. Se debe sentir como que las tarjetas deberían ser mas grandes por toda la información que se quiere meter en ella. no solo para mantener sus clases pequeñas también para evitar que se entre en mucho detalle demasiado pronto. Colaboraciones suele considerarse el auditorio para esta clase. pero esto no imposibilita otras notas. vea el problema desde un punto de vista de programador perezoso: ¿Que objetos deben aparecer mágicamente para solucionar los problemas? Las “colaboraciones” de la clase: ¿Que otras clases deben interactuar con esta? “Interactuar” es un término extenso intencionalmente. El nombre de la clase. y se escribe en ellas. La clase ideal debe ser entendida en un solo vistazo general. ¿Quien va a observarlo. pero son intencionalmente pequeñas. 2. un Quimico o un Espectador? El creador va querer conocer que químicos que se utilizarán para su construcción. 3. así es que tiene sentido visto rápidamente. La idea de las tarjetas CRC es asistir el comienzo del los primeros trazados del diseño así es que se obtiene una imagen general y se refina luego el diseño. Parte del valor de esta herramienta es que es una técnica de bajo nivel: comienza con un grupo de tarjetas de 3 x 5. la clase es muy compleja (o se esta siendo muy detallista. Es importante que este nombre capture la esencia de que hace la clase.BruceEckel. creo. Cada tarjeta representa una clase única. Una técnica excelente en determinar clases e interacciones es la tarjeta Class-Responsibility-Collaboration (CRC). si se quiere tiempo para realmente hacerlo elegantemente y disfrutarlo en el proceso. 72 Pensando en Java www. Fase 2: Como lo haremos En esta fase se debe comenzar con un diseño que describa como se ven que clases y como interactuarán. el multiplicador correcto es mas como tres o cuatro veces. puede significar acumulación o simplemente que otro objeto existe que ejecutará servicios para un objeto de la clase. Por ejemplo. si se crea una clase Petardo .

Se puede también necesitar realizar una descripción de las estructuras de datos. Cuando se comienza a mover a través de los casos de uso. Cada persona toma la responsabilidad por algunas clases (que al principio no tienen nombres u otra información). para sistemas o subsistemas en los cuales los datos son un factor dominantes (como una base de datos). pero trabajando en un diseño que era mas interesante para ellos en el momento: ellos. especialmente si se quiere colocar en un diagrama en la pared para que todos piensen.Uno de los grandes beneficios de las tarjetas CRC es en comunicación. decidiendo que mensajes son enviados a los distintos objetos para satisfacer cada escenario. pero puede ser útil.org) es a menudo utilizado como “pseudocódigo ejecutable” 73 . No se necesita utilizar UML. lo cual es una buena idea. Como se sabrá. ellos “de adueñaron” del diseño en lugar de dárselos. Todo lo que hice fue guiar el proceso haciendo las preguntas correctas. Efectivamente. Bueno. de debe querer crear una descripción mas formal de su diseño utilizando UML12. recomiendo el anteriormente mensionado UML Distilled 2da edición. El equipo (que sabía lo que el proyecto supuestamente haría) verdaderamente crearon el diseño. 13 Python (www. manejaba todas las “tarjetas CRC” en la pizarra. Es mejor hacerlo en tiempo real. Lo verdaderamente hermoso del proceso fue que el equipo aprendía como hacer un diseño orientado a objetos no repasando ejemplos abstractos. y tomando la información que me entregaba el equipo para modificar las conjeturas. Una alternativa de UML es una descripción textual de los objetos y sus interfases.Python. Antes de comenzar a utilizar las tarjetas CRC. la mayoría de ellos -hay usualmente un pequeño grupo de ellos que se deslizan a través de grietas y no se hacen 12 Para personas que recién comienzan. UML también proporciona una notación mediante diagramas adicional para describir el modelo dinámico de sus sistema. el código mismo13. las mas exitosas experiencias de consultoría las he tenido cuando se comienza con un diseño de inicio de proyecto que involucra el estar parado de frente al equipo -que no ha armado un proyecto de POO antes. o . Hablamos de como los objetos deben comunicarse unos con otros. Se ejecuta una simulación para solucionar un escenario por vez. Una vez que se ha comenzado con un grupo de tarjetas CRC. sin computadoras. en un grupo. se debería tener un primer corte bastante completo de su diseño.dibujando objetos en una pizarra. se ha terminado con la fase 2 cuando se ha descrito todas los objetos y sus interfases. dependiendo de su lenguaje de programación. Esto es útil en situaciones en las cuales las transiciones de estado de un sistema o subsistema son suficientemente predominantes que necesitan sus propios diagramas (como en un sistema de control). tratando las suposiciones.

4. utilización y reutilización. a través del proceso de desarrollo de un programa. Con esta nueva información. como una lista enlazada. Todo lo que respecta de esto es que eventualmente se ha descubierto todos los objetos. Pero esto esta bien. también. Algunos objetos son obvios si ya se tiene un grupo de librerías de clases. Las necesidades internas del objeto pueden requerir otras clases para soportarlo. se puede descubrir las necesidades para facilitar o ayudar alguna clase. Armado de objetos. que se puede bajar en www. 1. En lugar de eso. Cinco etapas para el diseño de un objeto La vida de un objeto durante el diseño no esta limitada a el tiempo en que se esta escribiendo el programa. Casi en el momento que se este creando un objeto se descubrirán las necesidades de nuevos miembros que no aparecen durante el descubrimiento. Esta etapa se sucede durante el análisis inicial de un programa. se da cuenta que el entendimiento de lo que un objeto hace y como se debe ver a través del tiempo. 74 Pensando en Java www. posiblemente agregando nuevas clases o nuevas jerarquías. 2. 3. Los objetos. los objetos evolucionan. Extensión del sistema. La necesidad de comunicación y interconexión con otros objetos en el sistema pueden cambiar las necesidades de sus clases o requerir nuevas clases. pero la POO proporciona una estructura suficiente así es que no es tan malo que se descubran objetos mas tarde. Es lindo descubrirlos temprano en el proceso. que contiene poca o no información del estado y simplemente ayuda a funciones de otras clases. en lugar de eso. De hecho. Una ves mas. Esta vista también se aplica al diseño de varios tipos de programas. A medida que se agreguen nuevas características a un sistema de puede descubrir que su diseño previo no soporta extensión fácil del sistema. muchos de los requerimientos de un objeto pueden aparecer en esta etapa tardía. Los objetos pueden ser descubiertos buscando factores externos y limites.com). el diseño de un objeto aparece a través de una secuencia de etapas. se pude reestructurar partes del sistema. Como se ha aprendido.com . duplicación de elementos en el sistema y las mas pequeñas unidades conceptuales. Por ejemplo. Construcción del sistema. el diseño de un objeto tienden a suceder en cinco etapas.BruceEckel.BruceEckel.conocer hasta la fase 3. Es muy útil tener esta perspectiva porque ya no se espera que las cosas salgan a la perfección en el momento. o mas tarde en el proceso. El populacho entre clases sugieren que las clases base y la herencia pueden aparecer al instante. tienen sus patrones que emergen a través del entendimiento. el patrón para un tipo de programa en particular emerge a través de luchar una y otra vez con esos problemas (esto se pone en crónicas en el libro Thinking in Patterns with Java. Descubrimiento del objeto.

Si ya ha encontrado esas clases. Cuando los puntos de decisión llegan. probablemente ellos descubran algún defecto. esto puede ser un proyecto fácil. Esto no es un proceso de un 75 .5. 4. Si alguno trata de reutilizar esta en una situación totalmente nueva. Fase 3: Arme lo principal Esta el la transformación inicial de el grueso diseño en un cuerpo de código que se puede compilar y ejecutar para ser probado. Los objetos transparentes con utilidades obvias son mejores que las interfases complicadas. Se debe dejar que un problema específico genere una clase. 5. Siempre se debe mantener la simpleza. Esto sucederá de todas formas. Las malas clases no echan a perder las buenas clases. Se debe comience a programar: Se puede obtener algo del trabajo y así probar o desaprobar el diseño. al descubrir las clases que se necesitan (y sus interfases) ya se ha hecho la mayor parte del diseño del sistema. entonces deje que la clase crezca y madure durante la solución de otros problemas. No se debe forzar a uno mismo a conocer todo en el comienzo. Así como se cambia una clase para adaptarla a mas nuevos programas. y en especial que al que se le puede probar o desaprobar la arquitectura. Esta es la verdadera presión sobre la clase. los principios generales de la clase se hacen mas claros. y se puede extender la interfase de la clase cuando se entienda que es mejor. no espere que muchos objetos de un sistema de sistema sean reutilizables -es perfectamente aceptable para el grueso de sus objetos que sean específicos del sistema. es difícil remover elementos de una clase. aprenda mientras marcha. 2. No tenga miedo de terminar escribiendo código del estilo procesal -las clases dividen el problema y ayudan a controlar la anarquía y la entropía. Se debe recordar que. Sin embargo. Se debe comenzar pequeño y simple. 3. porque las clases simples son siempre las mejores. Reutilización de objetos. hasta que se tenga un tipo verdaderamente reutilizable. En el momento de seguir. y ellos deben solucionar problemas mas generales para poder ser reutilizables. Los tipos reutilizables tienden a ser menos comunes. se debe utilizar la estrategia Occam’s Razor: Se debe considerar las opciones y seleccionar una que sea la mas simple. Líneas guía para el desarrollo de objetos Estas etapas sugieren algunas guías cuando se esté pensando en desarrollar clases: 1.

cada grupo de características que agregue es un pequeño proyecto por si mismo. dado que el concepto no es descartado luego del análisis y del diseño. Debemos asegurarnos de que las pruebas verifican los requerimientos y los usos de caso. además mas bien es una unidad fundamental de desarrollo a lo largo del proceso de armado del software. (Recordemos que es software es un negocio de subscripciones. también nos esta dando mas validez a la idea de un uso de caso. Al final de cada período. Probablemente también descubramos cambios y mejoras que puede hacerse a la arquitectura original-cosas que no hubiera aprendido si no se implementaba el sistema. un período de desarrollo razonablemente pequeño período de desarrollo. usted tiene un sistema integrado y verificado con mas funcionalidad de la que tenía antes.com . cada iteración dura de una a tres semanas (esto puede variar basado en el lenguaje de implementación). No solo nos esta dando una mejor idea de cual es el alcance que un uso de caso puede tener. La meta es encontrar el corazón de la arquitectura del sistema que necesita ser implementada para obtener un sistema ejecutable. Idealmente. Nos detenemos cuando alcanzamos la funcionalidad esperada o llega una fecha límite y el cliente puede ser satisfecho con la versión actual. Parte del armado del sistema son las pruebas reales que obtendremos de los ensayos contra su análisis de requerimientos y la especificación (de la forma en que existan). Estamos creando un esqueleto que podrá montar con iteraciones mas adelante. ¿Cuan grande es una iteración? Idealmente.solo paso.) Porque el proceso es interactivo. También estamos armando la primera de muchas integraciones del sistema y la primeras pruebas y dando información acerca de cómo el sistema se verá y como va progresando. Cada uso de caso es un paquete de funcionalidades relacionadas que son armadas durante una iteración. pero en lugar de eso se comienza con una serie de pasos que interactivamente arman el sistemas. Cuando el corazón del sistema es estable. como se verá en la fase 4. Usted agrega un grupo de característica durante una iteración. tenemos muchas oportunidades de enviar un producto antes de que se termine. Pero lo que es particularmente interesante es lo básico para la iteración: un solo uso de caso. estamos expuesto a algunos riesgos de criticas. Una vez que el esqueleto del corazón esta corriendo. estamos listos para movernos y agregar mas funcionalidad. los proyectos de código de dominio 76 Pensando en Java www. no importa que tan incompleto está ese sistema en el paso inicial.BruceEckel. Fase 4: Iterando los casos de uso.

” Se puede necesitar hacer un montón de cambios dado que se ha aprendido y entendido el problema mas profundamente. A lo mejor hay un mejor término para describir que esta sucediendo. Además debemos tener un poco sentido en la estructura del 14 Al menos un aspecto de la evolución es cubierto por Martín Fowler’s en el libro Refactoring: improving the design of existing code (Addison-Wesley 1999). o pequeñas partes de código desgarbados. en parte porque sugiere que estamos actualmente armando un programa prístino y todo lo que necesita hacer es cambiar partes. “No se logrará de primera. así que hay que permitirse la amplitud de aprender. Es también donde las clases pueden evolucionar desde un proyecto con un único uso a un recurso reutilizable.” Así muchas pequeñas interpretaciones equivocadas son aplicadas a el término “mantenimiento” que son tomadas como una calidad un poco engañosa. a corto y a largo plazo. Fase 5: Evolución. y donde aquellos temas que no realmente se entendieron en los primeros pasos se aclaran. aceitarlas y evitar que se oxiden. La elegancia que producida si se evoluciona hasta obtener lo correcto se recompensará. Esto puede reducir o eliminar la necesidad de reuniones que entumecen la mente para presentar estados e incrementar la confidencialidad y soporte de los stakeholders. Pero un beneficio mas importante es la comunicación con el stakeholders. lograr capturar todos los términos que puedan significar todo de “lograr que trabaje de la forma que fue realmente supuesta en el primer lugar” para “agregar aspectos que el cliente olvidó mencionar” para la mas tradicional “reparación de errores que se descubren” y “agregar nuevos aspectos que surgen de las necesidades.público trabajan exclusivamente en un ambiente iterativo con retro alimentación. los clientes tienen amplias oportunidades para cambiar de idea. Un proceso de desarrollo iterativo se valora mas por muchas razones. objetos muy grandes. which uses Java examples exclusively. que es precisamente lo que lo hace exitoso. que puede ver el estado actual exacto del producto donde todos mienten. Usted puede dar a conocer y resolver riesgos críticos de forma temprana. la satisfacción del programador es alta. Evolución es donde un programa se dirige desde lo bueno a lo mejor. regresar y hacer cambios. sin sintaxis complicada. y sienta que todo encaja bien. 77 . y el proyecto puede ser dirigida con mas precisión. “Obtener lo correcto” no es solo que el programa trabaje de acuerdo con los requerimientos y los casos de uso. Esto es. Usaremos el término evolución14 . Esto también significa que la estructura interna del código tenga sentido para usted. Este es el punto del ciclo de desarrollo que tradicionalmente es llamado “mantenimiento”.

y entonces lo examinaremos. muchas veces antes de obtener la solución correcta 1 5. esto a menudo termina en sistemas desordenados que son caros de mantener. y cualquier cambio en los resultados deben ser limitados a una subclase y o a colaboradores específicos de la clase que cambiemos. No solo debe únicamente entender que esta creando. Esto no es un echo menor. el soporte para la evolución puede ser el beneficio mas importante de la POO. en lugar de eso arman sobre el. entonces debemos armar su primer versión lo mas rápido posible así podemos encontrar si es ciertamente lo que queremos. crearemos algo que al menos se aproxima a lo que pensamos que estamos creando. su padre y sus hijos deben seguir funcionando. La evolución también sucede cuando creamos un sistema. también como el programa evolucionará (lo que yo llamo el vector de cambio). y vemos donde esta débil. Entonces podemos regresar y corregirlo rediseñándolo y implementando nuevamente las porciones del programa que no trabajan correctamente. Combinado el agujero en la estructura en la programación de los procedimientos.programa que sobrevivirá a los cambios que son inevitables durante su tiempo de vida y aquellos cambios que pueden ser fáciles y limpios. y luego tirar nuestro prototipo y crear el correcto. Tal vez lo mas importante para recordar es que por defecto-por definición en realidad-si modificamos una clase. Cuando veamos un sistema en operación.com . Si pensamos este tipo de evolución va a suceder. la programación orientada a objetos son particularmente adepto a soportar esta continua modificación-los límites creados por los objetos son los que tienden a mantener la estructura de quiebres. lo comparamos con los requerimientos. el cual podemos bajar en www. vemos que se corresponde con los requerimientos. 15 Esto es algo como “rápidamente hacer un protoripo”. De hecho. Con la evolución. Por fortuna.BruceEckel. Podemos necesitar solucionar un problema o un aspecto de un problema. Esto también permite hacer cambios-algunos que puedan ser drásticos en un programa procesal-sin causar terremotos en su código. donde se supone que crearemos una rápida y sucia versión con la que podemos aprender acerca del sistema. No debemos tener miedo a la modificación (especialmente si tenemos creadas un grupo de unidad de verificación para saber si son correctas sus modificaciones).BruceEckel. 78 Pensando en Java www. (Un estudio de diseño de patrones es útil aquí (Podemos encontrar información en Thinking in Patterns with Java.com). encontraremos que realmente queremos solucionar un problema diferente. y entonces descubrimos que no es lo que necesitamos actualmente. Los problemas con hacer un prototipo rápido es que las personas no tiran el prototipo. Las modificaciones no deben necesariamente romper el programa.

de vez en cuando. y encantador que he visto. desde que me gradué en la escuela. Estas metodologías que asustaban mucho de usar-se veían como gastando todo el tiempo escribiendo documentos y nunca programando (A menudo este era lo que sucedía). Programación extrema He estudiado técnicas de análisis y diseño. algún tipo de plan hará una mejora grande en el proyecto. un 60 por ciento de los proyectos fracasan (algunos estiman un 70 por ciento!). El desarrollo de software se fue a los extremos. XP es una filosofía acerca del trabajo de programación y un grupo de instrucciones parar hacerlo. Utilicemos una aproximación que se adapte a las necesidades (y a nuestra personalidad). Por su puesto no construiremos una casa sin un montón de cuidadosos planos. Siguiendo un plan-preferentemente uno corto y simple-y. los planos no son tan elaborados.xprogramming. pero probablemente comencemos con algún tipo de esquema para guiarnos. A pesar que discute fuertemente de todo el 79 .El planeamiento paga. El concepto de programación extrema (XP) es el mas radical. Alguna de estas instrucciones son reflejadas en otras metodologías recientes. se siente mas cerca del arte que de la tecnología. No solo le daremos a el programa la facilidad al construir y depurar. También nos sentiremos mucho mas satisfechos. pero las dos mas importantes y distintivas contribuciones. en mi opinión. lo opuesto a lo que sucederá si no tiene ningún plan. las personas no tenían mucha estructura en sus desarrollos. terminamos con metodologías que tenían una cantidad intimidatoria de estructura y detalle. comenzando con el diseño de la estructura antes de codificar.com. descubriremos que las cosas se dan juntas mucho mas fácilmente que si nos agarramos y comenzamos cabalgar. estimando. No es una búsqueda de frivolidad. Espero que lo que mostramos aquí sugiera un camino intermedio-una escala variable. Se puede encontrar su historia en Extreme Programming Explained by Kent Beck (Addison-Wesley. y esto es con lo que se maneja el valor financiero. Recordemos que. será mas fácil de entender y mantener. en un principio para aquellos grandes proyectos. Por experiencia propia el echo de comenzar con una elegante solución es profundamente satisfactoria a un nivel totalmente diferente. pero los proyectos grandes comenzaban a fracasar. Y la elegancia siempre tiene recompensa. Por un largo tiempo. No importa que tan pequeña elija hacerlas. Reaccionando a esto. son “escribe las pruebas primero” y “programación en pareja”. Si construimos una cubierta o una casa para el perro. 2000) y en la Web en www.

El lenguaje ensamblador prueba solo la sintaxis. yendo mas lejos que sus ropas negras y ríen con alegría cuando quiebran algo (para ser honesto. y las personas que se especializa en eso no nos esta dando un montón de estados y están a menudo acordonados en un sótano. Esta actividad nos otorga la otra mitad de las pruebas que ejecuta el compilador. De hecho. Escribir las pruebas primero tiene importantes efectos. Escribir primero las pruebas Las pruebas. pero las pruebas son un contrato que es impuesto por el compilador y el programa que se ejecuta. después de que “tenemos todo el proyecto. pero nada es mas real que un conjunto de pruebas. se escriben las pruebas antes que el código que será probado. XP completamente revoluciona el concepto de prueba dando igual (o a veces mayor) prioridad que al código. Los equipos de prueba tienen que responden amablemente. casos de usos. Las pruebas debes ser ejecutadas con éxito cada ves que realiza una integración del proyecto (que a menudo. etc. estamos forzados a pensar en la totalidad de la clase y a menudo descubriremos funcionalidades que necesitamos que puede que nos hayamos olvidado mientras experimentábamos con diagramas UML. La estrategia de prueba de XP va mas lejos que eso-especifica exactamente como se debe ver una clase.proceso. El segundo mas importante efecto de escribir las pruebas primero llega al correr las pruebas cada vez que armamos el software. y exactamente como la clase debe comportarse. a el consumidor de esa clase. Podemos escribir todos los versos. o crear todos los diagramas que queramos. No en términos inciertos. Es difícil imaginar una descripción mas concreta de una clase que las pruebas. he tenido ese sentimiento cuando quiebro compiladores). pero C impuso algunas restricciones semánticas y estas previenen de cometer cierto tipo de 80 Pensando en Java www. es mas de una vez al día).com . Beck apunta a que si adoptamos solo estas dos prácticas aumentaremos enormemente nuestra productividad y confianza. Esto a menudo sugiere que las personas “imaginen la clase perfecta para solucionar un problema particular” como una herramienta con la que trataremos de diseñar el sistema. lejos de los “programadores reales”. Tiene implícitamente una prioridad baja.BruceEckel. tradicionalmente son relegadas a la última parte del proyecto. Cuando creamos las pruebas. El molde es una lista de deseaos. Primero. Si miramos la evolución de los lenguajes de programación en esta perspectiva. veremos un avance en las tecnologías que actualmente evolucionan alrededor de las pruebas. describiendo como una clase debe comportarse y como se vera. fuerza a una clara definición de la interfase de una clase. solo para estar seguro”. y las pruebas se quedan con el código para siempre. tarjetas CRC.

En algún momento. Y a pesar de eso XP. Las pruebas extienden la red de seguridad que proporciona el lenguaje.errores. Los lenguajes de POO imponen aun mas restricciones semánticas. ¿Y simplemente teniendo al compilador mirando sobre nuestro hombro. y el trabajar con nuestros vecinos es considerado “hacer trampa”). 81 . Pero el sistema de pruebas incluido ofrecido por el diseño del lenguaje puede solo ir hasta ahí. Programación en pareja La programación en pareja va en contra del fuerte individualismo al que hemos sido adoctrinados desde el comienzo. y hacerlos trabajar. y la segura red de pruebas incluidas nos indica que tenemos un problema e indica donde es. que por si sola batalla contra el pensamiento convencional. “¿Es este tipo de dato utilizado correctamente?” o “¿Es esta función llamada correctamente?” son los tipos de pruebas ejecutadas por el compilador o en tiempo de ejecución del sistema. Vemos los resultados de tener estos tipos de pruebas armados en el lenguaje: las personas son capaces de escribir sistemas mas complejos. y las ejecutamos automáticamente cada vez que armamos el sistema. también son considerados parangones del individualismo-“codificadores vaqueros” como a Larry Constantine le gustaba decir. Porque sabemos que las pruebas encontraran cualquier problema que introduzca ( y regularmente agrega nuevas pruebas a medida que pensemos en ellas). y los medios. a través de la escuela (donde acertamos o fallamos nosotros mismos. Una de las cosas que he descubierto acerca del uso de lenguajes de programación mas y mas poderosos es que me animo a intentar experimentos mas descarados. dice que el código debe ser escrito por 16 A pesar de que esto puede ser una perspectiva mas americana. especialmente las películas de Hollywood en el que el héroe usualmente pelea contra el conformismo de los cortos de mente1 6. no queremos que estas pruebas nos ayuden desde el comienzo? Es por esto que escribimos las pruebas al comienzo. El esquema de pruebas de XP hace lo mismo con su proyecto entero. Los programadores. las historias de Hollywood alcanzan a todos. pero ahora me he dado cuenta que esta es la prueba: hacemos algo mal. podemos hacer grandes cambios cuando necesitemos sin preocuparnos en llevar el proyecto entero a un completo desastre. que si pensamos en ellas son actualmente formas de probar. con mucho menos tiempo y esfuerzo. He quedado perplejo acerca de por que es esto. nosotros debemos dar un paso adelante y agregar el resto de las pruebas que producen un grupo completo (en cooperación con el compilador y el sistema en tiempo de ejecución) que verifique todo nuestro programa. porque se que el lenguaje me evitará que gaste mi tiempo en perseguir errores. Esto es increíblemente poderoso.

La persona que piensa mantiene una imagen general en mente-no solo la imagen del problema a mano. cuando nadie estaba mirando comenzaba a atacar los cables de los parlantes. ayudándonos un poco con lo posible mediante reglas arbitrarias o algún requerimiento que usemos en un grupo particular de prestación. pueden intercambiar lugares.BruceEckel. Y si el codificador se queda parado. Por que se sucede Java La razón por la que Java ha sido tan exitoso es que la meta es solucionar muchos de los problemas a los que se enfrentan los desarrolladores hoy. Probablemente mas importante. también las instrucciones de XP. La meta de java es mejorar la productividad. Trabajar en parejas mantiene las cosas fluyendo y en camino. 82 Pensando en Java www. las decisiones de diseño son basadas en proporcionarle los máximos beneficios a el programador. Y que esto debe ser hecho en un área con un grupo de estaciones de trabajo. Si los dos se quedan parados. 17 Incluido (especialmente) el sistema PA. Java es diseñado para ser práctico. Si dos personas están trabajando. Finalmente. El valor de la programación en pareja es que en el momento en que una persona esta codificando la otra persona esta pensando acerca de esto. De echo. esto interrumpía constantemente nuestra productividad (pero los directores no comenzaban a entender lo vergonzoso que es un servicio tan honorable como el PA). por ejemplo. “No quiero escribir las pruebas primero”. hace la programación mucho mas social y divertida. y el lenguaje es diseñado para ayudarnos lo mas que se pueda.com . sin las barreras a las cuales personas del plantel de diseño esta tan encariñada. es menos probable que uno de ellos se vaya diciendo. Beck dice que la primera tarea para convertir en XP es llegar con un destornillador. Una vez trabajé en una compañía que insistía en transmitir cada llamada que llegaba a cada ejecutivo. Esta productividad llega de varias formas.dos personas por estación de trabajo. una llave Allen y quitar todo lo que este en el camino1 7 . sus meditaciones pueden ser alcanzadas por alguien mas en el área de trabajo que pueda contribuir. He comenzado a utilizar programación en pareja durante los períodos de ejercicios en alguno de mis seminarios y parece que mejora significativamente la experiencia de cada uno.

no hay nada peor que tener un error enterrado en algún lugar sin indicio de donde viene. cual es la solución en el espacio (“Coloque el bit en el chip que significa que el relee cerrará”).Los sistemas son fáciles de expresar y de entender Las clases están diseñadas para introducir el problema tendiendo a expresarlo de mejor manera. Máxima ventaja con librerías La manera mas rápida de crear un programa es utilizar código que ya este escrito: una librería. El manejo de excepciones de Java es una forma de garantizar que un error es indicado. así es que introduciendo una librería significa agregar nuevos tipos al lenguaje. Los otros beneficios de esta facilidad de expresión es el mantenimiento. y uno que a menudo es ignorado-cruzar los dedos es usualmente involucrado. El logro mas grande en Java es hacer las librerías fáciles de utilizar. Esto significa que cuando escribimos el código. Manejo de error El manejo de error en C es un problema notorio. no como debemos hacerlo. y que algo sucede como resultado. entonces es fácil de mantener. estamos describiendo nuestra solución utilizando la terminología del espacio del problema (“Coloca el clip en la papelera”) en lugar de utilizar los términos de la computadora. Usted se maneja con conceptos de alto nivel y puede hacer mucho mas con una simple línea de código. Esto es llevado a cabo fundiendo librerías dentro de nuevos tipos de datos (clases). Si un programa es fácil de entender. Si usted esta desarrollando un largo y complejo programa. Esto también reduce el costo de crear y mantener la documentación. Dado que el compilador tiene cuidado de cómo la librería es utilizada-garantizando la correcta inicialización y limpieza. y asegurándose que las funciones son llamadas de forma adecuada-podemos focalizar nuestra atención en lo que queremos que la librería haga. 83 . que (si se puede creer en los reportes) tomo una gran cantidad del costo del tiempo de desarrollo.

departamento o iguales comiencen a utilizar objetos?” Piense como una persona-un programador independiente. No hay líneas claras que nos digan cuando el lenguaje esta fallando o siquiera donde. tendré que rescribirlo en C!” En lugar de eso. colegas.BruceEckel. Así los costos extras silenciosamente crecen. preguntando a expertos e intercambiando consejos con sus amigos. Instrucciones He aquí algunas instrucciones a considerar cuando hacemos una transición a POO o Java: 84 Pensando en Java www. borrar el límite de crecimiento de complejidad entre un programa pequeño y uno largo. “Como puedo lograr que mis representantes. es como nadar por un fluido cada ves mas viscoso. “Mi programa BASIC solo creció demasiado. puede ser genial para obtener rápidas soluciones en grupo para cierta clase de problemas. Por ejemplo BASIC.com . pero si el programa tiene mas de algunas pocas páginas de largo. pero las características están cuando las necesitamos. Estrategias para el cambio Si se compran acciones de POO. la siguiente pregunta es que es probable es. Luego viene el proyecto en el “mundo real” que actualmente hace algo útil. luego se inicia un juicio de un proyecto para obtener las básicas sin hacer algo muy confuso. Y el compilador es agresivo a la hora de encontrar la causa de errores en pequeños y grandes programas de la misma forma. No diremos. Ciertamente no se necesita el uso de POO cuando estamos escribiendo una utilidad del estilo de “Hola mundo”. se tratará de calzar unas líneas mas para agregar una nueva característica. o nos aventuramos fuera de los dominios de problemas normales del lenguaje. Java esta diseñado para ayudar a programar a lo largoesto es. y se ayudará en cada paso a recordar como una persona pudo hacerlo. Se comienza con educación y ejemplos. Ya lo ha hecho primero. lo ignoraremos. Desde el comienzo del primer proyecto se continúa la educación leyendo. Esta es la aproximación que muchos programadores experimentados sugieren para migrar a Java.Programando a lo grande Muchos lenguajes tradicionales tienen limitaciones de armado para programas grandes y complejos. Migrar una compañía entera introducirá por supuesto cierta dinámica de grupo.puede aprender el uso de un nuevo lenguaje y de un nuevo paradigma de programación.

e instructiva. Este es el concepto general del diseño de patrones. un proyecto piloto (posiblemente un mentor externo) y dejar que el equipo de proyecto comience a educar al resto de la compañía. incluyendo un vistazo inicial de cursos de estrategia de dirección al igual que de diseño y programación para las personas que arman el proyecto. Sin embargo. el que es cubierto a lo largo de este libro). Tomemos un pequeño grupo para adoctrinar. dado que el costo es alto. Proyectos de bajo riesgo Se puede intentar primero con un proyecto de bajo riesgo y permitirnos errores.com. Entrenamiento El primer paso es algo de educación. preferentemente compuesto por personas que son curiosas. 3. Hay una buena probabilidad de que alguien ya haya solucionado el problema antes y aunque no se la solución exacta a su problema podremos aplicar lo que hemos aprendido sobre abstracción para modificar un diseño para que cumpla sus necesidades. Se obtendrá el ciclo de desarrollo mas corto cuando se pueda crear y utilizar 85 .BruceEckel. Una alternativa próxima que a veces es sugerida es la educación de toda la empresa por niveles. o a nivel de división de grandes compañías. esto significa que debe incluir la creación de clases que serán significativas para otros programadores en la empresa cuando les toque el turno de aprender Java. 2. algunos pueden elegir comenzar con proyectos a nivel de entrenamiento. la Librería Estándar de Java. 4. Una vez que se ha ganado alguna experiencia. por lo que no de debe ser una misión crítica para la compañía. Debe ser simple. El primer proyecto puede no trabajar correctamente al principio. que trabaje bien juntas y que puedan funcionar como su propia red de soporte cuando estén aprendiendo Java. Esto es especialmente bueno para las pequeñas compañías que hacen movimientos fundamentales en la forma que hacen las cosas. se puede iniciar otros proyectos con miembros de este primer equipo o utilizar miembros del equipo como soporte técnico de POO. Recordemos que la compañía invierte en código. auto contenida. Modelo para el éxito Se buscarán ejemplos de buenos diseños orientados a objetos antes de comenzar. Use librerías de clases existente La principal motivación económica para migrar a POO es la facilidad de uso del código existente en la forma de librerías de clases (en particular. cubierto en Thinking in Patterns with Java el que se . puede bajar de www.1. y trata de no echar todo a perder por seis o nueve meses donde cada uno se esfuerza para saber como funciona la interfase.

pero los miembros del equipo deben superar sus propios problemas para entender la nueva tecnología. su trabajo es proporcionar recursos para su equipo. Manejo de obstáculos Si usted es un director. Estos son costos altos que deben ser divididos en factores en un propósito relista. algunos nuevos programadores no entienden esto. 5. superar las barreras para su éxito. No rescribir código existente en Java Usualmente el mejor uso del tiempo tomar código existente y funcional y rescribirlo en Java (Si debe insertarlo en objetos. hay costos ocultos de pérdida de productividad del aprendizaje y un nuevo ambiente de trabajo. Además. Costos iniciales El costo de migrar a Java es mas que solo la adquisición de los compiladores de Java (El compilador de Java de Sun es gratis. ignoran la existencia de clases que pueden existir o fascinados con el lenguaje.com . decidir escribir clases que ya existan. y en general para proporcionar un ambiente productivo y agradable. no es gratis y hay obstáculos de los cuales deberemos ser concientes antes de tratar de cautivarse con una migración a Java sin su compañía y embarcarse en esta empresa solo. Sus costos a mediano y largo plazo serán minimizados si invierte en entrenamiento (y posiblemente tutelando su primer proyecto) y también si identifica y compra librerías de clases que solucionen su problema en lugar de intentar crear sus propias librerías.objetos de una librería propia. Aunque migrar a Java puede ser baratodependiendo de sus restricciones económicas-que las alternativas para un equipo de programadores C (y probablemente para programadores de otros lenguajes procesales). así que difícilmente es un obstáculo). descrita en el apéndice B. puede crear una interfase con código C o C++ usando la Interfase Nativa de Java. Durante este proceso 86 Pensando en Java www. Pero las posibilidades son que no se vea el dramático incremento en la productividad que se espera en los primeros proyectos a no ser que el proyecto sea nuevo. así su equipo es mas prometedor de lograr esos milagros que siempre son requeridos. y sería maravilloso si tampoco no costara nada. Los beneficios se incrementan.). El entrenamiento y la presencia de un tutor pueden ciertamente minimizar estos. Sin embargo. El éxito con la POO y Java puede ser optimizado si se hace un esfuerzo de búsqueda y reutilizar código de otras personas en el proceso de transición.BruceEckel. Migrar a Java corresponde con las tres categorías. Java y la POO se lucen mejor cuando se toma un proyecto de un concepto a la realidad. especialmente si el código es escrito para reutilizarse.

porque no expertos han desarrollado sin la compañía y porque hay mucha resistencia para retener asesores. Tecnologías como “hotspot” y las tecnologías de compilación han mejorado significativamente la velocidad en muchos casos y los esfuerzos continúan en camino a mejorar el rendimiento. Esto a menudo sucede a causa de una falta de respuesta de los expertos durante el diseño y la implementación de proyectos tempranos. si es lo suficientemente pequeño y rápido. El echo de que el rendimiento es tan crítico en esa porción del diseño es un indicador que puede ser parte del criterio primario de diseño.cometerán muchos errores (esto es una herramienta. es posible ser mas productivo aprendiendo Java (aun considerando que se están cometiendo muchas equivocaciones y escribiendo pocas líneas de código por día) que lo que sería quedándose con C. las clases correctas y el ambiente de desarrollo adecuado. Se tiene el beneficio de encontrar el problema temprano utilizando un rápido desarrollo. estas usualmente optimizadas por sus vendedores. Errores comunes de diseño Cuando un equipo comienza con POO y Java. buscaremos modificaciones que puedan ser hechas en la implementación subyacente así es que ningún código que use una clase particular necesita ser cambiada. Aun así. mirando primero acelerando lo que se pueda hacer escribiendo nuevamente pequeñas porciones de código. Si esto no ayuda. se colocan componentes lo más rápido posible ignorando los temas de rendimiento. Cuando el enfoque es un rápido prototipo. de cualquier forma no es un tema que se tenga cuando se desarrolla rápidamente. Si no. entonces se habrá terminado. “La POO no hace automáticamente mis programas mas grandes y pesados?” La respuesta es. Si se encuentra una función que es particularmente un cuello de botella. se deberá ajustar con una herramienta de prueba. “Eso depende”. los programadores típicamente cometen una serie de errores de diseño. Las características de seguridad extras en Java son tradicionalmente penalizadas como en lenguajes como C++. con algunos tipos de problemas de programación. es tema del apéndice B. se puede rescribir esta en C o C++ usando métodos nativos de Java. Si usted esta utilizando cualquiera de las librerías de terceros. el conocimiento de los errores es el camino mas rápido de aprendizaje) y serán menos productivos. Es fácil sentir que entendemos rápidamente la POO en el ciclo y nos vamos por un camino equivocado. Cuando tenemos un sistema que queremos. A veces lo que es obvio para alguien 87 . Temas de rendimiento Una pregunta usual es. Solo sin nada mas soluciona el problema se cambiará el diseño.

Mucho de este trauma puede ser evitado utilizando un experto con experiencia de afuera que sirva de tutor nos entrene. pero si pensamos acerca de esto: a tal grado Java intenta hacer fácil al programador solucionar problemas a nivel de aplicación como redes de trabajo y interfaces de usuarios en plataformas cruzadas. vimos mejoras en la estabilidad pero no logros dramáticos. Java tal vez reduzca la velocidad de caída del desarrollo en lugar de acelerarla. C++ aun así tiene algunas características que Java no. Y a pesar de eso. y es natural que veamos que C++ puede ser remplazado con Java. en ese orden). así es que no piense que ese lenguaje se desaparecerá pronto (Los lenguajes parecen merodear. ¿Se debe utilizar Java en lugar de C++ para un proyecto? Aparte de los applets. También. y a pesar de que ha habido un montón de promesas acerca de que Java será algún día igual de rápido o mas rápido que C++. si se quiere utilizar una gran cantidad de librerías de C++ existentes (y ciertamente se tiene un montón de productividad ganada aquí). Por una cosa. Y esto es lo que pienso que Java se lucirá: como el “siguiente VB”. o si se tiene código base C o C++. Se pueden o no se pueden estremecer por esto. C++ es un lenguaje que no intenta adecuarse a un molde. Hablando de uno de mis “Seminarios de Java Intermedio-avanzado”. Esto a pesar del echo que VB produce un tipo de código que comienza a ser inmanejable cuando el programa tiene algunas páginas de largo (y la sintaxis puede ser absolutamente desconcertante). modelos de componentes. Comencemos a cuestionarnos esta lógica. vemos parece estar en un continuo interés en C++. que usan la mayoría de los desarrolladores Windows? Microsoft’s Visual Basic (VB). Ciertamente ha sido adaptado en un varias formas para solucionar problemas particulares. Comienzo a creer que la fuerza de Java pelea en una arena algo diferente que C++. Java versus C++? Java se ve mucho como C++. Algunas herramientas de C++ combinan librerías. Primero.com . hay dos temas a considerar. 88 Pensando en Java www. Agreguemos a esto el echo de que Java tiene el sistema de verificación de tipos y manejo de errores mas robusto que he visto en un lenguaje y podemos dar un significante salto para adelante en la productividad al programar. Casi tan exitoso y popular es VB.BruceEckel. Allen Holub defendió que el lenguaje mas comúnmente utilizado era Rexx y COBOL. Sería bueno tener la simplicidad y poder de VB sin el código inmanejable resultante.experimentado con el lenguaje puede resultar un gran debate para un principiante. y herramientas de generación de código para solucionar el problema de desarrollo de aplicaciones finales para el usuario con ventanas (para Microsoft Windows). como un no muy buen ejemplo de lenguaje de diseño. y aun así con un diseño de lenguaje que permite la creación de código largo y flexible.

Su se sabe que las necesidades serán muy especializadas para el futuro predecible y si las restricciones específicas no pueden ser satisfechas por Java. y porque Java en particular es diferente. Es importante evaluar sus necesidades y decidir cuando Java va a satisfacer óptimamente esas necesidades.Si se esta desarrollando todo el código primariamente de uno poco prolijo. Si el rendimiento de Java no importa o puede compensarlo de alguna manera. POO y java no es para cualquiera. desde 20 a 50 veces mas lento que C en intérpretes originales. de misma forma que en www. Y compilar de forma cruzada un programa en Java puede ser mucho mas fácil que hacerlo en C o en C++ (en teoría. Se pueden encontrar comparaciones de Java con C++ y observaciones acerca de Java en los apéndices de la primera edición de este libro (Disponible en este CDROM. Resumen Este capítulo intenta dar un amplio sentido de los temas de la programación orientada a objetos y Java. entonces la simplicidad de Java sobre C++ acortará significativamente el tiempo de desarrollo-la evidencia anecdótica (historias de equipos C++ que hablas de cómo cambiaron a Java) dicen que doblaron la velocidad de desarrollo. o si es tal ves es mejor desenchufarse con otro sistema de programación (incluyendo el actualmente utilizado). El intérprete de Java es lento. y los compiladores de código nativo eliminan la tan pregonada ejecución de programas en plataforma cruzada pero pueden llevar mas cerca la velocidad del ejecutable mas cerca de C y C++. La clave para hacer Java factible para la mayoría de los proyectos de desarrollo es la apariencia de mejoras en la velocidad llamadas a veces compiladores “just-in time” (JIT). si no son significativamente mas rápidas para hacer algo entonces se hace a mano (He oído esto sugiriendo que se comienza con Java para ganar un tiempo de desarrollo.com). Lo que las computadoras son acerca de la velocidad. Esto ha mejorado mucho. El gran tema es el rendimiento. tiempo total de comercialización hace difícil elegir C++ sobre Java. pero la promesa fue hecha antes para otros lenguajes). entonces se deberá investigar las 89 . conceptos de metodologías de la POO y finalmente los tipos de temas que se encuentran cuando se migra una compañía a la POO y Java.BruceEckel. pero sigue siendo un valor muy alto. incluyendo por que la POO es diferente. la tecnología de propiedad de Sun’s llamada “hotspot”. entonces si necesita una mayor velocidad de ejecución se utiliza una herramienta y librerías de soporte para convertir el código a C++. solo se compila nuevamente.

con un programa bien diseñado. Esta es la razón por la que se necesitan representaciones intermedias cuando diseñamos programas procesales-por si solos. Dado que Java agrega muchos nuevos altos conceptos de los que encontrará en un lenguaje procesal.alternativas18. 90 Pensando en Java www. Usualmente hay una gran cantidad de código también. al menos se entenderán que opciones hay y tener una clara visión de por que se tomó esa dirección. Se sabe que un programa procesal se ve como: definiciones de datos y llamadas a funciones.BruceEckel. porque muchos de los problemas serán resueltos utilizando nuevamente código de librerías existentes.com . recomiendo mirar Pitón (http://www. estos programas tienden a ser confusos porque los términos de la expresión son orientados mas cerca de la computadora que del problema que se está intentando solucionar. Lo que se verá son definiciones de objetos que representen conceptos en el espacio de su problema (en lugar de los temas de la representación en computadoras) y mensajes enviados a esos objetos para representar las actividades en ese espacio. 18 En particular. se asumirá naturalmente que el main() en un programa Java será mucho mas complicado que su equivalente en C. Aquí se sorprenderá placenteramente: Un programa Java bien escrito es generalmente mas simple y mas fácil de entender que su programa equivalente en C. Para encontrar el significada de cómo un programa en el que hay que trabajar un poco. es fácil entender el código para leerlo.Python. Aun si se elige eventualmente Java como el lenguaje. mirar a través de las llamadas a funciones y los conceptos de bajo nivel para crear un modelo en la mente.org). Uno de los deleites de la programación orientada a objetos es esa.

así que hay una sola sintaxis consistente que utilizaremos en todos lados. incluso el programa. el identificador que manipularemos es actualmente una “referencia” a un objeto1 . todo es un objeto. En este capítulo veremos los componentes básico de un programa Java y aprenderemos que en Java. las referencias en Java son mucho mas semejantes a las de C++ que a punteros es sus sintaxis. Esto significa que antes que comencemos deberemos mover nuestra mente a un mundo orientado a objetos (a no ser que ya la tengamos ahí). Existen aquello que dicen “claramente. De la misma forma que trataremos todo como un objeto. “manejar”. El beneficio de este esfuerzo inicial es la habilidad de programar en un lenguaje que es muy simple de aprender y de utilizar que otros muchos lenguajes OO. la razón por la que C++ es un híbrido es para soportar la compatibilidad hacia atrás con el lenguaje C. A veces los programadores deben ser constantemente concientes de que tipo de manipulación está haciendo. Java es mas que un lenguaje orientado a objetos “puro”. Un lenguaje híbrido permite muchos estilos de programa. o esta lidiando con algún tipo de representación indirecta (un puntero en C o C++) que debe ser tratado con una sintaxis especial? Todo esto es simplificado en Java.2: Todo es un objeto A pesar de que es basado en C++. pero esto presume de una implementación de capas inferiores. El lenguaje Java asume que no queremos solo programación orientada a objetos. pero en Java los diseñadores sintieron que la hibridación no era tan importante como lo fue en C++. ¿Se esta manipulando el objeto directamente. Dado que C++ es un conjunto superior de C. Manipulamos objetos con referencias Cada lenguaje de programación tiene su propios métodos para manipular datos. C++ y Java son lenguajes híbridos. es un puntero”. elegí inventar un nuevo término. Se puede 1 Esto puede ser un punto de explosión. porque las 91 . esto incluye muchas de las características indeseables que pueden hacer algunos aspectos de C++ demasiado complicados. Además. El la primera edición de este libro. Tratamos todo como un objeto.

los abogados del lenguaje pueden reclamar que estoy mintiendo. pero pienso que mi aproximación simplifica entender el concepto con el que dejamos mal parado todo (bueno. Me retiré de lo que es C++ y no confundí a los programadores de C++ a quienes asumí como la mayor audiencia para Java. El tiempo que se tenga esta referencia. Generalmente se hace con la palabra clave new . sin televisor. 92 Pensando en Java www. se conecta con un nuevo objeto. Leí en un libro que es “completamente equivocado decir que Java soporta paso por “referencia” porque los identificadores de objetos en Java (de acuerdo con ese autor) son actualmente “referencias a objetos”. Debemos crear todos los objetos Cuando se crea una referencia. solo porque tener una referencia no significa que tengamos que estar conectados a ella. esto utiliza una característica especial de Java: las cadenas pueden ser inicializadas con texto entre comillas. el control remoto puede quedarse en nuestra propiedad. que es la que modifica el objeto. Si se queremos movernos por el cuarto y seguir teniendo control sobre el televisor tomaremos la reverencia-remoto y la llevaremos con nosotros. Normalmente. se tendrá conexión con el televisor. Una práctica segura es siempre inicializar una referencia cuando se es creada: String s = “asdf”. Esta palabra indica algo así referencias de C++ y las de Java tienen importantes diferencias. decidí que “referencia” es un término mas comúnmente utilizado.com . Se puede discutir de la precisión de tan complicadas explicaciones. Si decidimos enviar un mensaje a s en este momento. Además. no el objeto. y que todos cambian de C++ que tienen que hacer mucho mas frente a esa terminología de referencias. Así es que si queremos mantener una palabra o sentencia. hay personas que les desagrada también el término “referencia”. crearemos una referencia String: String s.imaginar esta escena como un televisor (el objeto) con un control remoto (la referencia). estamos “pasando una referencia a un objeto por valor”. pero diré que estoy proporcionando una abstracción apropiada). En la 2da edición. Sin embargo. Sin embargo. así es que pueden saltar en dos pies igualmente. pero cuando se le indica “cambie el canal” o “baje el volumen” lo que se esta manipulando es la referencia. Y (ahí va nuevamente) todo es actualmente pasado por valor.BruceEckel. Esto es. Pero aquí hemos creado solo la referencia. obtendremos un error (en tiempo de ejecución) porque s no está actualmente asociado con nada (no hay televisor). se deberá utilizar un tipo mas general de inicialización para los objetos. no el televisor. Así es que no estamos pasando por referencia.

Donde sea que necesitemos crear un objeto. simplemente se escribirá el código utilizando new y el espacio 2. 3. Java ya viene con una abundancia de tipos ya hechos. así es que aunque algún tipo de almacenamiento existe en la pila-en particular. Hay seis diferentes lugares donde se almacenan los datos. Esta es una especie de piscina de memoria de propósito general (también en la memoria RAM) donde viven todos los objetos.como “Hágame como uno nuevo de esos objetos”. Así que en el siguiente ejemplo podemos decir: String s = new String(“asdf”). 1. Esto es una forma muy rápida y eficiente para ubicar espacio de almacenamiento. a diferencia de la pila. Se encuentra en el área de la memoria RAM (Memoria de acceso aleatorio). El compilador Java debe saber. String no es el único tipo que existe. así es que los registros son reubicados por el compilador de acuerdo con sus necesidades. Este restricción de lugar limita la flexibilidad de sus programas. Lo que es mas importante que se puede hacer es crear sus propios tipos. 93 . en particular como la memoria es arreglada. La pila. pero tiene apoyo del procesador mediante el puntero de pila. Lo bueno del heap es que. Esto no solo significa “Hágame como un nuevo String”. también indica como crear ese String dando una cadena de caracteres como información inicial. referencias a objetos-los objetos por si solos no son colocados en la pila. Esta es la forma mas rápida de almacenar los datos porque se encuentra en un lugar muy diferente a cualquier otro dato almacenado: adentro del procesador. cuando es creado el programa el tamaño exacto del tiempo de vida de todos los datos que son almacenados en la pila. Donde se almacena Es útil visualizar algunos aspectos de cómo las cosas son colocadas cuando el programa esta corriendo. De hecho. seguido solo por los registros. Registros. Sin embargo el numero de registros es muy limitado. esta es la actividad fundamental en la programación Java. El heap. El puntero de pila es movido hacia abajo para crear un nuevo espacio en memoria y hacia arriba para liberar esa memoria. No se tiene un control directo y no se ve ninguna evidencia en sus programas de que los registros verdaderamente existan. Claro que. el compilador no necesita saber cuanto espacio necesita en el heap para algo o cuanto tiempo va a permanecer ahí. porque debe generar el código para mover el puntero de pila arriba y abajo. y esto es lo que aprenderá en el resto de este libro.

4. La razón para este tratamiento especial es que crear un objeto con new -especialmente si es pequeño. proporcionar soluciones completas de persistencia. y a pesar de eso puedan ser resucitados en un objeto basado en RAM regular cuando sea necesario. El almacenamiento contiene datos que están disponibles en cualquier momento de la ejecución de un programa. El truco con estos tipos de almacenamiento es convertir los objetos en algo que pueda existir en otro medio. 5. generalmente para ser enviados a otra máquina. como se puede hacer en C++). 6. Los valores constantes son colocados directamente en el código del programa donde están a salvo de que alguien los cambie. podemos pensar en esto como tipos “primitivos” que se utilizarán a menudo en los programas. Almacenamiento no-RAM.com . pueden existir cuando el programa no esta ejecutándose. Almacenamiento constante. Para esos tipos Java retrocede a una estrategia de C y C++. Claro que hay un precio que pagar por esta flexibilidad: toma mas tiempo asignar memoria en el heap que hacerlo en la pila (esto si se pudiera crear objetos en la pila en Java. los cuales son transformados en flujos de bytes. Se puede utilizar la palabra clave static para especificar un elemento particular que es estático. Casos especiales: tipos primitivos Este es el grupo de tipos que obtienen un tratamiento especial. Si los datos se encuentran completamente fuera del programa. en lugar de crear la variable utilizando new . o una simple variable-no es muy eficiente dado que new coloca los objetos en el heap. Esto es. una variable “automática” es creada que no es una 94 Pensando en Java www.BruceEckel. Java proporciona soporte para persistencia liviana (lightweight persistence) y futuras versiones pueden . A veces las constantes son acordonados por un anillo de seguridad por si mismos así que opcionalmente pueden ser colocados en la memoria de solo lectura (ROM). “Estático” es utilizado en el sentido de “en una posición fija” (a pesar de que es también en la RAM). Almacenamiento estático. pero los objetos en Java nunca son colocados en esta zona de almacenamiento.es asignado en el heap cuando el código es ejecutado. y los objetos persistentes que son colocados en disco y no se borran cuando el programa es terminado. Los dos ejemplos primarios de estos son los objetos en flujo (streamed objets).

referencia. así es no busque tipos sin signo. y es colocada en la pila que es mucho mas eficiente. Las razones para hacer esto serán mostradas en un capítulo mas adelante. Que significa que si quiere crear un tipo no primitivo en el heap para representar un tipo primitivo. Esto es. Character C = new Character(c). Tipo primitivo boolean char byte short int long float double void Tamaño 16 bits 8 bits 16 bits 32 bits 64 bits 32 bits 64 bits - Mínimo Unicode o -128 -215 -231 -263 IEEE754 IEEE754 - Máximo Unicode 216-1 +127 +215-1 +231-1 +263-1 IEEE754 IEEE754 - Tipo de envoltura Boolean Carácter Byte Short Integer Long Float Double Void Todos los tipos tienen signo. Estos tamaños no cambian de una a otra arquitectura como la mayoría de los lenguajes. ninguna tiene una primitiva análoga. Por ejemplo: char c = ‘x’. Ambas clases tienen métodos que proporcionan analogías para las operaciones que se ejecutan con los tipos primitivos. Números de precisión alta Java incluye dos clases para representar aritmética de precisión alta: BigInteger y BigDecimal. A pesar de que se aproximan a ajustar con alguna de las categorías de clase de “envoltura”. Los tipos de datos primitivos también tienen clases “envolturas” (wrapper). El tamaño del tipo boolean no es esta explícitamente definido. Java determina el tamaño de cada tipo primitivo. simplemente se utilizan llamadas a métodos en lugar de 95 . solo esta capacitado para tomar valores literales true o false. O también podemos utilizar: Character C = new Character(‘x’). Estos tamaños invariables es una razón para que los programas Java sean portátiles. puede utilizar la envoltura asociada. Esta variable almacena un valor. si se quiere hacer algo con un BigInteger o un BigDecimal se puede hacer también con int o float.

BigDecimal se utiliza para números de punto fijo de precisión arbitraria. Se debe asignar un objeto a cada referencia antes de utilizarlo.com . También. pero asumiendo que es seguro e incrementa la productividad es un precio justo. El chequeo de rango incluye un pequeño precio a pagar de memoria en cada arreglo así como la verificación del índice en tiempo de ejecución. Cuando crea un arreglo de objetos. y cada una de estas referencias es automáticamente inicializada a un valor especial que esta indicado con la palabra clave null. las operaciones serán mas lentas. Esto significa que se puede representar valores enteros de cualquier tamaño sin perder información durante las operaciones. 96 Pensando en Java www. así es que muchos de los problemas que son flagelos de los programadores de C y C++ no se repiten en Java. Todo será explicado en detalle en capítulos siguientes. Usar arreglos en C o en C++ es peligroso porque aquellos esos arreglos son simplemente bloques de memoria.BruceEckel. Si un programa accede a un arreglo fuera de este bloque o utiliza la memoria antes de la inicialización (los errores mas comunes de programación) se obtienen resultados impredecibles. y si intentamos utilizar una referencia null. Se esta cambiando velocidad por exactitud. cuando Java ve null reconoce que la referencia en cuestión no esta apuntando a un objeto. También se pueden crear arreglos de primitivas. En este caso. el problema será reportado en tiempo de ejecución. se pueden utilizar estos para cálculos monetarios exactos por ejemplo. Estos.operaciones. Un arreglo en Java esta garantizado a ser iniciado y no pueden ser accedidos fuera de ese rango. Consulte la documentación en línea por detalles acerca de los constructores y los métodos que se pueden llamar para estas dos clases. errores típicos de arreglos son prevenidos en Java. dado que son mas evolucionadas. también el compilador garantiza la inicialización agregando ceros en la memoria de ese arreglo. BigInteger soporta enteros de precisión arbitraria. Una de las principales metas de java es la seguridad. esta creando en realidad un arreglo de referencias. Arreglos en Java Virtualmente todos los lenguajes de programación soportan arreglos.

Dado que Java es un lenguaje de forma libre. La sangría hace el código mas fácil de leer. /* ilegal */ } } El compilador indica que la variable x ya esta definida. Se ve que no es posible hacer lo siguiente. aunque sea legal en C y en C++: { int x = 12. C++ y Java. Alcance Muchos lenguajes procesales tienen el concepto de alcance (scope). /* solo esta disponible x */ { int q = 96. En C. ¿Cuanto tiempo estuvo la última variable? ¿Si se supone que hay que destruirla. Este determina la visibilidad y el tiempo de vida de los nombres definidos en determinado alcance. los espacios extra. Esta habilidad que tienen C y C++ de “esconder” una variable en un largo alcance no esta permitida porque los diseñadores de Java piensan que da lugar a programas confusos. { int x = 96. 97 . el alcance está determinado por las llaves { y }. y esta sección muestra como Java simplifica el tema del trabajo de limpieza. el concepto de tiempo de vida de una variable ocupa una significativa parte del esfuerzo de programación. cuando deberías hacerlo? Las confusiones por tiempos de vida de variable acumulan una gran cantidad de errores.No se necesita nunca destruir un objeto En muchos lenguajes de programación. /* x y q están disponibles */ } /* solo x esta disponible */ /* q esta “fuera de alcance” */ } Una variable definida sin alcance esta disponible solo en el final de ese alcance. Así es que dado un ejemplo: { int x = 12. tabulaciones y retornos de carro no afectan el problema resultante.

BruceEckel. y cuando no se utilizan mas se van a ir solos.com . } /* final del alcance */ La referencia a s deja de existir en el final del alcance. En este pequeño código.Alcance de los objetos Los objetos de java no tienen el mismo tiempo de vida que las primitivas. Sin embargo. y ciertamente eso tiene sentido. Los problemas mas grandes parecen ocurrir en C++ porque no se tiene ayuda del lenguaje acerca de si los objetos están disponibles cuando se necesitan. que determina como un objeto se verá y comportará? Dicho de otra forma. ¿Si Java deja los objetos dando vueltas por ahí. la totalidad de los problemas escabrosos de programar C++ simplemente se desvanecen en Java. Esto significa que no es necesario preocuparse por reclamar memoria. Aquí es donde un poco de magia se sucede. que evita que llenen la memoria y detengan el programa? Este es exactamente el tipo de problema que sucede en C++. en donde el programador olvida liberar memoria. para que pueda ser utilizada por nuevos objetos. Java tiene un recolector de basura(garbage collector). Históricamente sin embargo. Nos olvidamos de eso porque los objetos creados con new siguen presentes tanto como se quiera. Esto elimina cierto tipo de problema: el llamado “filtración de memoria” (memory leak). simplemente se crean objetos. que observa todos los objetos cuando son creados con new y resuelve cuales no son referenciados mas. entonces libera la memoria de esos objetos. el objeto String al que s fue apuntado sigue ocupando memoria. Esto plantea una pregunta interesante. no hay forma de acceder al objeto porque la única referencia al objeto esta fuera del alcance. En capítulos siguientes se verá como la referencia a el objeto puede ser pasada y duplicada en el transcurso del programa. este queda colgado pasado el final del alcance. ¿Que establece el tipo de un objeto? Siempre se espera una palabra clave llamada “tipo”. Y mas importante. Cuando se crea un objeto utilizando new . De esta manera si se usa: { String s = new String("una cadena"). Creando nuevos tipos de datos: clases ¿Si todo es un objeto. muchos lenguajes orientados a objetos han utilizado la palabra clave class que significa “voy a indicarte como se verá un 98 Pensando en Java www. en C++ se debe asegurar que destruye los objetos cuando se ha terminado con ellos.

seguido por el nombre del miembro adentro del objeto: 99 . Este se logra comenzando con el nombre del objeto referenciado. float f. } Esta clase no hace nada. el cuerpo de la clase consiste solo en un comentario (el comienzo. Cada objeto tiene su propio espacio para sus datos miembro. La palabra clave class (que es tan común que no le voy a colocar mas negrita en este libro) esta seguida por el nombre del nuevo tipo. no se le puede enviar ningún mensaje interesante) hasta que no se definan algunos métodos para esto. De hecho. las llaves y lo que hay adentro. se pueden colocar dos tipos de elementos en las clases: datos miembro (a veces llamados campos).nuevo tipo de objeto”. Un dato miembro es un objeto de cualquier tipo con el que nos comunicamos por medio de su referencia. Si es una referencia a un objeto. pero primero debe conocer como se hace referencia a un miembro de un objeto. boolean b. y funciones miembro (llamadas típicamente métodos). pero podemos crear un objeto: DataOnly d = new DataOnly (). Se pueden asignar valores a los miembros datos. Pueden ser uno de los tipos primitivos (que no son referencias). así es que ahora se puede crear un objeto de este tipo utilizando new : NombreDelTipoA a = new NombreDelTipoA (). En AtypeName. lar referencias pueden también ser inicializadas en el momento de la definición). los datos miembros no son compartidos entre objetos. que será discutido mas adelante en este capítulo). crear objetos con estas clases. debe inicializarse la referencia para poder conectarlo a un objeto actual (utilizando new . y enviar mensajes a aquellos objetos). seguido por un periodo (punto). como se ha visto ya) en una función especial llamada constructor (descrito totalmente en el Capitulo 4). no se le puede decir que haga mucho de nada (esto es. Campos y métodos Cuando se define una clase (y todo lo que se hace en Java es definir clases. Si es un tipo primitivo podemos iniciarlo directamente en el punto de la definición de la clase (como se ha visto anteriormente. Por ejemplo: class NombreDelTipoA { /* el cuerpo de la clase va aquí */ } Esto introduce un nuevo tipo. así que no es mucho lo que se puede hacer con esto. Aquí hay un ejemplo de una clase con algunos datos miembros. class DataOnly { int i.

f = 1. De esta manera.referenciaAObjeto.1f. Esto garantiza no aplicar a variables “locales”-aquellas que no son campos de una clase.BruceEckel.0d Estos son los valores por defecto garantizados por Java cuando una variable es utilizada como miembro de una clase.com . reduciendo una fuente de errores. Para entender como trabajan estos.capacidad = 100. Esto asegura que las variables miembro de tipos primitivos serán siempre inicializadas (algo que C++ no hace).0f 0.b = false. Para esto . d. Por ejemplo: miAvion.miembro Por ejemplo: d. Valores por defecto de miembros primitivos Cuando un tipo de datos es miembro de una clase. estos valores iniciales pueden no ser los correctos o legales para el programa que se esta escribiendo. porque no tiene funciones miembros (métodos). solo guardar datos. Sin embargo. d. explicados mas adelante.i = 47.tanqueIzquierdo. se deberá primero entender los argumentos y valores de retorno que serán . Es también posible que un objeto pueda tener adentro otros objetos que contienen datos que se quieran modificar. Es mejor explícitamente inicializar las variables. solo mantenga “conectado los puntos”. es garantido obtener un valor por defecto si no se ha inicializado: Tipo primitivo boolean char byte short int long float double Valor por defecto false ‘\u0000’ (null) (byte)0 (short)0 0 oL 0. La clase DataOnly no puede hacer mucho. si dentro de una definición de función tenemos: 100 Pensando en Java www.

Los métodos en java determinan los mensajes que un objeto puede recibir. supongamos que tenemos un método f() que no tiene argumentos y retorna 2 Los métodos estáticos (static). Por ejemplo. Los métodos en Java pueden ser creados solo como parte de una clase. y valores de retorno Hasta ahora el termino función ha sido utilizado para describir una subrutina nombrada. 101 . Un método puede ser llamado solo por un objeto2. pueden ser llamados por la clase. no será automáticamente inicializada a cero. el tipo retornado y el cuerpo. como “una forma de hacer algo”. Si se olvida. los argumentos. He aquí la forma básica: tipoRetornado nombreDelMetodo ( /* lista de argumentos */ ) { /* Cuerpo del método */ } El tipo retornado es el tipo del valor que el método retorna luego de llamarlo. Llamamos un método de un objeto nombrando el objeto seguido de un período (punto).int x. podemos continuar pensando en términos de funciones.nombreDeMetodo(arg1. sin un objeto. argumentos. Es realmente una diferencia sintáctica. pero desde ahora en adelante “método” será utilizado en este libro preferiblemente antes que “función”. pero en Java estos son errores). Es nuestra responsabilidad asignar un valor apropiado antes de utilizar la variable x. de la siguiente forma: nombreDeObjeto. En esta sección aprenderemos que tan simple es definir un método. Java definitivamente mejora a C++. El nombre del método y la lista de argumentos juntos excepcionalmente identifican el método. El término que es mas comúnmente utilizado en Java es método. se obtendrá un mensaje de error en tiempo de compilación. se tendrá un error de compilación indicando que la variable puede no haber sido iniciada (Muchos compiladores C++ advertirán la presencia de variables sin iniciar. seguido por el nombre del método y su lista de argumentos. Métodos. arg2. La lista de argumentos entrega a el método los tipos y nombres de la información que se pasa al método. Si se intenta llamar el método equivocado para un objeto. arg3) . x será algún valor arbitrario (como en C y C++). que aprenderemos mas adelante. y ese objeto puede ser capaz de ejecutar los métodos llamados. Las partes fundamentales de un método son. el nombre. Si deseamos. Entonces.

char. si bien pasamos objetos. lo que debemos pasar debe ser una cadena.f(). o dos bytes de largo para soportar caracteres Unicode). En general. El argumento de este tipo es String y es llamado s. el valor es colocado inmediatamente después de la instrucción return. el valor retornado es producido por la evaluación de la expresión s. hace dos cosas. se puede tratar simplemente como otro objeto (Podemos enviarle mensajes). int. que es uno de los métodos de la clase Strings. En este caso. si tenemos un objeto llamado a para cada f() que pueda ser llamado. La POO es también resumida como simplemente “enviar mensajes a objetos”. Como en cualquier situación en Java donde parece ser que se manejan objetos por todos lados. esta información-al igual que cualquiera en Java-toma la forma de objetos. si el método produce un valor. Como podemos adivinar. La lista de argumentos La lista de argumentos del método especifica que información pasaremos a el método. Segundo. Si el argumento supuestamente es un String. He aquí la definición. estamos pasando actualmente referencias3. byte. Este acto de llamar un método es comúnmente llamado enviar un mensaje a un objeto. Como podemos ver el uso de la palabra clave return.com . El tipo de la referencia debe ser el correcto. 3 Con la usual excepción de los anteriormente “especiales” tipos de datos boolean. Considerando un método que toma un String como argumento. el método length() es llamado. (Cada char en un String es de 16 bits. Aquí. En el siguiente. Así es que. sin embargo. el mensaje es f() y el objeto es a. El tipo retornado debe ser compatible con el tipo de x. Una ves que s es pasado al método. short. Entonces. significa “dejando el método. lo que especifiquemos en la lista de argumentos son los tipos de los objetos que se pasarán y el nombre que utilizará cada uno. 102 Pensando en Java www.largo() * 2. Primero. float y double. que debe ser colocada con una definición de clase para ser compilada: int storage(String s) { return s. long. he terminado”.BruceEckel.length() * 2. este retorna el numero de caracteres en una cadena. lo que realmente tratamos de decir es que pasamos referencias a objetos. colocaremos esto: int x = a.un valor del tipo int. } Este método nos muestra cuantos bytes son requeridos para almacenar la información en un String particular.

C++ introduce los espacios de nombres utilizando palabras clave. pero en el siguiente capítulo veremos como hacer el trabajo de bajo nivel tomando decisiones con un método. He aquí algunos ejemplos: boolean bandera() { return true. Esto es efectivamente mucho de lo que lo que sigue. pero si el tipo de retorno no es void el compilador forzará (con mensajes de error) a retornar el tipo apropiado independientemente de donde retorne. y otro programa usa el mismo nombre en otro módulo. se debe hacer indicando que el método retorna void. Para producir nombres sin ambigüedad de una librería. Sin embargo. es el control de nombres. Si usamos un nombre en un módulo del programa. se puede ver como un programa simplemente es una acumulación de objetos con métodos que toman otros objetos como argumento y se envían mensajes a esos otros objetos. De hecho. Visibilidad de nombres Un problema en cualquier lenguaje de programación. la palabra clave return solo es utilizada para salir del método. Para solucionar este problema.718f. como distinguimos un nombre de otro y prevenir que los nombres entren en “conflicto”? En C esto es un problema porque un programa es un mar inmanejable de nombres.Podemos retornar cualquier tipo que deseemos. Java es capaz de evitar todo esto con una estrategia nueva. así es que los conflictos siguen siendo posibles. los creadores de Java quieren que se utilice los nombres de dominios de la Internet en reversa dado que es garantido que sean únicos. Se puede retornar de un método en cualquier punto. enviar mensajes es suficiente. Dado que mi dominio es 103 . C++ sigue permitiendo datos y funciones globales. y es por consiguiente innecesaria cuando se alcanza el final del método. } void nada2() {} Cuando el tipo de retorno es void. En este punto. Armando un programa Java Hay otros muchos temas que debemos entender antes de entender el primer programa Java. } void nada() { return. Las clases en C++ (de donde están basadas las clases de Java) anidan funciones con clases así es que no pueden entrar en conflicto con nombres anidados en otras clases. Para este capítulo. } float baseLogaritmicaNatural() { return 2. el que fija las condiciones no es diferente a un nombre de dominio de la Internet. pero si no se debe retornar nada.

pero mas de una definición de esa clase existe (presumiblemente son definiciones diferentes). da a entender que todos sus ficheros automáticamente están en sus propios espacios de nombres. pero eso es un problema. En Java 1. y cada clase con un fichero debe ser un único identificador. nombres de dominios inversos.utilidades.vulnerabilidades. mi librería de utilidades de vulnerabilidades puede ser llamada com. Parte del camino a través del desarrollo de Java 2. O peor. cual es la librería de clases (en otros lenguajes. estaban capitalizados por convención. por ejemplo: 104 Pensando en Java www.com .BruceEckel. Este mecanismo. La mayoría del tiempo se estará utilizando componentes de la librería estándar de Java que vienen con su compilador. pero recuerde que todo el código en Java debe ser escrito adentro de una clase).BruceEckel. debe eliminar todas las ambigüedades potenciales. Utilizando otros componentes En el momento que se quiera usar una clase predefinida en un programa. y la totalidad del nombre del paquete es en minúsculas. simplemente se indica. Por su puesto. fue descubrir que esto causaba problemas. ¿Que sucede con las clases que están en otros ficheros? Debemos suponer que el compilador debería ser lo suficientemente inteligente para simplemente ir y encontrarla. Con ellas. Así no necesitamos aprender herramientas especiales de lenguaje para solucionar este problema-el lenguaje se encarga de esto por nosotros. simplemente utilizamos la clase-aun si la clase no esta definido hasta tarde en el fichero. etc.com .vulnerabilidades. así que la librería aparecería: COM. sin embargo. import le indica al compilador que traiga de un paquete. los puntos representan subdirectorios. una librería puede consistir en funciones y datos de la misma forma que clases.1 las extensiones de dominio com . edu . org .bruceeckel. Esto es logrado indicándole a el compilador de Java exactamente cuales clases queremos usar mediante la palabra clave import. net. no hay necesidad de preocuparnos acerca del largo. Luego de que se invierte el nombre de dominio. Para solucionar este problema..0 y Java 1. la clase debe existir en el mismo fichero de código fuente de donde es llamado. Java elimina el problema de la “referencia anticipada” así es que no se necesita pensar en ello.utilidades.bruceeckel. imaginemos que se esta escribiendo un programa y cuando se esta armando agregamos una nueva clase en la librería que entra en conflicto con el nombre de una clase ya existente. En ese caso. Imaginemos que se quiere utilizar una clase de un nombre particular. el compilador debe saber donde encontrarla.

uitil.*. Por ejemplo. debemos simplemente colocar la palabra clave static antes de la definición. no pueden directamente acceder a miembros o métodos no estáticos simplemente llamando a otros miembros sin referenciar a un nombre de objeto (dado que los miembros no estáticos y métodos deben ser ligados a un objeto particular). lo siguiente produce un miembro de dato estático y lo inicializa: class pruebaEstatica { 105 . Pero hay dos situaciones en la que ese acercamiento no es suficiente. Esto es. y no para objetos particulares de la clase. Es mas común importar una colección de clases de esta manera que importar las clases individualmente. Esto es fácil de lograr usando ‘*’ para indicar un comodín: import java. y en ese punto el espacio de almacenamiento de los datos es creado y los métodos se hacen disponibles. Así que si nunca se creo un objeto de esa clase podemos llamar un método estático o acceder a una parte de un dato estático.ArrayList. Para hacer un dato miembro o método estático. se necesita un método que se pueda llamar aun si no se han creado objetos. La otra es si se necesita un método que no esta asociado con un objeto de la clase. sin importar cuantos objetos son creados. que dado que los métodos estáticos no necesitan objetos para ser creados antes de utilizarlos. No tenemos nada hasta que se crea un objeto de esa clase con new . util contiene muchas clases y deberíamos utilizar muchas de ellas sin declarar explícitamente todas ellas. Podemos lograr estas dos cosas con la palabra clave static .util. A veces en la literatura de Java se utiliza esos términos también. cuando creamos una clase estamos describiendo que objetos de esa se ven y como se comportarán. Claro. o tal vez aun si el objeto no ha sido creado. Sin embargo. para decirle al compilador que se quiere utilizar la clase de Java ArrayList. significa que el dato o método no está atado a una instancia particular de un objeto de una clase. dado que con los métodos y datos no estáticos debemos conocer el objeto en particular con el que se va a trabajar.import java. La palabra clave static Por lo general. Algunos lenguajes orientados a objetos utilizan los términos datos de la clase y métodos de la clase. Con datos y métodos no estáticos se debe crear un objeto y utilizarlo para acceder a los datos o métodos. dando a entender que los datos y métodos existen para la clase entera como un todo. Cuando se dice que algo es estático. Una es si se quiere tener un solo lugar para almacenar cierto tipo de dato.

Lógica similar se aplica a métodos estáticos.i++. Hay dos formas de referirse a una variable estática. En este punto.i++. st1. En este momento. Dado que static .metodo(). se puede nombrar mediante el objeto. cuando es aplicado a un método no es tan dramático. ambos. También podemos referirnos a ellos con el nombre de la clase.i. un método estático puede crear o utilizar nombres de objetos de su tipo.incr(). a través de un objeto.incr() O.i tienen el valor 48. sf. en la definición del método main() que es el punto de entrada para ejecutar una aplicación. Se puede llamar a incr() de la forma típica. PruebaEstatica. Puede referirse a un método estático a través de un objeto como con cualquier método. } Ahora. 106 Pensando en Java www. si creamos dos objetos pruebaEstatica. fue aplicado a un dato miembro. indicando por ejemplo. st1. } } Se puede ver que el método de DiversionEstatica llamado incr() incrementa el dato estático i.com . definitivamente cambia la forma en que el dato es creado (uno para cada clase contra el no estático uno para cada objeto). Como se indica mas arriba. Si se define un método estático de una forma similar: class StaticFun { static void incr() { PruebaEstatica.BruceEckel.i y st2. Como cualquier método. como incr() es un método estático. Ambos objetos compartirán el mismo i. DiversionEstatica sf = new DiversionEstatica(). consideremos: PruebaEstatica st1 = new PruebaEstatica(). así es que muchas es veces utilizado como “pastor” para bloquear instancias de su propio tipo.static int i = 47. El operador ++ incrementa la variable.i y st2. Un uso importante de static para métodos es permitir que se llame a un método sin crear un objeto. solo tendrán un lugar donde almacenan PruebaEstatica.i tienen el mismo valor de 47 dado que se refiere a la misma dirección de memoria. algo que no se puede hacer con un miembro no estático (Esta es la forma preferible de referirse a una variable estática dado que enfatiza su naturaleza estática). Esto es esencial. st2. o con la sintaxis especial adicional nombreDeClase. PruebaEstatica st2 = new PruebaEstatica(). podemos llamarlo directamente a través de su clase: DiversionEstatica.i. como veremos.

lang esta implícitamente incluida en cada fichero de código.read(). Ahora se pueden encontrar cada clase por separado de las que vienen con Java. esto es porque ciertas librerías de clases son automáticamente traídas en cada fichero Java: java. la fecha. hoy es: "). se verá todas las diferentes librerías de clases que vienen con Java.lang.util. Este comienza desplegando una cadena.sun.println("Hola. y luego.java import java.out. se deberá ver todas las clases.lang. he aquí el programa4. lo que indica que se encuentra en la librería util y se debe colocar import java. Este código involucra conceptos que no serán introducidos hasta mucho mas adelante en este libro. Se puede seleccionar “Tree” en la documentación de Java. Entonces se puede buscar Date con la función “Find”. traerá una lista de clases que son parte de la librería. utilizando la clase Date de la librería estándar de Java. lo que significa que debemos importar otra librería para usarla. public class HolaFecha { public static void main(String[] args) { System.util. así es que no intente entenderlo ahora.lang.Su primer programa Java Finalmente. Si se mira la lista de paquetes. En un navegador se puede ver la documentación de Sun (si se han bajado los programas de java.Date.* para poder utilizar Date. try { Sytem.out.println(new Date()). 107 . } catch (Exception e) {} Esto detendrá la salida hasta que se presione “Entrada” (o algunas otras teclas).*. Se verá que está listada como java.util. No hay una clase Date listada en ja va. de otra forma deberá instalarse la documentación). } } Al comienzo de cada fichero de programa se deberá colocar el comando import para traer cualquier clase extra que se necesite para el código en ese fichero.com. Note que dice “extra”. Dado que java. estas clases están disponibles automáticamente. Se puede ver que un estilo de comentario adicional es introducido aquí: el ‘//’. 4 Algunos ambientes de programación destellarán el programa en la pantalla y lo cerrarán sin que exista la posibilidad de ver los resultados. Si se conoce la librería en donde se encuentra una clase particular. System. Podemos colocar este pequeño código en el final del main() para introducir una pausa en la salida. que es un comentario hasta el final de la línea: // HolaFecha.in. Si se selecciona java. pero hará la trampa.

Los args no serán utilizados en este programa.out.println(“cosas”) en cualquier lugar para imprimir algo en la consola. pero este libro asumirá que estamos utilizando el JDK de Sun.out.com se puede encontrar información y enlaces que puede dirigirnos a través del proceso de bajar e instalar JDK para una plataforma en particular. y todos los demás programas en este libro. una de las clases en el fichero debe tener el mismo nombre que el fichero (El compilador se queja si no lo hacemos de esta forma). Cuando se crea un programa independiente como este. Dado que es estático no se necesita crear nada. PrintStream es mostrado en la descripción como un vínculo. Convenientemente. en java. el objeto Date es innecesario.BruceEckel. se debe tener un ambiente de programación Java. y se selecciona java. El argumento para main() es un arreglo de objetos String. Por ahora estamos interesados en println(). 108 Pensando en Java www. Compilando y ejecutando Para compilar y ejecutar este programa. que en efecto significa “imprima lo que le estoy dando en la consola y termine con un final de línea”. Lo que se puede hacer con este objeto out esta determinado por su tipo: un PrintStream.println(new Date()). No hay necesidad de preocuparse por ello. Si se esta utilizando otro sistema de desarrollo. La línea que imprime la fecha es muy interesante: System.Si se regresa al principio. Hay algunos ambientes desarrollados por terceros.sun. se pobra ver que la clase System tiene varios campos. y el recolector de basura puede venir y llevárselo en cualquier momento. de esta forma se puede ver la lista de todos los métodos que se pueden llamar para PrintStream. Esto. esta clase debe contener un método llamado main() con la siguiente firma: public static void main(String[] args) { La palabra clave public significa que el método esta disponible para el mundo exterior (descrito en detalle en el capítulo 5). Considerando el argumento: un objeto Date es creado justo para enviar el valor a println().lang y luego System . En la Internet. Tan pronto como este comando es terminado. que es gratis. se necesitará ver la documentación para el sistema para determinar como compilar y ejecutar programas. Hay muchos de ellos que son cubiertos mas adelante en este libro. y si se selecciona out se descubrirá que hay un objeto estático PrintStream. pero el compilador insiste en que ellos estén allí porque sostienen los argumentos invocados en la línea de comandos. El objeto out esta siempre y simplemente se puede utilizar. El nombre de la clase es el mismo que el del fichero. en cualquier programa que se escriba podemos hacer System.com .

Este es el proceso con el que se compila y ejecuta cada uno de los programas de este libro.BruceEckel. Sin embargo. así es que no hay diferencia en decir: /* Este es un comentario que continúa a través de las líneas */ La segunda forma de comentar llega con C++. Nótese que muchos programadores comienzan cada línea de un comentario continuo con un *. estos comienzan con un // y continúan hasta el final de la línea. Esto creará un subdirectorio para cada capítulo de este libro. si solo se obtiene el indicador de línea de comando nuevamente.com). Por otro lado. podrá verse que el código fuente de este libro también tiene un fichero llamado makefile en cada capítulo. El primero es el tradicional comentario estilo C que fue heredado por C++. posiblemente a través de muchas líneas. En la página del libreo en www. Estos comentarios comienzan con un /* y continúan.BruceEckel. hasta un */. así que se podrá ver: /* Este es un comentario * que continúa * a través de las líneas */ Recordemos. y este contiene comandos “make” para armar automáticamente los ficheros para ese capítulo. sin embargo que todo lo que está entre el /* y el */ es ignorado. No se necesita cazar a través del teclado en búsqueda de un / 109 . bajemos y desempaquetemos el código fuente de este libro (se puede encontrar en el CD ROM que viene con este libro. Podemos movernos a el subdirectorio co2 y escribir: javac HelloDate. o en www. se podrá escribir: java HelloDate y tendrá el mensaje y la fecha como salida.java Este comando puede no producir respuesta. Este tipo de comentario es conveniente y comúnmente utilizado porque es fácil. Si se tiene algún tipo de mensaje de error quiere decir que no ha instalado JDK correctamente y se necesita investigar ese problema. Comentarios y documentación incluida Hay dos tipos de comentarios en Java. Estos son los comentarios de una sola línea.Una vez que JDK esta instalado y configurada la ruta podremos encontrar javac y java.com hay detalles de como utilizar los makefiles.

un comentario de variable aparece inmediatamente antes que la 110 Pensando en Java www. De esta forma se puede olvidar del poco trabajo para generar una documentación de programa decente. Hay dos formas principales de utilizar javadoc: insertando HTML o utilizando “etiquetas de documentación” (Doc tags). se necesita una sintaxis especial para indicar documentación especial y herramientas para extraer esos comentarios y colocarlos en una forma útil. Esto es lo que Java ha hecho. Los comentarios terminan con */ como es usual. La salida de javadoc es un fichero HTML que se puede ver con un navegador Web.com . y no se necesita cerrar el comentario.BruceEckel. Posiblemente el problema mas grande con documentar código es mantenerlo documentado. No solo extrae la información indicada por esas etiquetas. Sintaxis Todos los comandos se suceden con comentarios /**. sin embargo. La herramienta para extraer los comentarios es llamada javadoc. los métodos que contienen los comentarios. se presiona la misma tecla dos veces). Las etiquetas de documentación son comandos que comienzan con un ‘@’ y son colocadas al comienzo de una línea de comentario (Un ‘*’ inicial. Esto es. que corresponden con el elementos del comentario que preceden: clase.y luego un * (en lugar de eso. también extrae los nombres de las clases. Comentarios de documentación Una de las partes de Java mas consideradas. un comentario de una clase aparece a la derecha luego de una definición de clase. La manera mas fácil de hacer esto es colocar todo en el mismo fichero. y es tan fácil como cualquiera pueda esperar o incluso exigir de una documentación con todas las librerías Java. es que los diseñadores no consideran que escribir código sea la única parte importante de la actividadpiensan también en documentarlo. Así es que también se podrá ver: // Este es un comentario de una sola línea. Utiliza tecnología del compilador de Java para buscar etiquetas de comentarios especiales que se colocan en los programas. Hay tres “tipos” de comentarios de documentación. sin embargo. comienza a molestar cambiar la documentación cada ves que cambiamos el código. Si la documentación y el código están separados. Esta herramienta permite crear y mantener un único fichero y automáticamente generar documentación útil. variable o método. Dado que javadoc tiene una norma para crear documentación. es ignorado). La solución es simple: unir el código con la documentación. Para completar esta imagen.

Sería muy bueno entrar el código anterior. Los comentarios para miembros private y “frendly” (vea el capítulo 5) son ignorados y no se obtendrá ninguna salida (sin embargo. La salida para el código siguiente es un fichero HTML que tiene el mismo formato que el resto de la documentación de java.definición de la variable. Sin embargo. los asteriscos en el comienzo de una línea son eliminados por javadoc. /** * Posemos <em>tambien</em> insertar una lista: * <ol> * <li> Primer item * <li> Segundo item * <li> Tercer item * </ol> */ Debe notarse que con los comentarios de documentación. Esto tiene sentido. que es la perspectiva del cliente programados. se puede utilizar la bandera -private para incluir miembros private igual). todos los comentarios de las clases son incluidos en la salida. * </pre> */ Podemos también usar HTML justo como se desearía en cualquier otro documento para dar formato a texto regular en sus descripciones. Javadoc le da un formato a todo lo este de acuerdo con la apariencia de la documentación estándar. /** * <pre> * System. enviarlo a través de javadoc y ver el fichero HTML para ver los resultados. junto con los espacios iniciales. HTML incrustado Javadoc pasa comandos HTML hacia el documento HTML generado. así es que los usuarios estarán cómodos con el formato y fácilmente pueden navegar en sus clases. /** Comentario de método */ public void f() {} } Note que javadoc procesará los cometarios de documentación solo para los miembros public y protected.out. y un comentario de un método aparece inmediatamente antes de la definición del método.println(new Date()). Como el ejemplo simple: /** Comentario de clase */ public class docTest { /** Comentario de variable */ public int i. Esto permite un uso completo de HTML. dado que solo los miembros públicos y protegidos están disponibles fuera del fichero. sin embargo el principal motivo es permitir formatear el código. No se pueden utilizar 111 .

Javadoc generará vínculos HTML con las etiquetas @see hacía otra documentación. @autor Esta es la forma: @autor información-del-autor 112 Pensando en Java www.BruceEckel. variables y métodos-pueden soportar HTML incrustado. @see: refiriéndose a otras clases Los tres tipos de comentarios de documentación (clases. que permite referirse a la documentación en otras clases. variables y métodos) pueden contener etiquetas @see. Cuando la bandera –version es colocada junto con javadoc en la línea de comando.encabezamientos como <h1> o <hr> en el código HTML insertado porque javadoc inserta sus propios encabezamientos y los demás interfieren con ellos. la información de la versión será colocada especialmente en la documentación generada en HTML. Todos los tipos de comentarios de documentación-clases.com . @version Esta es la forma: @version información-de-versión en donde la información-de-versión es cualquier información significativa que se vea que se debe incluir. Javadoc no verificará los vínculos así que se deberá estar seguro de que son válidos. La documentación de clase puede ser utilizada por interfaces (vea el capítulo 8). Las formas son: @see nombre-de-clase @see nombre-de-clase-totalmente-calificado @see nombre-de-clase-totalmente-calificado#nombre-de-metodo Cada uno agrega un vínculo “See Also” en la documentación generada. la documentación de clase puede incluir etiquetas para información de la versión y el nombre del autor. Etiquetas de documentación de clase Junto con el código HTML insertado y las referencias @see.

Etiquetas de documentación de métodos De la misma forma que la documentación insertada y las referencias @see. Se verá aparecer en la documentación HTML de Java para indicar que versión de JDK esta utilizando. los métodos permiten etiquetas de documentación para los parámetros. presumiblemente una por cada parámetro. Cuando la bandera –autor es colocada en la línea de comandos al invocar javadoc la información del autor será colocada especialmente en la documentación generada. presumiblemente su nombre.en donde la información-del-autor es. Se pueden tener varias etiquetas de autor para una lista de autores. Se pueden tener cualquier número de ellas. Toda la información de los autores será puesta junta en un solo párrafo en el código HTML generado. y la descripción es el texto que puede continuar en las subsecuentes líneas. pero deben ser colocadas de forma consecutiva. @since Esta etiqueta indica la versión de este código que da inicio a un aspecto particular. Etiquetas de documentación de variables La documentación variable puede incluir solo HTML insertado y referencias @see. La descripción es considerada terminada cuando una nueva etiqueta de documentación es encontrada. @param Esta es la forma: @param nombre-de-parámetro descripción en donde el nombre-de-parámetro es el identificador en la lista de parámetros. 113 . valores de retorno y excepciones. pero que también puede incluir su dirección de correo y otra información apropiada.

esta vez con agregados de comentarios de documentación. pero brevemente son objetos que pueden ser “lanzados” afuera del método si ese método falla. Ejemplo de documentación He aquí el primer programa Java nuevamente.util.0 */ public class HelloDate { /** Único punto de entrada a la clase y a la aplicadcion * @param args arreglo de argumentos Strings * @return No hay valor de retorno 114 Pensando en Java www. Así es que la forma para la etiqueta de excepciones es: @throws nombre-de-clase-totalmente-calificado descripción en donde nombre-de-clase-totalmente-calificado da un nombre no ambiguo de una clase de excepción que está definida en alguna parte.BruceEckel. A pesar de que solo una excepción puede emerger cuando se llama a un método.java import java. las cuales. * @author Bruce Eckel * @author www.*. /** El primer ejemplo de programa de Thinking in Java. dado que en un futuro será eliminada. Puede continuar el las líneas subsiguientes. todas necesitan descripciones. Un método que es marcado como @deprecated causa que el compilador emita una alerta si es utilizada. La etiqueta deprecated sugiere que no se use mas esa característica particular. //: c02:HelloDate. un método particular puede producir cualquier número de tipos diferentes de excepciones. @throws Las excepciones son mostradas en el capítulo 10.com * @version 2. @deprecated Esto es utilizado para marcar características que se encuentran reemplazadas por una característica mejorada.BruceEckel. y descripción (que puede continuarse en las subsiguientes líneas) indican el tipo particular de excepción que puede emerger de una llamada a un método.@return Esto es de la siguiente forma: @return descripcion donde descripcion le da un significado al valor retornado.com . * Despliega una cadena y la fecha de hoy date.

El código Java que se verá en las librerías Sun también siguen la ubicación de las llaves de apertura y cierre que se verá en este libro. y ser compasivo.org) usa esta información para extraer los archivos de código. Estilo de código Es estándar no oficial de Java es llevar a mayúscula la primer letra de un nombre de clase. System.. La última línea también termina con un comentario. void cambieElTonoDeLosColores (int nuevoTono) { // . y la primer letra de cada palabra es llevada a mayúscula. } Por supuesto debemos recordar que el usuario debe también escribir todos esos nombres largos. } } ///:~ La primer línea del fichero usa mi propia técnica de poner un ‘:’ como marca especial para la línea de comentario que contiene el nombre de fichero fuente.. y este indica el final del listado del código fuente. Para al menos todo lo demás: métodos.Python.out. que permite automáticamente extraer del texto de este libro y verificarlo con un compilador. como aquí: class TodosLosColoresDelArcoiris { // . y referencias a nombres de objetos.. } // . el estilo aceptado es el mismo que para las clases excepto que la primer letra del identificador es minúscula. Esa línea tiene la información de ruta a el fichero (en este caso.println(new Date())..out.. campos (variables miembro). irán todas juntas (esto es. co2 indica capítulo 2) seguido del nombre del fichero5. 5 Una herramienta que he creado usando Python (www. sin utilizar caracteres de subrayado para separar los nombres).. colocarlos en los subdirectorios apropiados y crear makefiles. hoy es: ").* @exception exceptions No se lanzan excepciones */ public static void main(String[] args) { System.println("Hola. Si el nombre de clase consiste en varia palabras. 115 . Por ejemplo: class TodosLosColoresDelArcoiris { int unEnteroRepresentandoColores.

2.BruceEckel. Necesitará solamente un método en esa clase (el “main” es el que se ejecuta cuando el programa se inicia). 116 Pensando en Java www. dispon ible por una pequeña propina en www. Modifique el ejercicio 3 a fin de que los valores de los datos en DataOnly sean asignados e impresos en main(). Encuentre los fragmentos de código que involucran ATypeName y colóquelos dentro de un programa que pueda compilar y ejecutar. Coloque los fragmentos de código de Data Only dentro de un programa que pueda compilar y ejecutar. 5. 1. aprenda como compilar y ejecutar programas en ese ambiente. 7. Compile el programa con javac y ejecútelo utilizando java. Para hacer esto. 6.Resumen En este capítulo se vio suficiente de la programación Java para entender como escribir un simple programa. luego aquello. Siguiendo el ejemplo HelloDate.com .com .java en este capítulo. como “si el resultado entregado es rojo. Escriba un programa que incluya y llame el método storage() definido como un fragmento de código en este capítulo.BruceEckel. 8. mundo” que simplemente imprima esa frase. luego se hará algo mas”. entonces hago otra cosa”? El soporte en Java para esta actividad fundamental de la programación será cubierta en el siguiente capítulo. 4. Escriba un programa que imprima tres argumentos tomados de la línea de comando. Convierta los fragmentos de código de StaticFun en un programa que se pueda ejecutar. los ejemplos hasta ahora son todos del tipo “se hace esto. y se tiene una visión general del lenguaje y de algunas de sus ideas básicas. Ejercicios La solución de los ejercicios seleccionados pueden encontrarse en el documento electrónico The T h i n k i n g i n J a v a A n n o t a t e d S o l u t i o n G u i d e. sin embargo. Recuerde que es estático y debe incluir la lista de argumentos aunque no la utilice. si no. 3. Convierta el ejemplo AllTheColorOfTheRainbow en un programa que compile y corra. Que si se quiere que el programa haga elecciones. necesitará indexar dentro del arreglo de Strings de la línea de comandos. hago eso. Si está utilizando un ambiente de desarrollo diferente que el JDK. cree un programa “Hola.

Extracte estos comentarios de documentación en un fichero HTML utilizando javadoc y véalos con su navegador.java. Encuentre el código de la segunda versión de HelloDate. luego ejecute javadoc con el. 117 . 12. que simplemente es un ejemplo de comentarios de documentación. 10.9. Agregue una lista de ítem HTML a la documentación del ejercicio 10. Verifique la documentación resultante con su navegador. Convierta docTest en un fichero que compile y corra. Ejecute javadoc para el fichero y vea los resultados con su navegador. 11. Tome el programa del ejercicio 1 y agréguele comentarios de documentación.

Utilizando operadores Java Un operador toma uno o mas argumentos para producir un nuevo valor. un programa debe manipular su mundo y hacer elecciones durante su ejecución. división (/) y asignación (=) igual que en cualquier lenguaje. En Java se manipulan objetos y datos utilizando operadores. 118 Pensando en Java www.3: Controlando el flujo del programa Como una criatura sensible.BruceEckel. multiplicación (*). Además. . La excepción es ‘=’. Nos deberemos sentir razonablemente confortable con los conceptos generales de operadores de nuestra experiencia previa programando. y se realizan elecciones con instrucciones de control. Los argumentos están en una forma diferente que las llamadas a métodos comunes. un operador puede cambiar el valor de un operado. Casi todos los operadores trabajan solo con primitivas. que trabajan con todos los objetos (y son un punto de confusión para los objetos). la clase String puede utilizar ‘+’ y ‘+=’. Esto es llamado un efecto secundario. así es que muchos de estas instrucciones y operadores serán ser familiares a programadores C y C++. pero el efecto es el mismo. Si nos encontramos moviéndonos torpemente un poco en este capítulo.com . asegurémonos mediante el CD ROM multimedia dentro de este libro: Thinking in C: Fundations for Java y C++ Contiene lecturas de audio. ‘==’ y ‘!=’. además. resta y menos unitario (-). Java fue heredado de C++. diapositivas. Suma (+). Todos los operadores producen un valor de sus operandos. El uso mas común de los operandos que modifican operandos es generar el efecto secundario para simplemente utilizarlas en operaciones sin efectos secundarios. ejercicios y soluciones específicamente diseñados para darnos la velocidad con la sintaxis de C necesaria para aprender Java. Java también ha agregado algunas mejoras y simplificaciones.

Java tiene reglas específicas que determinan el orden de evaluación.). si decimos A = B para primitivas. podemos asignar un valor constante a una variable (A = 4. un valor físico para almacenar un valor). } public class Assignment { 119 . Si modificamos A.Precedencia La precedencia de los operadores define como una expresión evalúa cuando varios operadores están presentes. Asignación La asignación se realiza con el operador =. tiene diferente significado que la misma instrucción con un grupo particular de paréntesis: A = X + (Y – 2) / (2 + Z). Como programador esta es la forma en la que se esperará en la mayoría de las situaciones.). La forma más fácil de recordar esto es que la multiplicación y la división sucede antes que la suma y la resta. pero no podemos asignar nada a un valor constante-no puede ser un lvalue (no podemos decir 4 = A. ambos terminan apuntando a el mismo objeto que apuntaba D. Un rvalue es cualquier constante. lo que se está manipulando es la referencia. cuando asignamos primitivas copiamos el contenido de un lugar a otro. Esto significa “tome el valor del lado derecho (a veces llamado el rvalue) y cópielo en el lado izquierdo (a veces llamado lvalue). Por ejemplo: A = X + Y – 2 / 2 + Z. así es que cuando asignamos “de un objeto a otro” estamos actualmente copiando una referencia de un lugar a otro. Esto significa que si decimos C = D para objetos. La asignación de primitivas es mas directo. Dado que la primitiva almacena el valor actual y no la referencia a un objeto. Por ejemplo. el contenido de B será copiado en A. En el momento en que manipulamos un objeto. variable o expresión que pueda producir un valor.java // Assignment with objects is a bit tricky. class Number { int i. El siguiente ejemplo demostrará esto: He aquí el ejemplo: //: c03:Assignment. y un lvalue que debe ser una variable distinta (Esto es. las cosas cambian. sin embargo. Los programadores a menudo olvidan las reglas de precedencia. así es que se suele utilizar paréntesis para crear un orden explícito de evaluación. Cuando asignamos objetos. Por ejemplo. B naturalmente no es afectado por esta modificación.

java // Passing objects to methods may not be what // you're used to. class Letter { 120 Pensando en Java www.BruceEckel. } } ///:~ La clase Number es simple y dos instancias de ella (n1 y n2) son creadas con un main(). pero a causa de que se asigna una referencia en la salida se vera: 1: n1. así es que se debería leer el apéndice A. pero como verá mas adelante realizar la manipulación de los campos de un objeto es desordenado y va contra los principios de una buen diseño de programación orientada a objetos.out.println("3: n1. En muchos lenguajes de programación se esperaría que n1 y n2 sean independientes en todo momento. deberemos tener en mente que la asignación de objetos puede tener sorpresas.i).println("1: n1.com . n1 = n2. n2.out. y n2 es asignado a n2.i: 47 2: n1. System.i: 47. n1 y n2 contienen la misma referencia. n1.i: 27.i: " + n1. Esto mantiene dos objetos separados en lugar de eliminar uno y tratar a n1 y n2 como el mismo objeto. n2. System. El valor i en cada clase Number se le asigna un valor diferente.i: 9.i: " + n2.i = 9. Por lo pronto.i = 47.i: 47 3: n1.i: " + n2.i: " + n1. System.i).i: " + n2.out.println("2: n1.public static void main(String[] args) { Number n1 = new Number().i = n2. n2. el objeto será limpiado por el recolector de basura. Aliasing en la llamada a métodos El aliasing puede ocurrir cuando pasamos un objeto a un método: //: c03:PassObject. Esto no es un tema trivial.i + ". n2.i + ".i). n1. n2. y n1 es cambiado. que es puntero a el mismo objeto (La referencia original que estaba en n1 y apuntaba a el objeto que guardaba fue sobrescrita durante el asignación y efectivamente perdido. n2. Number n2 = new Number(). Este fenómeno es también llamado aliasingy es la forma fundamental con la que Java trabaja con objetos. n2. Pero que si no se quiere que ocurra esto en este caso? Se puede privar de asignar la referencia y decir: n1.i. que es devoto del aliasing.i: 27 Si se cambia el objeto n1 se puede ver que se cambia el objeto n2 de la misma forma! Esto es porque ambos.i: " + n1.i + ".i = 27.

Esto se logra con un operador seguido de un signo de igual. La salida muestra esto: 1: x. y es consistente con todos los operadores en el lenguaje (donde quiera que tenga sentido). Pero una vez mas una referencia es pasada de esta forma en la línea: y. Este ejemplo muestra el uso de los operadores matemáticos: //: c03:MathOps. f(x).c = ‘z’. para agregar 4 a la variable x y asignando el resultado x. x. } // shorthand to print a string and an int: static void pInt(String s. y a pesar que se debe esperar hasta el apéndice A para todas las respuestas. public class MathOps { // Create a shorthand to save typing: static void prt(String s) { System.char c. en lugar de redondear.out.println("2: x. import java. multiplicación (*) y módulo (%. } } ///:~ En muchos lenguajes de programación. Operadores matemáticos Los operadores matemáticos básicos son los mismos que los disponibles en muchos lenguajes de programación. suma (+).c). int i) { 121 . que produce el resto de una división entera).util. System. el método f() puede parecer que hace una copia de su argumento Letter y en el alcance de un método.c: " + x. se deberá estar alerta de esto en este punto así es que vamos a poder tener algunas dificultades.out. } public static void main(String[] args) { Letter x = new Letter(). Por ejemplo.c).c = 'a'. resta (-). System.java // Demonstrates the mathematical operators. Java también utiliza una notación taquigráfica para ejecutar una operación y una asignación en el mismo momento. el resultado.c: z Aliasing y esta solución es un tema complejo. división (/). actualmente es cambiado el objeto fuera de f().out. La división entera trunca. } public class PassObject { static void f(Letter y) { y.println("1: x.c: a 2: x.c = 'z'.*.println(s).c: " + x. utilice: x += 4.

int i. long. pInt("k / j". u). w = rand.w". } } ///:~ La primera cosa que se verá son algunos métodos taquigráficos para imprimir: el método prt() imprime un String.out. pFlt("v . j. too v = rand. float f) { prt(s + " = " + f). // seeds with current time by default: Random rand = new Random(). pFlt("v". i = j . i). u = v + w. u). } // shorthand to print a string and a float: static void pFlt(String s. } public static void main(String[] args) { // Create a random number generator. u /= v. // and double: u += v. i = k * j. short.prt(s + " = " + i). el programa primero crea un objeto Random . int. nextLong(). u). pFlt("u -= v". pFlt("u *= v". pInt("j . u = v . u = v * w. k. // the following also works for // char.nextInt() % 100. nextFloat() o nextDouble(). se termina siempre utilizando System. pFlt("w". j %= k. // applies to doubles. pFlt("u /= v". i = k / j.w. u). byte. u = v / w. v). i). u). Por supuesto. j).k). i). u).nextFloat(). u -= v. pFlt("v * w". u). pInt("k % j". k = rand.j). pInt("j". el pInt() imprime un String seguido por un int y el pFlt() imprime un String seguido por un float. El programa genera una determinada cantidad de diferentes tipos de números aleatorios con el objeto Random simplemente llamando a diferentes métodos: nextInt(). // Floating-point number tests: float u. pFlt("v + w".nextFloat().w. 122 Pensando en Java www. w). i = k % j. pInt("k * j".k". i). Como ningún argumento es pasado durante la creación. i). pInt("j %= k".k. i = j + k.com . // '%' limits maximum value to 99: j = rand. u *= v. pInt("k". pFlt("v / w".println().nextInt() % 100. Java utiliza la hora actual como semilla para el generador de números aleatorios.v. pFlt("u += v". Para generar números. u). pInt("j + k".BruceEckel.

Si a es un int. Para los incrementos y decrementos subsiguientes (i.java // Muestra los operadores ++ y --. ++a o --a). pero el lector puede confundirse. El unitario mas proporciona una simetría con el menos unitario. por ejemplo. y a también fácil o difícil de leer. Los atajos pueden hacer el código mas fácil de escribir. Incremento previo significa que el operador ++ aparece antes que la variable o expresión. El operador de incremento es ++ y significa “incremente en una unidad”.e. El compilador es capaz de figurárselo: x = a * -b. y incremento subsiguiente significa que el operado ++ aparece luego de la variable o expresión. prt("++i : " + ++i). a menudo llamadas las versiones prefijo y postfijo. Para los incrementos y decrementos previos (i. luego la operación se realiza. Por ejemplo. así es que no tiene efecto.El operado módulo. Como ejemplo: //: c03:AutoInc. Hay dos versiones para cada tipo de operador. la operación es realizada y el valor es producido. Dos de los mas lindos atajos son los operadores de incremento y decremento (muchas veces refiriéndose como los incremento y decrementos propios). así que es mas claro decir: x = a * (-b). El compilador se imagina el uso que se pretende dar por la forma de escribir la expresión. la expresión ++a es equivalente a (a = a + 1).e. El operador de decremento es -. como C esta lleno de atajos. El menos unitario produce el negativo del valor. la instrucción: x = -a.. Los operadores de incremento y decremento producen el valor de la variable como resultado. prt("i : " + i). // incremento previo prt("i++ : " + i++). que utilizamos con el resultado del generador de números aleatorios. limita el resultado a una cota superior del operador menos uno (99 en este caso). el valor es producido.y significa “decremente en una unidad”. public class AutoInc { public static void main(String[] args) { int i = 1. tiene un significado obvio. // incremento subsiguiente 123 . a++ y a--). Operadores unitarios menos y mas Los operadores unitario menos (-) y mas (+) son los mismos operadores menos y mas binarios. Incremento y decremento propio Java.

pero su significado a veces es confuso para el programados que recién comienza con Java.java public class Equivalence { 124 Pensando en Java www. // decremento previo + i--). pero en la forma postfija se obtiene el valor antes que la operación es realizada. pero las otras comparaciones no funcionan con el tipo boolean. Bill Jow (uno de sus creadores). En un discurso temprano acerca de Jaca. mayor que (<). El operador de incremento es una explicación para el nombre C++. cambian el operador antes que se use su valor. Operadores relacionales Los operadores relacionales generan un resultado del tipo boolean. Ellos evalúan la relación entre los valores de los operadores. } } ///:~ La salida para el programa es: i : ++i i++ i : --i i-i : 1 : : 3 : : 1 2 2 2 2 Se puede ver que para la forma prefija se obtiene el valor luego que la operación ha sido ejecutada. Estos son los únicos operadores (otros involucran asignaciones) que tiene efectos distintos de acuerdo al lado donde se ponga (esto es. dijo que “Java=C++--” (C mas mas menos menos). Equivalencia y no equivalencia trabajan con todos los tipos de datos . Los operadores de relación son menor que (<).out. menor igual que (<=). He aquí un ejemplo: //: c03:Equivalence. equivalente (==). } static void prt(String s) { System.println(s). y aun java no es mucho mas simple que C++.BruceEckel. sugiriendo que Java es C++ con las partes innecesariamente duras removidas y sin embargo un lenguaje mucho mas simple.com . Examinando la equivalencia entre objetos Los operadores relacionales == y != también trabajan con todos los objetos. Una expresión relacional produce true si la relación es verdadera y false si la relación es falsa. // decremento subsiguiente i).). implica “un paso adelante de C”. + --i). mayor igual que (>=).prt("i : prt("--i prt("i-prt("i : " : : " + " " + i). y no equivalente (!=).

println(n1 == n2) imprimirá el resultado de una comparación boolean sin ella.public static void main(String[] args) { Integer n1 = new Integer(47). } public class EqualsMethod2 { public static void main(String[] args) { Value v1 = new Value().i = 100. Así es que la salida es false y luego true. como esta: //: c03:EqualsMethod2. La mayoría de las librerías de clase de Java implementan equals() así es que se comparan el contenido de los objetos en lugar de sus referencias. Que si se desea saber si el contenido actual de dos objetos son iguales? Para esto se utiliza el método especial equals() que existe para todos los objetos (no primitivos.println(n1.i = v2.out.out.println(v1. v1.out. } } ///:~ volvemos a lo convencional: el resultado es false. Integer n2 = new Integer(47).out. Ha.out. } } ///:~ La expresión System. como se esperaba. Integer n2 = new Integer(47). Seguramente la salida tendría que ser true y luego false. no aprenderemos a sustituir hasta el capítulo 7. } } ///:~ El resultado será true. dado que ambos objetos Integer son los mismos. Se utiliza de la siguiente forma: public class EqualsMethod { public static void main(String[] args) { Integer n1 = new Integer(47). que trabajen bien con == y !=). 125 . pero hay que tener cuidado con la forma que equals() se comporta puede ayudar a evitar alguna pena mientras tanto. Value v2 = new Value().println(n1 != n2).equals(v2)). Pero a pesar de que el contenido de los objetos son el mismo. no es tan simple como esto. Así es que si no se sustituye equals() la clase no tendrá el comportamiento deseado. System. esto sorprende a las personas al comienzo.println(n1 == n2). System.java class Value { int i. Esto es porque el comportamiento por defecto de equals() es comparar referencias. System. Naturalmente. System. pero.equals(n2)). las referencias no son las mismas y los operadores == y != comparan referencias a objetos. Si se crean clases completamente nuevas. Desafortunadamente.

prt("i <= j is " + (i <= j)). Se puede ver que falla cuando se intenta hacer.*. prt("j = " + j). prt("i < j is " + (i < j)).Operadores lógicos Los operadores lógicos AND (&&). //: c03:Bool. int i = rand. Este ejemplo utiliza operadores relacionales y lógicos. //! prt("i || j is " + (i || j)).BruceEckel. prt("(i < 10) || (j < 10) is " + ((i < 10) || (j < 10)) ). OR o NOT únicamente a valores boolean. prt("i != j is " + (i != j)).nextInt() % 100. import java. OR (||) y NOT (!) produces un valor boolean true o false basado en la relación lógica de sus argumentos. prt("i > j is " + (i > j)). } } ///:~ Se puede aplicar AND.nextInt() % 100. sin embargo producen valores boolean utilizando comparaciones relacionales. prt("i == j is " + (i == j)). // Tratar un int como boolean no // es legal en Java //! prt("i && j is " + (i && j)).java // Relational and logical operators. Las expresiones que siguen.util. La salida listado se vería como esto: i j i i i i i i = 85 = 4 > j is true < j is false >= j is true <= j is false == j is false != j is true 126 Pensando en Java www. prt("(i < 10) && (j < 10) is " + ((i < 10) && (j < 10)) ).println(s). prt("i >= j is " + (i >= j)).out. int j = rand. public class Bool { public static void main(String[] args) { Random rand = new Random(). prt("i = " + i).com . luego utilizan operadores lógicos en los resultados. //! prt("!i is " + !i). } static void prt(String s) { System. No se puede utilizar un valor que no es boolean como si lo fuera en una expresión lógica de la misma forma que se hace en C y C++.

out. // with logical operators.out.java // Demonstrates short-circuiting behavior.out. } static boolean test3(int val) { System.out. Las pruebas son llamadas utilizando la expresión: if(test1(0) && test2(2) && test3(2)) Naturalmente. System. } static boolean test2(int val) { System. return val < 1. podemos pensar que estas tres funciones son ejecutadas. También imprime información para indicar que ha sido llamada. } public static void main(String[] args) { if(test1(0) && test2(2) && test3(2)) System.println("result: " + (val < 2)). Un numero que es un poquito mas grande que cero no será cero. Hay que ser cuidadoso. Como resultado. System.out. System. Esto significa que la expresión será evaluada solo hasta la verdad o falsedad de la expresión entera pueda ser claramente determinada. return val < 2.println("result: " + (val < 3)). sin embargo.out. Cortocircuitado Cuando se trata con operadores lógicos nos topamos con un fenómeno llamado “cortocircuitado”. Podemos remplazar la definición de int en el programa antes citado con otro tipo de datos primitivo que no sea boolean. que la comparación de punto flotante es muy estricta.out.println("expression is false").println("expression is true"). todas las partes de la expresión pueden no ser evaluadas. } } ///:~ Cada prueba realiza una comparación del argumento y retorna verdadero o falso.println("test3(" + val + ")"). public class ShortCircuit { static boolean test1(int val) { System.println("result: " + (val < 1)).out.(i < 10) && (j < 10) is false (i < 10) || (j < 10) is true Se puede notar que un valor boolean es automáticamente convertido a una forma de texto apropiada si es utilizado donde se espera un String. Aquí hay un ejemplo que demuestra cortocircuitado: //: c03:ShortCircuit. return val < 3. pero la salida muestra otra cosa: 127 .println("test1(" + val + ")"). else System.println("test2(" + val + ")"). Un número que es una diminuta fracción diferente de otro número será “no igual”.

El operador NOT (~.BruceEckel. pero no podemos ejecutar una NOT a nivel de bits (presumiblemente para prevenir confusión con el NOT lógico). también llamado operador complemento) es un operador unitario.test1(0) result: true test2(2) result: false expression is false La primer prueba produce un resultado true. |= y ^= son todas legítimas (Dado que ~ es un operador unitario no puede ser combinado con el signo de =). un cero si es uno. 128 Pensando en Java www. Java originalmente fue diseñado para incrustar en televisores. podemos a menudo manipular hardware directamente y tener que determinar bits en registros de hardware. Los operadores a nivel de bits y los operadores lógicos utilizan los mismos caracteres. de echo es precisamente esa. La razón para cortocircuitar. Los operadores pueden ser combinados con el signo = para combinar la operación y la asignación: &=. El tipo boolean es tratado como un valor de un solo bit así que es un algo diferente. Sin embargo. la segunda prueba produce un resultado false. Dado que esto significa que la totalidad de la expresión será false. ¿Sin embargo. El operador a nivel de bits AND (&) produce un bit uno en la salida si ambas entradas son uno. así es que la evaluación de la expresión continúa. Operadores a nivel de bits.com . Los operadores a nivel de bits realizan álgebra de Bool con los bits correspondientes de los argumentos para producir un resultado. El operador NOT produce el opuesto de la entrada a nivel de bits-uno si el bit de entrada es cero. OR y XOR. podemos obtener un incremento potencial de rendimiento si todas las partes de una expresión no necesitan ser evaluadas. así que es muy útil tener dispositivos nemotécnicos para ayudarnos a recordar los significados: dado que los bits son “pequeños”. toma solo un argumento (Todos los demás operadores son binarios). Los operadores a nivel de bits son los orientados a bajo nivel de C. porque continuar el resto de la expresión? Esto puede ser caro. de otra forma produce un cero. Para valores boolean se incluye un operador XOR lógico que no está incluido en la lista de operadores “lógicos”. probablemente no se utilice mucho operadores a nivel de bits. así es que la orientación de bajo nivel tiene sentido. hay solo un carácter en las operaciones a nivel de bits. El operador OR (|) produce un uno si alguna de las entradas son uno y cero si ambas entradas son cero. Los operadores a nivel de bits permiten manipular bits de forma individual en un tipo de dato primitivo. Se puede ejecutar un operación a nivel de bits AND.

Se esta impedido de utilizar valores boolean en expresiones de desplazamiento, como será descrito mas adelante.

Operadores de desplazamiento
Los operadores de desplazamiento pueden también manipular bits. Estos pueden ser utilizados exclusivamente con tipos primitivos enteros. El operador de desplazamiento a la izquierda (<<) produce que el operando se desplace a la derecha el numero de bits especificados luego del operador (insertando ceros en los bits de menor orden). El operador de desplazamiento a la derecha con signo (>>) produce que el operando a la izquierda se desplace a la derecha el numero de bits especificado luego del operador. El operador >> utiliza extensión de signo: si el valor es positivo, se insertan ceros en los bits de mayor orden; si el valor es negativo, son insertados unos en los bits de mayor orden. Java también agrega el desplazamiento a la derecha sin signo >>>, que usa extensión cero: independientemente del signo, ceros son insertados en los bits de mayor orden. Este operador no existe en C o C++. Si se desplaza un char, by te o short, será convertido a int antes de que el desplazamiento tenga lugar, y el resultado será un int. Solo los cinco bits de orden mas bajo del lado derecho serán utilizados. Esto previene que se desplacen mas que el número de bits en un entero. Si se esta operando con un long, tendremos un resultado long. Solo los seis bits de menor orden del lado derecho serán utilizados así es que no se puede desplazar mas que el número de bits en un long. Los desplazamientos pueden ser combinados con el signo de igual (<<= o >>= o >>>=). El lvalue es remplazado por el lvalue desplazado con el rvalue. Aquí hay un problema, sin embargo, con el desplazamiento sin signo combinado con la asignación. Si se usa con byte o short no se obtendrán los resultados correctos. En lugar de eso, estos son convertidos a int y desplazados a la derecha, y serán truncados cuando se asignen nuevamente a sus variables, así se obtendrá un -1 en esos casos. El ejemplo que sigue demuestra esto:
//: c03:URShift.java // Prueba de utilización de desplazamiento sin signo a la derecha. public class URShift { public static void main(String[] args) { int i = -1; i >>>= 10; System.out.println(i); long l = -1; l >>>= 10; System.out.println(l); short s = -1; s >>>= 10;

129

System.out.println(s); byte b = -1; b >>>= 10; System.out.println(b); b = -1; System.out.println(b>>>10); } } ///:~

En la última línea, el valor resultante no es asignado de vuelta a b, pero es impreso directamente y es comportamiento esperado es correcto. Aquí hay un ejemplo que demuestra el uso de todos los operadores que involucran bits:
//: c03:BitManipulation.java // Usando operadores a nivel de bits. import java.util.*; public class BitManipulation { public static void main(String[] args) { Random rand = new Random(); int i = rand.nextInt(); int j = rand.nextInt(); pBinInt("-1", -1); pBinInt("+1", +1); int maxpos = 2147483647; pBinInt("maxpos", maxpos); int maxneg = -2147483648; pBinInt("maxneg", maxneg); pBinInt("i", i); pBinInt("~i", ~i); pBinInt("-i", -i); pBinInt("j", j); pBinInt("i & j", i & j); pBinInt("i | j", i | j); pBinInt("i ^ j", i ^ j); pBinInt("i << 5", i << 5); pBinInt("i >> 5", i >> 5); pBinInt("(~i) >> 5", (~i) >> 5); pBinInt("i >>> 5", i >>> 5); pBinInt("(~i) >>> 5", (~i) >>> 5); long l = rand.nextLong(); long m = rand.nextLong(); pBinLong("-1L", -1L); pBinLong("+1L", +1L); long ll = 9223372036854775807L; pBinLong("maxpos", ll); long lln = -9223372036854775808L; pBinLong("maxneg", lln); pBinLong("l", l); pBinLong("~l", ~l); pBinLong("-l", -l); pBinLong("m", m); pBinLong("l & m", l & m); pBinLong("l | m", l | m); pBinLong("l ^ m", l ^ m);

130

Pensando en Java

www.BruceEckel.com

pBinL ong("l << 5", l << 5); pBinLong("l >> 5", l >> 5); pBinLong("(~l) >> 5", (~l) >> 5); pBinLong("l >>> 5", l >>> 5); pBinLong("(~l) >>> 5", (~l) >>> 5); } static void pBinInt(String s, int i) { System.out.println( s + ", int: " + i + ", binary: "); System.out.print(" "); for(int j = 31; j >=0; j--) if(((1 << j) & i) != 0) System.out.print("1"); else System.out.print("0"); System.out.println(); } static void pBinLong(String s, long l) { System.out.println( s + ", long: " + l + ", binary: "); System.out.print(" "); for(int i = 63; i >=0; i--) if(((1L << i) & l) != 0) System.out.print("1"); else System.out.print("0"); System.out.println(); } } ///:~

Los dos métodos al final, pBinInt() y pBinLong() toman un int o un long respectivamente, y los imprimen en formato binario con una descripción. Se puede ignorar esta implementación por ahora. Se puede notar el uso de System.out.print() en lugar de System.out.println(). El método print() no produce un salto de línea, lo que permite desplegar una sola línea de una sola vez. Igualmente como se demuestra el efecto de todos los operadores a nivel de bit para int y long, este ejemplo muestra el los valores mínimo, máximo, +1 y -1 para int y long así que se puede observar como se ven. Puede notarse que el bit mas alto representa el signo: 0 significa positivo y 1 negativo. La salida para la parte del int se vería como esto:
-1, int: -1, binary: 11111111111111111111111111111111 +1, int: 1, binary: 00000000000000000000000000000001 maxpos, int: 2147483647, binary: 01111111111111111111111111111111 maxneg, int: -2147483648, binary: 10000000000000000000000000000000 i, int: 59081716, binary: 00000011100001011000001111110100 ~i, int: -59081717, binary:

131

11111100011110100111110000001011 -i, int: -59081716, binary: 11111100011110100111110000001100 j, int: 198850956, binary: 00001011110110100011100110001100 i & j, int: 58720644, binary: 00000011100000000000000110000100 i | j, int: 199212028, binary: 00001011110111111011101111111100 i ^ j, int: 140491384, binary: 00001000010111111011101001111000 i << 5, int: 1890614912, binary: 01110000101100000111111010000000 i >> 5, int: 1846303, binary: 00000000000111000010110000011111 (~i) >> 5, int: -1846304, binary: 11111111111000111101001111100000 i >>> 5, int: 1846303, binary: 00000000000111000010110000011111 (~i) >>> 5, int: 132371424, binary: 00000111111000111101001111100000

La representación binaria de los números es referida como complemento a dos con signo.

Operador ternario if-else
Este operador es inusual porque tiene tres operandos. Es un verdadero operador porque produce un valor, a diferencia del comando if-else común que se podrá ver en la siguiente sección de este capítulo. La expresión es de esta forma:
boolean-exp ? value0 : value1

Si bolean-exp se evalúa como true, value0 es evaluado y su resultado es el producido por el operador. Si boolean-exp es false, value1 es evaluado y su resultado es el producido por el operador. Claro que, se puede utilizar un comando if-else (descrito mas adelante), pero el operador ternario es muy conciso. De la misma forma que C (de donde este operador es originario) se enorgullece de ser un lenguaje conciso, y el operador ternario puede ser introducido en parte por eficiencia, se debe ser un poco cuidadoso al utilizarse en las básicas de todos los días-es fácil producir código ilegible. El operador condicional puede ser utilizado por sus efectos de lado o por el valor que produce, pero en general el valor que genera el operador lo distingue del if-else. He aquí un ejemplo:
static int ternary(int i) { return i < 10 ? i * 100 : i * 10; }

132

Pensando en Java

www.BruceEckel.com

Como se puede ver, este código es mas compacto que el que se necesita para escribir sin el operador ternario:
static int alternative(int i) { if (i < 10) return i * 100; else return i * 10; }

La segunda forma es mas fácil de entender, y no necesita mucho mas escritura. Así es que debemos asegurarnos de deliberar las razones cuando elijamos el operador ternario.

El operador coma
La coma es utilizado en C y C++ no solo como separador en listas de argumentos de funciones, también como operador para evaluaciones secuenciales. El único lugar donde el operador coma es utilizado en Java es en los bucles for, que serán descritos mas adelante en este capítulo.

Operador de String +
Aquí tenemos un uso especial de un operador en Java: el operador + puede ser utilizado para concatenar cadenas, como ya hemos visto. Parece un uso natural del + aun si no corresponde con la forma tradicional que el signo es utilizado. Esta habilidad que parecía una buena idea en C++, de tal manera que la sobrecarga de opera dores fue agregada a C++ para permitir al programador agregar significado a casi cualquier operador. Desafortunadamente, la sobrecarga de operadores combinada con algunas otras restricciones en C++ se convirtió en un rasgo bastante complicado para los programadores para diseñar dentro de sus clases. A pesar que la sobrecarga de operadores sería mucho mas fácil de implementar en Java que en C++, este rasgo sigue considerándose muy compleja, así es que los programadores Java no pueden implementar sus propios operadores sobrecargados como los programadores C++. El uso del + en String tiene algunos comportamientos interesantes. Si una expresión comienza con un tipo String, todos los operandos que siguen deben ser del tipo String (recordemos que el compilador convertirá secuencias de caracteres entre comillas a tipo String).
int x = 0, y = 1, z = 2; String sString = "x, y, z "; System.out.println(sString + x + y + z);

Aquí, el compilador Java convertirá x, y y z en sus representaciones del tipo String en lugar de sumarlos entre ellos primero. Ahora si colocamos:
System.out.println(x + sString);

133

Java convertirá x + y + z en un String y luego lo concatenará a sString.

Dificultades comunes utilizando operadores
Una de las dificultades cuando utilizamos operadores es tratar de quitar los paréntesis incluso cuando se tiene la mas pequeña incertidumbre acerca de como una expresión se evaluará. Esto sigue siendo verdad en Java. Un error extremadamente común en C y C++ se vería como esto:
while(x = y) { // .... }

El programador trató de probar la equivalencia (==) en lugar de realizar una asignación. En C y C++ el resultado de esta asignación siempre será true si y no es cero, y probablemente se tendrá un bucle infinito. En Java, el resultado de esta expresión no es un tipo boolean, y el compilador espera un boolean y no lo convertirá a int, así es que convenientemente entregara un error en tiempo de compilación y se encontrará el problema antes que se intente correr el programa. Así es que es escollo jamás sucederá en Java (El único momento en que no recibirá un error en tiempo de compilación es cuando x e y son del tipo boolean, caso en el cual x = y es una expresión legal, y en el caso anterior, probablemente un error). Un problema similar en C y C++ es utilizando los AND y OR a nivel de bits en lugar de las versiones lógicas. AND y OR a nivel de bits uso un solo carácter (& o |) y AND y OR lógicos usan dos (&& y ||). Igual que con = y ==, es fácil escribir un solo carácter en lugar de dos. En Java, el compilador previene contra esto porque no deja utilizar caballerescamente un tipo donde no pertenece.

Operadores de conversión
Java puede cambiar automáticamente un tipo de datos en otro cuando sea apropiado. Para algunas ocasiones, si se asigna un valor entero a una variable de punto flotante, el compilador automáticamente convertirá en int en float. La conversión permite hacer este tipo de cosas de forma explícita, o forzarla cuando no sucedería normalmente. Para realizar una conversión, se coloca el tipo de dato deseado (incluidos todos los modificadores) adentro de paréntesis a la izquierda del un valor. He aquí un ejemplo:
void casts() { int i = 200; long l = (long)i;

134

Pensando en Java

www.BruceEckel.com

long l2 = (long)200; }

Como se puede ver, es posible realizar una conversión de un valor numérico igual que para una variable. En ambos casos mostrados aquí, sin embargo, la conversión es superflua, dado que el compilador automáticamente promover un valor int a un valor del tipo long cuando sea necesario. sin embargo, está permitido utilizar conversiones superfluas para marcar un punto o hacer el código mas claro. En otras situaciones, una conversión puede ser esencial sencillamente para hacer que el código compile. En C y C++, la conversión de tipo puede causar algunos dolores de cabeza. En Java, la conversión es segura, con la excepción que cuando se ejecuta una conversión muchas veces llamada estrecha (narrowing esto es, cuando , partimos de un tipo que puede almacenar mas información hacia un tipo que no almacena la totalidad) se corre el riesgo de perder información. Aquí el compilador fuerza a hacer una conversión, en efecto diciendo “esto puede ser una cosa peligrosa para hacer-si quiere que lo haga igual debe hacer la conversión explícitamente”. Con una conversión que se ensancha (widening) no es necesario porque el nuevo tipo puede almacenar mas que el tipo viejo así es que no se pierde información. Java permite convertir cualquier tipo a cualquier otro tipo primitivo, excepto para boolean, no permite ninguna conversión. Los tipos de clases no permiten conversiones. Para convertir uno en otro debe haber un método especial (String es un caso especial, y se verá mas adelante en este libro que objetos pueden convertirse con una familia de tipos, un Oak puede ser convertido en un Tree y a la inversa, pero no a un tipo extraño como Rock).

Literales
Normalmente cuando insertamos un valor literal en un programa el compilador sabe exactamente que tipo utilizar con el. A veces, sin embargo, el tipo es ambiguo. Cuando esto sucede debemos guiar el compilador agregando información extra en la forma con que los caracteres asociados con el valor literal. El siguiente código muestra esos caracteres:
//: c03:Literals.java class Literals { char c = 0xffff; // max char hex value byte b = 0x7f; // max byte hex value short s = 0x7fff; // max short hex value int i1 = 0x2f; // Hexadecimal (lowercase) int i2 = 0X2F; // Hexadecimal (uppercase) int i3 = 0177; // Octal (leading zero) // Hex and Oct also work with long. long n1 = 200L; // long suffix long n2 = 200l; // long suffix long n3 = 200; //! long l6(200); // not allowed

135

float f1 = 1; float f2 = 1F; // float suffix float f3 = 1f; // float suffix float f4 = 1e-45f; // 10 to the power float f5 = 1e+9f; // float suffix double d1 = 1d; // double suffix double d2 = 1D; // double suffix double d3 = 47e47d; // 10 to the power } ///:~

Los hexa decimales (base 16), que funcionan con todos los tipos de datos, se indican con un 0x o 0X adelante seguidos o por un dígito (0-9) o por una letra (a-f) en mayúscula o minúscula. Si intentamos inicializar una variable con un valor mayor que el que se puede almacenar (independiente de la forma del valor numérico), el compilador dará un mensaje de error. Se puede notar en al código anterior los valores máximos para char, byte y short. Si se exceden estos valores, el compilador automáticamente creará el valor entero e indicará que se necesita convertir el tipo. Se sabrá que esta superando la línea. Los octales (base 8) de indican con un cero al comienzo del numero y se utilizan dígitos de 0 a 7. No hay representación literal para números binarios en C, C++ o Java. Un carácter que se arrastra luego de el valor literal establece su tipo. En mayúscula o en minúscula la L significa long, en mayúscula o en minúsculas F significa float y la D significa double. Los exponentes utilizan una notación que se siempre se encuentran antes del desmayo: 1.39 e-47f. En ciencia e ingeniería, ‘e’ se refiere a la base natural logarítmica, aproximadamente 2.718. (Un valor double mas preciso está disponible en Java como Math.E). Este es utilizado en expresiones exponenciales como 1.39 x e-47, que significa 1.39 x 2.718-47 . Sin embargo, cuando FORTRAN fue inventado se decidió que e naturalmente significa “diez a la potencia”, que es una decisión curiosa porque FORTRAN fue diseñado para la ciencia e ingeniería y uno puede pensar que sus diseñadores podrían ser sensibles acerca de introducir tal como una ambigüedad1 . En cualquier evaluación, este hábito fue seguido en C, C++ y
1

John Kirkham escribió, “He estado computando en 1962 utilizando FORTRAN II en una IBM 1620. En este momento, y durante todos los años 60 y en los 70, FORTRAN fue un lenguaje en mayúsculas. Esto probablemente comenzó porque muchos de los dispositivos al inicio fueron viejas unidades de teletipo que utilizaban código Baudot de 5 bit, y no tenían capacidad de representar minúsculas. La letra ‘E’ en la notación exponencial fue también siempre mayúscula y nunca se confundió con el logaritmo en base natural ‘e’, que siempre fue minúscula. La ‘E’ simplemente se mantuvo de pie para exponencial, que fue por la base del sistema de numeración utilizado-usualmente 10. En el momento que octal fue también ampliamente utilizado por los programadores. A pesar que yo nunca distinguí cual usar, si tengo que ver un numero octal en notación exponencial nunca hubiera considerado que fuera de base 8. El primer momento que recuerdo ver un uso exponencial de la letra

136

Pensando en Java

www.BruceEckel.com

ahora en Java. Así es que si se esta acostumbrado a pensar en términos de e como la base de los logaritmos naturales, se deberá realizar una traslación mental cuando se use una expresión como 1.32e-47f en Java; esto significa 1.39x10-47 . Se debe notar que no se necesita utilizar el último carácter cuando el compilador puede figurarse el tipo apropiado. Con
long n3 = 200;

no hay ambigüedad, así es que una L luego del 200 puede ser superfluo. Sin embargo con
float f4 = 1e-47f; // 10 a la potencia

el compilador normalmente toma los números exponenciales como dobles, así es que sin el último carácter f dará un error indicando que se debe utilizar una conversión de tipo explícitamente double a float.

Promoción
Se descubrirá que si se realiza una operación matemática o a nivel de bits en tipos de datos primitivos que son menores que un int (esto es, char, byte, o short), estos valores serán ascendidos a int antes de realizar las operaciones, y el valor resultante será del tipo int. Así es que si se desea asignar nuevamente a un tipo mas pequeño, se deberá utilizar conversiones de tipo (Y, dado que se está asignando a un tipo mas pequeño, se podrá perder información). En general, los tipos mas grandes es el que determina el tamaño del resultado de una expresión; si multiplicamos un float y un double, el resultado deberá ser double; si sumamos un int y un long, el resultado será long.

Java no tiene “sizeof”
En C y C++, el operador sizeof() satisface una necesidad específica: indica el número de bytes asignados para ítem de datos. La necesidad mas urgente para sizeof() en C y C++ es la portabilidad. Los diferentes tipos de datos pueden ser de diferentes tamaños en diferentes máquinas, así es que el programador debe averiguar cuan grandes son los tipos que hay cuando se realizan operaciones sensibles a el tamaño. Por ejemplo, una computadora puede almacenar enteros en 32 bits, mientras que otra puede almacenar enteros de 16 bits. Los programas pueden almacenar valores grandes de enteros en la primer máquina. Como se debe imaginar, la portabilidad es un enorme dolor de cabeza para los programadores C y C++.
minúscula ‘e’ fue a finales de los 70 y también me sentí confundido. El problema se origina lentamente en FORTRAN, no es sus comienzos. Tenemos actualmente funciones para usar que realmente queremos utilizar la base de logaritmos naturales, y existen todas en mayúsculas”.

137

Java no necesita un operador sizeof() para este propósito porque todos los tipos de datos son del mismo tamaño en todas las máquinas. No se necesita pensar acerca de la portabilidad en este nivel-esta diseñado dentro del lenguaje.

Revisión de precedencia
Enzima de escucharme quejar sobre la complejidad de recordar la precedencia de operadores durante uno de mis seminarios, un estudiante sugirió un nemotécnico que es simultáneamente una observación: “Ulcer Addicts Really Like C A lot.” (Los adictos a las úlceras realimente les gusta C un montón). Nemotécnico Ulcer Addicts Really Like C A lot Tipo de operador Unitario Aritmético (y desplazamiento) Relacional Lógico (y a nivel de bits) Condicional (ternarios) Asignación Operadores + - ++ -* / % + - << >>

> < >= == != && || & | ^ A>B¿X:Y = (y asignación compuesta como *=) Claro, que con el desplazamiento y los operadores a nivel de bits distribuidos por la tabla no es un nemotécnico perfecto, pero para operaciones que no son a nivel de bit trabaja.

Un compendio de operadores
El siguiente ejemplo muestra que tipos de datos primitivos puede ser utilizado con un operador particular. Básicamente, es el mismo ejemplo repetido una y otra vez, pero utilizando diferentes tipos de datos. El fichero compilara sin error dado que las líneas que pueden causar error están comentados con un //!.
//: c03:AllOps.java // Prueba todos los operadores para todos los // datos primitivos para mostrar con los // aceptados por el compilador Java. class AllOps { // Para aceptar los resultados de una prueba booleana: void f(boolean b) {} void boolTest(boolean x, boolean y) { // Operadores aritméticos: //! x = x * y; //! x = x / y; //! x = x % y; //! x = x + y;

138

Pensando en Java

www.BruceEckel.com

//! x = x - y; //! x++; //! x--; //! x = +y; //! x = -y; // Relacionales y lógicos: //! f(x > y); //! f(x >= y); //! f(x < y); //! f(x <= y); f(x == y); f(x != y); f(!y); x = x && y; x = x || y; // Operadores a nivel de bits: //! x = ~y; x = x & y; x = x | y; x = x ^ y; //! x = x << 1; //! x = x >> 1; //! x = x >>> 1; // Asignación compuesto: //! x += y; //! x -= y; //! x *= y; //! x /= y; //! x %= y; //! x <<= 1; //! x >>= 1; //! x >>>= 1; x &= y; x ^= y; x |= y; // Casting: //! char c = (char)x; //! byte B = (byte)x; //! short s = (short)x; //! int i = (int)x; //! long l = (long)x; //! float f = (float)x; //! double d = (double)x; } void charTest(char x, char y) { // Operadores aritméticos: x = (char)(x * y); x = (char)(x / y); x = (char)(x % y); x = (char)(x + y); x = (char)(x - y); x++; x--; x = (char)+y; x = (char)-y;

139

// Relacionales y lógicos: f(x > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x || y); // Operadores a nivel de bits: x= (char)~y; x = (char)(x & y); x = (char)(x | y); x = (char)(x ^ y); x = (char)(x << 1); x = (char)(x >> 1); x = (char)(x >>> 1); // Asignamiento compuesto: x += y; x -= y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= y; x ^= y; x |= y; // Casting: //! boolean b = (boolean)x; byte B = (byte)x; short s = (short)x; int i = (int)x; long l = (long)x; float f = (float)x; double d = (double)x; } void byteTest(byte x, byte y) { // Operadores aritméticos: x = (byte)(x* y); x = (byte)(x / y); x = (byte)(x % y); x = (byte)(x + y); x = (byte)(x - y); x++; x--; x = (byte)+ y; x = (byte)- y; // Relacionales y lógicos: f(x > y); f(x >= y); f(x < y); f(x <= y);

140

Pensando en Java

www.BruceEckel.com

f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x || y); // Operadores a nivel de bits: x = (byte)~y; x = (byte)(x & y); x = (byte)(x | y); x = (byte)(x ^ y); x = (byte)(x << 1); x = (byte)(x >> 1); x = (byte)(x >>> 1); // Asignamientos compuestos: x += y; x -= y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= y; x ^= y; x |= y; // Conversiones: //! boolean b = (boolean)x; char c = (char)x; short s = (short)x; int i = (int)x; long l = (long)x; float f = (float)x; double d = (double)x; } void shortTest(short x, short y) { // Operadores aritméticos: x = (short)(x * y); x = (short)(x / y); x = (short)(x % y); x = (short)(x + y); x = (short)(x - y); x++; x--; x = (short)+y; x = (short)-y; // Relacionales y lógicos: f(x > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x || y);

141

// Operadores a nivel de bits: x = (short)~y; x = (short)(x & y); x = (short)(x | y); x = (short)(x ^ y); x = (short)(x << 1); x = (short)(x >> 1); x = (short)(x >>> 1); // Asignaciones compuestas: x += y; x -= y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= y; x ^= y; x |= y; // Conversiones: //! boolean b = (boolean)x; char c = (char)x; byte B = (byte)x; int i = (int)x; long l = (long)x; float f = (float)x; double d = (double)x; } void intTest(int x, int y) { // Operadores aritméticos: x = x * y; x = x / y; x = x % y; x = x + y; x = x - y; x++; x--; x = +y; x = -y; // Relacionales y lógicos: f(x > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x || y); // Operadores a nivel de bits: x = ~y; x = x & y; x = x | y; x = x ^ y;

142

Pensando en Java

www.BruceEckel.com

x = x << 1; x = x >> 1; x = x >>> 1; // Asignamientos compuestos: x += y; x -= y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= y; x ^= y; x |= y; // Conversiones: //! boolean b = (boolean)x; char c = (char)x; byte B = (byte)x; short s = (short)x; long l = (long)x; float f = (float)x; double d = (double)x; } void longTest(long x, long y) { // Operadores aritméticos: x = x * y; x = x / y; x = x % y; x = x + y; x = x - y; x++; x--; x = +y; x = -y; // Relacionales y lógicos: f(x > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x || y); // Operadores a nivel de bits: x = ~y; x = x & y; x = x | y; x = x ^ y; x = x << 1; x = x >> 1; x = x >>> 1; // Asignamientos compuestos: x += y;

143

x -= y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= y; x ^= y; x |= y; // Conversiones: //! boolean b = (boolean)x; char c = (char)x; byte B = (byte)x; short s = (short)x; int i = (int)x; float f = (float)x; double d = (double)x; } void floatTest(float x, float y) { // Operadores aritméticos: x = x * y; x = x / y; x = x % y; x = x + y; x = x - y; x++; x--; x = +y; x = -y; // Relacionales y lógicos: f(x > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x || y); // Operadores a nivel de bits: //! x = ~y; //! x = x & y; //! x = x | y; //! x = x ^ y; //! x = x << 1; //! x = x >> 1; //! x = x >>> 1; // Asignaciones compuestas: x += y; x -= y; x *= y; x /= y; x %= y; //! x <<= 1;

144

Pensando en Java

www.BruceEckel.com

//! x >>= 1; //! x >>>= 1; //! x &= y; //! x ^= y; //! x |= y; // Casting: //! boolean b = (boolean)x; char c = (char)x; byte B = (byte)x; short s = (short)x; int i = (int)x; long l = (long)x; double d = (double)x; } void doubleTest(double x, double y) { // Operadores aritméticos: x = x * y; x = x / y; x = x % y; x = x + y; x = x - y; x++; x--; x = +y; x = -y; // Relacionales y lógicos: f(x > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x && y); //! f(x || y); // Operadores a nivel de bits: //! x = ~y; //! x = x & y; //! x = x | y; //! x = x ^ y; //! x = x << 1; //! x = x >> 1; //! x = x >>> 1; // Asignamiento compuesto: x += y; x -= y; x *= y; x /= y; x %= y; //! x <<= 1; //! x >>= 1; //! x >>>= 1; //! x &= y; //! x ^= y; //! x |= y;

145

la ausencia de la conversión de tipos naturalmente simplifica el código. short s = (short)x. byte y short se puede ver el efecto de promoción con los operadores aritméticos. int i = (int)x. sin embargo.java // Sorpresa! Java indica desbordamiento. No se puede ser calmado al pensar que todo es seguro.BruceEckel. Cada operador aritmético de cualquiera de estos tipos tiene un resultado del tipo int. Por otro lado. // valor maximo para int prt("big = " + big). long l = (long)x. } } ///:~ Se puede notar que boolean es un poco limitado. se puede obtener un desbordar el resultado. byte B = (byte)x. cualquier tipo primitivo puede ser convertido a cualquier otro tipo primitivo. Nuevamente. Si se multiplican dos valores del tipo int que son suficientemente grandes. Java es bueno. } static void prt(String s) { System. sin embargo. con excepción del tipo boolean.com . y se puede verificar por verdad o falsedad. Se puede ver que. En char. Con los valores del tipo int.out.// Casting: //! boolean b = (boolean)x. que debe ser explícitamente convertido a el tipo original (una conversión a un tipo inferior puede hacer perder información) para asignar nuevamente al tipo anterior.println(s). El siguiente ejemplo demuestra esto: //: c03:Overflow. pero no tan bueno. pero no puede sumar tipos boolean o realizar otro tipo de operaciones con ellos. int bigger = big * 4. byte o short. y no hay excepciones en tiempo de ejecución. Se puede asignar los valores true y false. } } ///:~ La salida de esto es: big = 2147483647 bigger = -4 y no se tienen errores o advertencias del compilador. float f = (float)x. prt("mayor = " + bigger). Las asignaciones compuestas no necesitan conversiones de tipo para char. public class Overflow { public static void main(String[] args) { int big = 0x7fffffff. char c = (char)x. porque todo ya se encuentra con el tipo int. aún si realizan promociones que tienen los mismos resultados que las operaciones aritméticas directas. debe 146 Pensando en Java www. no se necesita convertir el tipo.

Control de ejecución Java utiliza todas las instrucciones de control de ejecución de C. así es que se puede utilizar if de dos formas: if(expresion booleana) instrucciones o if(expresión booleana) instrucciones else instrucciones 147 . Debe notarse que Java no permite el uso de un número como tipo boolean. el muy maldecido goto (que todavía puede ser la forma mas conveniente forma de solucionar cierto tipo de problemas). Un ejemplo de expresión condicional es A == B. Cualquiera de los operadores relacionales que se han visto antes en este capítulo pueden ser utilizados para producir expresiones condicionales. Muchos lenguajes de programación procesales tienen algún tipo de instrucciones de control. como es if(a ¡= 0) . La expresión retorna true o false. Estas utilizan el operador condicional == para ver si el valor de A es equivalente a el valor de B. El else es opcional. y a menudo de pasan entre lenguajes. sin embargo. las palabras clave incluyen ifelse. así es que se verá familiar si se ha programado con C o C++. true y false todas las instrucciones condicionales usan la verdad o falsedad de una expresión condicional para determinar el camino de ejecución. Aún se puede realizar un salto del tipo de goto. for. Si se quiere utilizar un tipo que no sea boolean en una prueba del tipo boolean. como if(a).tener cuidado de los efectos de hacer mas pequeño un tipo cuando se convierte a un tipo inferior. if-else La instrucción if-else es probablemente la forma mas básica de controlar el flujo de un programa. Java no soporta. debemos convertirlo a el valor boolean utilizando una expresión condicional. y una instrucción de control llamada switch. do-while. while. aun cuando esto esta permitido en C y en C++ (donde verdadero en un valor distinto de cero y falso es cero). pero es mucho mas limitado que el típico. de otra forma puede perder sin saberlo información al convertir el tipo. En Java.

System. else result = 0. if(testval > target) result = +1. else return 0. if(testval > target) return +1. 148 Pensando en Java www. // Match } public static void main(String[] args) { System.println(test(5. // Match return result. return La palabra clave return tiene dos propósitos: especifica que valor de retorno tendrá un método (si no tiene un tipo void como retorno) y produce un retorno inmediato de ese valor. //: c03:IfElse2.java public class IfElse2 { static int test(int testval. Las instrucciones es una instrucción terminada con un punto y coma o una instrucción compuesta.out.java public class IfElse { static int test(int testval. he aquí un método test() que nos indicará si un número para adivinar es superior.println(test(10.La condición debe producir un resultado del tipo boolean.com . inferior o igual a un número secreto: //: c03:IfElse. que es un grupo de instrucciones encerrado entre llaves. int target) { int result = 0.out.println(test(5. 10)).out.println(test(10. En cualquier momento que la palabra “instrucción” sea utilizada. } } ///:~ Estos son cosas normales que se le piden a el cuerpo de una instrucción de control de flujo así que se puede determinar fácilmente donde comienza y donde termina. 5)).out. else if(testval < target) result = -1. } public static void main(String[] args) { System. System. int target) { int result = 0. System. 10)). A forma de ejemplo de if-else. else if(testval < target) return -1.out. siempre implica que la instrucción pueda ser simple o compuesta.BruceEckel. 5)). El método test() anterior puede ser reescrito para tomar ventaja de esto.println(test(5. 5)).

while(r < 0. public class WhileTest { public static void main(String[] args) { double r = 0. que genera un valor double entre 0 y 1 (Esto incluye 0 pero no 1).random(). La expresión condicional para el while dice “manténgase haciendo este bucle hasta que el numero es 0.println(test(5. System.out. Cada vez que se ejecute este programa obtendremos una lista de números de diferente tamaño. Iteración Los bucles de control while.java // Demonstrates the while loop. do-while es menos común que while.System. La forma del bucle while es while(expresión booleana) instrucción La expresión booleana es evaluada una vez al inicio del bucle y nuevamente cada nueva iteración de la instrucción.out.println(r). En la práctica. 149 .99 o mayor”. do-while y for son clasificados como instrucciones de interacción.99d) { r = Math. 5)). do-while la forma del do-while es do instrucción while(expresion booleana) la única diferencia entre while y do-while es que la instrucción en do-while se ejecuta siempre por lo menos una vez. He aquí un simple programa ejemplo que genera números al azar hasta que una condición particular es encontrada: //: c03:WhileTest. Una instrucción se repite hasta que la expresión de control booleana evaluada es falsa. si la condición es falsa la primera vez la instrucción nunca se ejecuta. } } ///:~ Aquí no hay necesidad de la palabra clave else ya que el método no continua luego de ejecutar un return. En un while. } } } ///:~ Utiliza un método estático llamado random() en la librería Math. aún si la expresión se evalúa como falsa la primera vez.

En Java y en C++ se puede desparramar las declaraciones de variables por todo el bloque. c < 128. dentro de la expresión de control del bucle for. No se puede utilizar este método con ninguna otra instrucción de selección o iteración. j = 1. Los lenguajes tradicionales como C requiere que todas las variables sean definidas en el comienzo del bloque así es que cuando el compilador crea un bloque puede asignar el espacio para esas variables. expresión booleana. Los bucles for usualmente son utilizados para tareas de “cuenta” //: c03:ListCharacters. definiéndolas en el punto en que se necesiten. y tan pronto como se evalúa en false la ejecución continúa en la línea seguida por la instrucción for. j++) /* cuerpo del bucle */. al final de cada iteración. En el final de cada bucle.for Un bucle for realiza un inicialización antes de la primer iteración. Se pueden definir múltiples variables con la instrucción for. public class ListCharacters { public static void main(String[] args) { for( char c = 0. 150 Pensando en Java www. El alcance de c es la expresión controlada por el for. La forma del bucle for es: for (inicialización. c++) if (c != 26 ) // Borrado de pantalla ANSI System. Esto permite un estilo de código mas natural y hace el código mas fácil de entender. i++.out. pero estas deben ser del mismo tipo: for(int i = 0. i < 10 && j != 11. } } ///:~ Se puede ver que la variable c esta definida en el punto donde va a ser utilizada. Luego realiza pruebas condicionales y.println( "value: " + (int)c + " character: " + c). La habilidad para definir variables en la expresión de control está limitada por el bucle for. La expresión es probada luego de cara iteración.java // Demuestra los bucles "for" listando // todos los caracteres ASCII.BruceEckel.com . paso) instrucción Cualquiera de las expresiones inicialización. expresión booleana o n pueden ser vacías. la definición del tipo int en la instrucción for cubre i y j. alguna forma de “adelantamiento de a pasos”. antes que al comienzo de el bloque indicado por la llave de apertura. paso se ejecuta.

que es utilizado para separar definiciones y argumentos de funciones) tiene solo un uso en Java: en la expresión de control de un bucle for. public class BreakAndContinue { public static void main(String[] args) { for(int i = 0.El operador coma En los comienzos de este capítulo se dijo que el operador coma (no el separadorcoma.out. i++) { if(i == 74) break. El trozo de código anterior utiliza esa habilidad.java // Demonstrates break and continue keywords. Este programa muestra ejemplos de break y continue con bucles for y while: //: c03:BreakAndContinue.println(i). He aquí otro ejemplo: //: c03:CommaOperator. i < 5. } } } ///:~ Y su salida: i= i= i= i= 1 2 3 4 j= j= j= j= 11 4 6 8 Como se puede ver en ambos en la inicialización y en las parte de pasos las instrucciones son evaluadas en orden secuencial. // Siguiente iteración System. break y continue Adentro del cuerpo de cualquiera de las instrucciones de la iteración se puede tener control del flujo del bucle utilizando break y continue. y aquellas instrucciones serán evaluadas de forma secuencial. Además. break se sale del bucle sin ejecutar el resto de las instrucciones en el bucle. // Fuera del bucle if(i % 9 != 0) continue.java public class CommaOperator { public static void main(String[] args) { for(int i = 1. la parte de la inicialización puede tener cualquier número de definiciones de un tipo. i < 100. i++. } int i = 0. // Un “bucle infinito": while(true) { 151 . continue detiene la ejecución de la actual iteración y regresa al comienzo del bucle para comenzar en la siguiente iteración. j = i + 10. En la inicialización y en la parte de pasos de la expresión de control se pueden tener varias instrucciones separadas por comas.out.println("i= " + i + " j= " + j). j = i * 2) { System.

se puede ver que continue regresa a el inicio del bucle sin completar el resto (Estas impresión se sucede en el segundo bucle solo cuando el valor de i es divisible entre 10). Ciertamente. goto fue el génesis del control de programa en lenguaje ensamblador: “Si se da la condición A. goto es un salto a nivel de código fuente.) de la misma forma así es que cualquiera que se use es por preferencias de programación. dentro del bucle hay una instrucción break que saldrá del bucle.. en teoría.). La salida es: 0 9 18 27 36 45 54 63 72 10 20 30 40 El valor de 0 es impreso porque 0 % 9 produce 0. de otra forma salte aquí”.. La segunda parte muestra un”bucle infinito” que puede. El infame “goto” La palabra clave goto ha estado presente en los lenguajes de programación desde el comienzo. Una segunda forma de bucle infinito es for(. // fuera del bucle if(i % 10 != 0) continue. // Arriba en el bucle System. se puede ver que el control del programa tiene muchos saltos. Cuando esto se sucede el valor es impreso. } } } ///:~ En el bucle for el valor de i nunca llega a 100 porque la instrucción break sale del bucle cuando i es 74. se usará una línea break como esta solo si no se conoces cuando va a suceder la condición de terminación. Normalmente.com .out.i++. continuar para siempre. El compilador trata while(true) y for(. Si se lee el código ensamblador que se genera al final de virtualmente cualquier compilador. Sin embargo. Sin embargo. if(j == 1269) break. La instrucción continue produce que la ejecución regrese a el comienzo de la iteración del bucle (de esta manera se incrementa i) cuando quiera que i no sea divisible entre 9. entonces salte aquí.println(i). y esto es lo que ha traído descrédito. Además.BruceEckel. int j = i * 27. no hay una forma de reorganizar el código 152 Pensando en Java www. ¿Si un programa siempre salta de un punto a otro.

continue regresa al inicio la iteración interior. Esto es porque las instrucciones break y continue serán normalmente interrumpidas solo en el bucle actual. la etiqueta1 del continue quiebra la iteración interior y la iteración exterior. // 1 //… continue. Sin embargo. Una etiqueta es un identificador seguido de dos puntos. y desde que maltratar a goto ha sido un deporte popular. // 2 //… continue etiqueta1. la break etiqueta1 también quiebra mas allá de la etiqueta1. Actualmente no sale de ambas iteraciones. El problema no es el uso de goto. 153 . la tierra media es lo mas fructífero. sino la sobre utilización de goto-en situaciones poco comunes goto es actualmente la mejor manera de estructurar el control de flujo. pero comenzando con la iteración externa. pero no ingresa nuevamente la iteración. // 3 //… break etiqueta 1. Como es típico en situaciones como esa. Y esto significa justo antes-no es bueno colocar otra instrucción entre la etiqueta y la iteración. pero cuando son utilizadas con una etiqueta serán interrumpidos encima de donde la etiqueta existe: etiqueta 1: iteración exterior { iteración interior { //… break. no es utilizada en el lenguaje. hace algo que se ve un poco como salto atado con las palabras clave break y continue. todo allá en la etiqueta1. Entonces de echo continúa la iteración. Pero en el caso 3. En el caso 2. Y la única razón para colocar una etiqueta después de una iteración es si esta anidada a otra iteración o un switch dentro de ella. No es un salto porque mas bien es una forma de quebrar una instrucción de iteración. gracias a los abogados de la expulsión de palabras clave se ha echado a correr. // 4 } } En el caso uno. En el caso 4. La razón es a menudo olvidar las discusiones de goto porque utiliza el mismo mecanismo: una etiqueta. como esta: etiqueta1: Al único lugar donde es útil en Java es justo antes de una instrucción de iteración. Java no tiene goto.para que el control de flujo no salte? goto cae en una verdadera desaprobación luego de la publicación de el famoso trabajo “goto considerado dañino” por Edsger Dijkstra. break quiebra la iteración interior y se termina en la iteración exterior. A pesar que goto es una palabra reservada en Java.

if(i == 2) { prt("continua").Aquí hay un ejemplo utilizando bucles for: public class LabeledFor { public static void main(String[] args) { int i = 0. continue outer. // De otra forma nunca // seré incrementado. } if(i == 8) { prt("quiebre para afuera"). i < 10. La instrucción continue outer en el caso de i == 7 también salta al inicio del bucle y evita el incremento.out. i++. i++) { prt("i = " + i). He aquí la salida: 154 Pensando en Java www. // De otra forma nunca seré // incrementado. } } } } // No se puede quebrar o continuar // con etiquetas aquí } static void prt(String s) { System. y la expresión de incremento no ocurre hasta que se termine el bucle for. } for(int k = 0. i++. break. Dado que brea k evita la expresión de incremento. } if(i == 7) { prt("continua por afuera"). Debe notarse que break sale del bucle for.com . true . } if(i == 3) { prt("break"). el incremento es realizado directamente en el caso de i == 3. outer: // No puede haber instrucciones aquí for(. continue inner.println(s). así es que se incrementa directamente también. k < 5. } } ///:~ Esto utiliza el método prt() que ha sido definido en otros ejemplos.) { // bucle infinito inner: // No puede haber instrucciones aquí for(.BruceEckel. k++) { if(k == 3) { prt("continua en el interior"). break outer. continue.

} if(i == 3) { prt("continuar afuera"). continue.i = 0 continua en el interior i = 1 continua en el interior i = 2 continua i = 3 break i = 4 continua en el interior i = 5 continua en el interior i = 6 continua en el interior i = 7 continua por afuera i = 8 quiebre para afuera Si no fuera por la instrucción break outer. break. Claro que en los casos donde se sale de un bucle y también salir del método se puede utilizar simplemente return. prt("i = " + i).java // Java's "labeled while" loop. public class LabeledWhile { public static void main(String[] args) { int i = 0. podría no ser la forma de salir del bucle exterior desde adentro del bucle interior. continue outer. Aquí podemos ver una demostración de las instrucciones break etiquetado y continue para bucles while: //: c03:LabeledWhile. outer: while(true) { prt("salida del bucle while"). break outer. while(true) { i++. if(i == 1) { prt("continuar"). } if(i == 5) { prt("quebrar"). dado que break por si solo puede salir del bucle mas interno (Lo mismo es cierto para continue). } if(i == 7) { prt("quebrar para afuera"). } 155 .

La salida de este método lo aclara: Outer while loop i = 1 continue i = 2 i = 3 continue outer Outer while loop i = 4 i = 5 break Outer while loop i = 6 i = 7 break outer Es importante recordar que la única razón para utilizar etiquetas en Java es cuando se tiene bucles anidados y se quiera realizar un break o un continue a través de uno o mas niveles anidados. 2. Es también interesante de destacar que este es un caso en donde dadas las características del lenguaje es mas útil restringir el poder de la instrucción. Un break “desciende inmediatamente al final” del bucle. En el trabajo de Dijkstra’s “goto considerado dañino”. Una instrucción continue etiquetada salta a la etiqueta y entra en el bucle inmediatamente después de la etiqueta. lo que el específicamente objetaba eran las etiquetas. ya que son limitadas a un sitio y no pueden ser utilizadas para transferir el control en una manera ad hoc. dado que introduce ciclos en la gráfica de ejecución.println(s). } } ///:~ Las mismas reglas se cumplen para while: 1. Se puede ver que las etiquetas en Java no padecen de este problema. El observaba que el número de errores parecían incrementarse con el número de etiquetas en un programa. Una instrucción continue simple salta a el comienzo del bucle mas interno y continúa.com . Las etiquetas y las instrucciones goto hacen que sea difícil de analizar la sintaxis de los programas. 4. Un break etiquetado desciende al final del bucle indicado por la etiqueta.out. 3.} } } static void prt(String s) { System.BruceEckel. 156 Pensando en Java www. no el goto.

se ejecuta la instrucción en default. break. compuesta) correspondiente se ejecuta. Su forma es: switch(selector-integral) case valor-integral1 : case valor-integral2 : case valor-integral3 : case valor-integral4 : case valor-integral5 : // . instrucción. por ejemplo. Si no se encuentra ninguna coincidencia.switch switch es también clasificada como una instrucción de selección. Si se desea utilizar. La última instrucción.java // Demonstrates the switch statement. el código de la siguiente instrucción case se ejecuta hasta que un break es encontrado. break.. deberá utilizar una serie de instrucciones if.. puede ser útil para un programador experimentado. pero esto requiere un selector que evalúe un valor integral como lo es int o char. 157 . He aquí un ejemplo que genera letras al azar y determina cual de ellas son vocales y cual de ellas son consonantes: //: c03:VowelsAndConsonants. La instrucción switch compara el resultado de selector-integralcon cada valorintegral Si se encuentra una coincidencia. pero la instrucción break es opcional. Para tipos que no sean integrales. que continúa el default.random() * 26 + 'a'). que hace que la ejecución salte a el final del cuerpo del switch. default: instrucción. La instrucción switch es una forma limpia de implementar una selección con varios caminos(i. no funcionará con la instrucción switch. Esta es la forma convencional de armar una instrucción switch. Se verá en la definición anterior que cada case termina con un break.. break. System. } { instrucción. Si no se encuentra. A pesar de que no se quiera este tipo de comportamiento. Esta selecciona de un conjunto de segmentos de código basadas en el valor de una expresión integral. break. instrucción. selector-integrales una expresión que produce un valor integral. seleccionando de una determinada cantidad de diferentes caminos de ejecución).out. una cadena o un valor de punto flotante como selector. instrucción. no tiene un break porque la ejecución termina donde el brea k debería salir. i < 100. la instrucción(simple o .e. public class VowelsAndConsonants { public static void main(String[] args) { for(int i = 0. Se puede colocar un break al final de la instrucción default sin daño si considera importante por un tema de estilo. break.print(c + ": "). instrucción. i++) { char c = (char)(Math.

random() produce un valor double. si se tiene el valor 29. Debe notarse que las instrucciones case pueden ser “apiladas” arriba de cada una de las otras para proporcional múltiples coincidencias para una segmento de código particular. El resultado double es regresado a el tipo char con una conversión. Esto significa que ‘a’ debe ser convertido a double para realizar la suma. ¿Que es lo que el convertir a el tipo char hace? ¿Esto es. default: System. lo que también produce un valor del tipo double. el valor resultante es 30 o 29? La respuesta a esto puede verse en el ejemplo: //: c03:CastingNumbers. Aunque a pesar de que parece estar conmutando caracteres. así es que el valor 26 es convertido a un double para ejecutar la multiplicación.java 158 Pensando en Java www. case 'y': case 'w': System. Math. la instrucción switch esta utilizando el valor integral del carácter.out. merece una mirada detallada.println( "Sometimes a vowel").println("vowel").println("consonant").out. El carácter entre comillas simples en la instrucción case también produce valores integrales que pueden utilizarse para la comparación.random() genera un valor entre 0 y 1.BruceEckel.com . necesita solo multiplicar por el límite superior del rango de números que desea producir (26 por las letras del alfabeto) y agregar un desplazamiento para establecer el limite inferior. Detalles de cálculo La instrucción: char c = (char)(Math. break.switch(c) { case 'a': case 'e': case 'i': case 'o': case 'u': System. de otra forma el control continuará simplemente ejecutando el siguiente case. break. Se debe ser cuidadoso con que es esencial colocar una instrucción break al final de cada case. } } } } ///:~ Dado que Math.out.7 y convertimos el tipo a char.random() * 26 + 'a').

7 below: 0. o [0.1)? (Los paréntesis rectos significan “incluye” y los paréntesis significan “no incluye”) Nuevamente.0!").println( "(int)below: " + (int)below). La segunda pregunta atañe a Math.4.println( "(int)above: " + (int)above). } } ///:~ La salida es: above: 0.random() != 0.println("below: " + below). System.out. } 159 . System.out.out.java // Does Math.out.println("above: " + above). esto es (0.1). if(args[0].// Que sucede cuando convertimos un tipo float // o double en un valor entero? public class CastingNumbers { public static void main(String[] args) { double above = 0.equals("lower")) { while(Math.length != 1) usage(). System. System.random().println( "(char)('a' + above): " + (char)('a' + above)).out. below = 0.0 and 1.out. System. } public static void main(String[] args) { if(args.exit(1).println("Usage: \n\t" + "RandomBounds lower\n\t" + "RandomBounds upper"). System.println("Produced 0.1] o [0. un programa probara la respuesta: //: c03:RandomBounds.out.0? public class RandomBounds { static void usage() { System.1]. ¿Produce un valor de cero a uno.random() produce 0. // Keep trying System. inclusive o el valor 1 no esta incluido? ¿En lengua matemática. o (0. System.4 (int)above: 0 (int)below: 0 (char)('a' + above): a (char)('a' + below): a Así es que la respuesta de convertir el tipo float o double a un valor entero siempre trunca.7.println( "(char)('a' + below): " + (char)('a' + below)).out.0) .

0) .1).BruceEckel. } } ///:~ Para ejecutar el programa. p es la precisión (dígitos en la mantisa). precedencia. Si consideramos27 que hay alrededor de 262 diferentes fracciones del tipo double entre 0 y 1. así es que apareceráque Math. p = 53.0 esta incluido en la salida de Math. escribiremos una de las dos siguientes líneas de comandos: java RandomBounds lower o java RandomBounds upper En ambos casos estamos forzados a terminar al programa manualmente.random() != 1. así es que 1/4 de esa expresión. b = 2 así es que el número total de numeros es 2(1023+1022+1)2^52 =2((2^10-1)+(2^10-1))2^52 =(2^10-1)2^54 =2^64-2^54 La mitad de estos números (corresponden a exponentes en el rango [-1022.0]) que son menos que 1 en magnitud (ambos positivos y negativos). 2 Chuck Allison escribió: El número total de números en un sistema de punto flotante es 2(M-m+1)b^ (p-1)+1 donde b es la base (usualemente 2).random() nunca produce o 0. Ahora estamos listos para comenzar a dar pasos que nos acerquen al mundo de la programación orientada a objetos. Vea mi trabajo en http://www. IEEE754 utiliza: M = 1023. seguido del capítulo con el esencial concepto de implementación oculta.1). Pero esto es un experimento que puede ser engañoso. 160 Pensando en Java www. la probabilidad de alcanzar cada uno de los valores experimentalmente excede el tiempo de vida de una computadora.com/1995006a.out.o o 1. o 2^62 . M es el largo del exponente.freshsources.htm (al final del texto). m = -1022. es [0. en lenguaje matemático.else if(args[0]. O lo que. o inclusive la de un experimentador. tipos de conversiones y selección e iteración.0!").com .2^52 + 1 (aproximadamente 2^62) está en el rango de [0.println("Produced 1. y m es el exponente mas pequeño. Entrega que 0. // Keep trying System. El siguiente capítulo cubre importantes temas acerca de inicialización y limpieza de objetos.random().equals("upper")) { while(Math. } else usage().0. Resumen Con este capítulo concluye el estudio de los aspectos que aparecen en la mayoría de los lenguajes de programación: cálculos.

5. En el main(). y coloque el switch dentro de un bucle for que examine cada caso. 10. 2.BruceEckel. Debe ejecutarse hasta que se interrumpa con el teclado (típicamente presionando Control-C). 3. De las secciones etiquetadas “if-else” y “return”.com . Hay dos expresiones en la sección etiquetada “precedencia” al inicio de este capítulo. 4. Para cada valor. Escriba un programa que utilice dos bucles for anidados y el operador módulo (%) para detectar e imprimir números primos (números enteros que solo sean divisibles por si mismos y por la unidad). Para el == y el ¡=. luego quite los breaks y vea que sucede. coloque los métodos test() y test2() en un programa que trabaje. Trate de utilizar return en su lugar. menor o igual a un segundo valor generado al azar. 6. Cree una instrucción switch que imprima un mensajes por cada case. Coloque estas expresiones en un programa y demuestre que producen diferentes resultados. Coloque un break luego de cada caso y pruébelo. llame su función con diferentes objetos String. 161 . 7. Modifique el ejercicio 7 para que el código circule con un bucle while “infinito”. Coloque los métodos ternary() y alternative() en un programa que trabaje. Escriba un programa que genere 25 números al azar enteros. también realice la prueba con equals(). Modifique el ejercicio 4 para que el programa salga utilizando la palabra clave break con el valor 47. Escriba una función que tome dos argumentos String. 8. 9.Ejercicios La solución de los ejercicios seleccionados pueden encontrarse en el documento electrónico The T h i n k i n g i n J a v a A n n o t a t e d S o l u t i o n G u i d e. y utilice todas las comparaciones boolean para comparar los dos Strings e imprima el resultado. Escriba un programa que imprima valores desde uno a 100. 1. utilice una instrucción if-then-else para clasificar si es mayor. disponible por una pequeña propina en www.

un método especial que es automáticamente llamado cuando un objeto es creado.com . Inicialización garantida con el constructor Podemos imaginarnos creando un método llamado initialize() para cada clase que escriba. el diseñador puede garantizar la inicialización de cada objeto proporcionando un método especial llamado constructor. Si una clase tiene un constructor. C++ introduce el concepto de constructor. El siguiente desafío es como llamar a este método. antes de que los usuarios puedan inclusive poner las manos en el. Desafortunadamente. Java también ha adoptado el constructor. esto significa que el usuario debe recordar llamar al método.4: Inicialización y Limpieza Así como la revolución de las computadoras progresa. o inclusive si deberían. El primero es que cualquier nombre puede entrar en conflicto con un nombre 162 Pensando en Java www. De esta manera. La limpieza es un problema especial porque es fácil de olvidar un elemento cuando ha terminado con el. Java automáticamente llama el constructor cuando el objeto es creado. El nombre es una insinuación debería ser llamado antes de utilizar un objeto. Este capítulo examina los temas de inicialización y limpieza. y además tiene un recolector de basura que automáticamente libera los recursos de memoria cuando no son utilizados mas. puesto que ya no interesa. En Java. Esto es especialmente verdadero con librerías donde los usuarios no conocen como inicializar un componente de la librería. la programación “insegura” se ha convertido en una de los mayores culpables que hace la programación costosa.BruceEckel. y su soporte en Java. la memoria). Así es que la inicialización es garantida. los recursos utilizados por ese elemento son retenidos y se puede fácilmente quedarse sin recursos (el que mas se hace notar. Aquí hay dos temas. Dos de estos temas de seguridad son la inicialización y la limpieza. Mucho errores en C se suceden cuando el programador olvida inicializar una variable.

i < 10. } } ///:~ 163 . i++) new Rock(). class Rock2 { Rock2(int i) { System. class Rock { Rock() { // This is the constructor System.java // Constructors can have arguments. la memoria es asignada y el constructor es llamado. Se puede ver que el estilo de codificación al colocar la primera letra de todos los métodos en minúscula no se aplica a los constructores. el constructor puede tener argumentos que permitan especificar como un objeto es creado.out. } } public class SimpleConstructor { public static void main(String[] args) { for(int i = 0.println( "Creating Rock number " + i). } } ///:~ Ahora cuando un objeto es creado: new Rock(). Esto garantiza que el objeto será propiamente inicializado antes que se pueda poner las manos encima de el.java // Demonstration of a simple constructor.out.println("Creating Rock"). //: c04:SimpleConstructor2. esto tiene sentida dado que el método será llamado automáticamente en la inicialización. El siguiente ejemplo puede ser fácilmente cambiado para que el constructor tome un argumento. Como cualquier método. El segundo es que dado que el compilador es responsable por llamar el constructor. i < 10. dado que el nombre del constructor debe corresponder con el nombre de la clase exactamente. así es realizado en Java también: el nombre del constructor es el mismo nombre que el de la clase. Aquí hay una clase simple con un constructor: //: c04:SimpleConstructor. } } public class SimpleConstructor2 { public static void main(String[] args) { for(int i = 0. este debería siempre saber que método llamar. i++) new Rock2(i).que se desee utilizar en un miembro de la clase. La solución de C++ parece la mas simple y lógica.

Los constructores no retornan nada y no se tiene otra opción.BruceEckel. “camizaLavar la camisa”. Nos referimos a todos los objetos y métodos utilizando nombres. // arbol de 12 pies Si Tree(int) es su único constructor. Por ejemplo. A menudo. En el anterior fragmento de código. por ejemplo. Si hay un valor de retorno y se pudiera seleccionar uno propio. se puede crear objetos Tree como estos: Tree t = new Tree(12). Sería tonto ser forzado a decir. “lava el auto” y “lava el perro”. Un problema se origina cuando se proyecta el concepto de matiz en el lenguaje humano en un lenguaje de programación. entonces el compilador no creará un objeto Tree de ninguna otra forma. La mayoría de los lenguajes humanos son redundantes. Los constructores eliminan una larga lista de problemas y hacen el código mas fácil de leer. Se dice “lava la camisa”. El constructor es un tipo de método inusual porque no retorna ningún valor.com . la definición y la inicialización son conceptos unificados-no se puede tener uno sin el otro. Utilizando nombres para describir su sistema. 164 Pensando en Java www. Cuando se crea un objeto. si la clase Tree tiene un constructor que toma un único argumento entero indicando el tamaño del árbol. Es muy parecido a escribir en prosala meta es comunicarse con sus lectores. en donde el método no retorna nada pero se sigue teniendo la opción de realizar algún tipo de retorno. especialmente cuando viene de diferencias triviales. se crea un programa que es fácil de entender y modificar para las personas. la misma palabra tiene muchos significados-esto es sobrecargar Esto es útil.Los argumentos del constructor proporcionan una manera de proporcionar parámetros a la inicialización de un objeto. No necesitamos identificadores únicos-podemos deducir el significado del contexto. no verá una llamada explicita a ningún método initialize() que este conceptualmente separado de la definición. Esto es claramente diferente de un valor de retorno void. Sobrecarga de métodos Una característica importante en cualquier lenguaje de programación es el uso de nombres. En Java. “autoLavar el auto” y “perroLavar el perro” simplemente porque el oyente no necesita realizar ninguna distinción acerca de la acción a realizar. . se le da un nombre a una región para almacenamiento. el compilador necesitaría de alguna forma saber que hacer con ese valor de retorno. un método es el nombre para una acción. Nombres bien elegidos hacen mas fácil para nosotros y otros entender el código. así es que si perdemos algunas palabras se seguirá determinando el significado.

out. } Tree(int i) { prt("Creating new Tree that is " + i + " feet tall"). Necesitará dos constructores. } 165 . ¿Pero que sucede si se quiere crear un objeto de mas de una forma? Por ejemplo. En Jaca (y C++). He aquí un ejemplo que muestra dos constructores y dos métodos ordinarios sobrecargados: //: c04:Overloading. class Tree { int height. De esta manera.java // Demonstration of both constructor // and ordinary method overloading. Así es que no se puede tener una función llamada print() para imprimir enteros y otra llamada print() para imprimir números de punto flotante-cada función requiere un único nombre. import java. solo puede haber un nombre de constructor.util. Ambos son constructores. es de conveniencia general y puede ser utilizada con cualquier método. la sobrecarga de métodoses esencial para permitir el mismo nombre de métodos para ser utilizado con diferentes tipos de argumentos. Tree() { prt("Planting a seedling"). height = i. que es el nombre del fichero con el cual se inicializará el objeto. uno que no tome argumentos (el constructor por defecto. } void info(String s) { prt(s + ": Tree is " + height + " feet tall"). Y a pesar de que la sobrecarga de métodos es una condición necesaria para constructores. } void info() { prt("Tree is " + height + " feet tall"). también llamado constructor no-arg). Dado que el nombre del constructor es predeterminado por el nombre de la clase.Muchos lenguajes de programación (C en particular) requiere que tenga un único identificador para cada función. height = 0. } static void prt(String s) { System.println(s). otro factor fuerza la sobrecarga de nombres de métodos: el constructor. supongamos que se crea una clase que pueda inicializarse sola en una manera estándar o leyendo información desde un fichero.*. así que deben tener el mismo nombre-el nombre de la clase. y uno que tome un argumento del tipo String.

166 Pensando en Java www. Por fortuna. El término “constructor por defecto” ha estado en uso por muchos años y usaré ese. como puede Java saber a que método nos referimos? Hay una regla simple: cada método sobrecargado debe tomar una única lista de tipos de argumentos. } // Overloaded constructor: new Tree(). la sobrecarga de métodos permite utilizar el mismo nombre para ambos.BruceEckel. Si pensamos en esto por unos segundos. Distinguiendo métodos sobrecargados ¿Si los métodos tienen el mismo nombre. hay dos constructores.java // Overloading based on the order of // the arguments. Parece extraño dar dos nombres separados para lo que es obviamente el mismo concepto. t. int i) { 1 En alguna literatura de Java de Sun en lugar de llamarlos así se refieren con el desmañado pero descriptivo nombre de “constructores no-arg”.info("overloaded method"). con un argumento del tipo String si tenemos un mensaje extra que queremos imprimir. } } ///:~ Un objeto Tree puede ser creado en un almácigo de una semilla. Por ejemplo. o como una planta crecida en un criadero. tiene sentido: ¿como puede si no un programador indicar la diferencia entre dos métodos que tienen el mismo nombre que por el tipo de sus argumentos? Incluso las diferencias en el orden de los argumentos son suficientes para distinguir dos métodos: (A pesar de que normalmente no se quiera tomar esta aproximación.com . i < 5. //: c04:OverloadingOrder. t.info(). con un tamaño. Queremos también llamar al método info() en mas de una forma. dado que produce código difícil de mantener). i++) { Tree t = new Tree(i).} public class Overloading { public static void main(String[] args) { for(int i = 0. uno que no toma argumentos (llamamos a los constructores que no toman argumentos constructores por defecto1 ) y uno que toma la altura existente. y sin necesidad de saber mas. Para respaldar esto. sin argumentos. public class OverloadingOrder { static void print(String s.

println( "int: " + i + ". public class PrimitiveOverloading { // boolean can't be automatically converted static void prt(String s) { System. } void f2(double x) { prt("f2(double)"). pero el orden es diferente. } void f2(float x) { prt("f2(float)"). } void f2(byte x) { prt("f2(byte)"). } void f2(int x) { prt("f2(int)"). } void f2(long x) { prt("f2(long)"). } void f4(int x) { prt("f4(int)"). "Int first"). 11). } void f1(short x) { prt("f1(short)").java // Promotion of primitives and overloading. } void f1(char x) { prt("f1(char)").out. y eso es lo que los hace distintos.println(s). Sobrecarga con primitivas Una primitiva puede automáticamente promoverse de un tipo mas pequeño a uno mas grande y esto puede ser ligeramente confuso en combinación con la sobrecarga. String: " + s). } void f3(float x) { prt("f3(float)"). } void f1(double x) { prt("f1(double)"). } void f1(byte x) { prt("f1(byte)"). print(99. } void f4(float x) { prt("f4(float)"). } void f1(long x) { prt("f1(long)"). } void f1(int x) { prt("f1(int)"). String s) { System. } void f1(float x) { prt("f1(float)"). } void f3(double x) { prt("f3(double)").println( "String: " + s + ".out.out. } void f3(long x) { prt("f3(long)"). El siguiente ejemplo muestra que sucede cuando una primitiva es manejada por un método sobrecargado: //: c04:PrimitiveOverloading.System. } void f4(long x) { prt("f4(long)"). } } ///:~ Los dos métodos print() tienen argumentos idénticos. } void f2(short x) { prt("f2(short)"). } 167 . int: " + i). } static void print(int i. } void f3(int x) { prt("f3(int)"). } void f4(double x) { prt("f4(double)"). } public static void main(String[] args) { print("String first". } void f3(short x) { prt("f3(short)").

BruceEckel.f6(x). } void testChar() { char x = 'x'. f1(x). } void f6(float x) { prt("f6(float)").testShort().com . p.f5(x).f6(x).void f5(long x) { prt("f5(long)").f6(x).f2(x).f2(x).f2(x).f5(x).f3(x).f6(x). } void testShort() { short x = 0.f7(x).f5(5).f3(x).testConstVal(). } void f5(double x) { prt("f5(double)").f3(5).f5(x).f4(x).f5(x).f6(x). prt("long argument:").testFloat().f2(x). f1(x). } void testLong() { long x = 0.f4(x). p.f3(x).testByte().f2(x). } void testDouble() { double x = 0. } void f6(double x) { prt("f6(double)"). f1(5). prt("char argument:").f2(x).f7(x). prt("short argument:"). f1(x).testLong(). prt("int argument:").f6(5).f7(5). f1(x).f6(x).f7(x). } void f7(double x) { prt("f7(double)"). f1(x).f7(x). p. 168 Pensando en Java www. f1(x).f4(x).f3(x).f5(x).f4(5). p. } void testInt() { int x = 0.f4(x). } void testByte() { byte x = 0. f1(x). p. } public static void main(String[] args) { PrimitiveOverloading p = new PrimitiveOverloading().f7(x).f3(x).f2(5). prt("float argument:").f5(x).f4(x).f2(x).testInt().f4(x). prt("double argument:"). } void testFloat() { float x = 0. } void f5(float x) { prt("f5(float)"). p.f3(x).f7(x).f6(x).f3(x). p. prt("byte argument:"). } void testConstVal() { prt("Testing with 5").f7(x).f5(x).f4(x).testChar().

} void f4(byte x) { prt("f4(byte)"). dado que no encuentra una coincidencia exacta con un char. } void f2(int x) { prt("f2(int)").f7((char)x).p. } void f2(char x) { prt("f2(char)"). El tipo char produce un efecto ligeramente diferente. public class Demotion { static void prt(String s) { System. } void f3(byte x) { prt("f3(byte)"). } void f1(double x) { prt("f1(double)"). } void f5(char x) { prt("f5(char)"). así es que si un método sobrecargado esta disponible que tome un tipo int es utilizado.testDouble(). } } ///:~ Si vemos la salida de este programa. } void f1(int x) { prt("f1(int)"). 169 .java // Demotion of primitives and overloading. } void f2(byte x) { prt("f2(byte)"). } void f4(char x) { prt("f4(char)"). } void f5(short x) { prt("f5(short)"). } void f4(short x) { prt("f4(short)").f6((byte)x). si se tiene un tipo de datos que es mas pequeño que el argumento en el método. ese tipo de datos es promovido. } void f2(long x) { prt("f2(long)").println(s).f3((long)x). } void f1(byte x) { prt("f1(byte)"). } void f2(float x) { prt("f2(float)").out. } void f6(byte x) { prt("f6(byte)"). } void f3(long x) { prt("f3(long)"). } void f7(char x) { prt("f7(char)").f2((float)x). } void testDouble() { double x = 0. En los otros casos. } void f1(short x) { prt("f1(short)"). } void f3(char x) { prt("f3(char)"). se puede ver que el valor constante 5 es tratado como un tipo int. } void f1(long x) { prt("f1(long)"). prt("double argument:").f4((int)x). } void f4(int x) { prt("f4(int)"). } void f6(char x) { prt("f6(char)"). f5((short)x). es promovido a el tipo int. } void f3(short x) { prt("f3(short)"). } void f2(short x) { prt("f2(short)"). } void f1(char x) { prt("f1(char)"). } void f1(float x) { prt("f1(float)"). } void f3(int x) { prt("f3(int)"). f1(x). } void f5(byte x) { prt("f5(byte)"). ¿Que sucede si su argumento es mayor que el argumento esperado por el método sobrecargado? Una modificación al programa anterior nos da la respuesta: //: c04:Demotion.

son distinguidos fácilmente uno de otro: void f() {} int f() {} Esto funciona correctamente cuando el compilador puede determinar sin equivocarse el significado del contexto. no se puede utilizar para distinguir métodos sobrecargados el tipo de valor de retorno. } } ///:~ Aquí. esto es a menudo referido como llamar a un método por su efecto secundario(calling a method for its side effect) dado que no importa el valor de retorno pero en lugar de eso se quieren los otros efectos de la llamada al método. Constructores por defecto Como se ha mencionado previamente.testDouble(). como en int x = f(). se puede llamar a un método e ignorar el valor de retorno. que tienen el mismo nombre y argumentos. Si no hace esto. 170 Pensando en Java www. Así es que si se llama el método de esta forma: f(). estos dos métodos. el compilador automáticamente crea un constructor por defecto para usted. los métodos toman valores primitivos mas pequeños. p. que significa que se puede perder información durante la conversión. Sobrecarga de valores de retorno Es común preguntarse “¿Por que solo nombres de las clases y listas de argumentos de métodos? Por que no distinguir métodos basándose en sus valores de retorno?” Por ejemplo. Si el argumento es mayor entonces se deberá convertir el tipo a el necesario utilizando el nombre del tipo entre paréntesis. un constructor por defecto (también conocido como constructor “no-arg”) es el que no tiene argumentos.com . Si se crea una clase que no tiene constructores.BruceEckel. ¿como puede Java determinar que f() debe llamar? ¿Y como debería alguien que lee el código verlo? A causa de este tipo de problema. utilizado para crear un “objeto vainilla”. el compilador emitirá un mensaje de error.java class Bird { int i. Sin embargo. Esto es porque el compilador fuerza a hacerlo-para indicar la conversión a menos. Se debe ser consciente de que esto es una conversión a menor.} public static void main(String[] args) { Demotion p = new Demotion(). Por ejemplo: //: c04:DefaultConstructor.

Es como cuando no se agrega ningún constructor. como puede ese método saber de donde ha sido llamado. podemos preguntarnos como es que se puede llamar un método f() para ambos objetos: class Banana { void f(int i) { /* . b = new Banana().1). del objeto a o del b? Para que se pueda escribir el código con una sintaxis orientada a objetos conveniente en donde se pueda “enviar un mensaje a un objeto”. Banana.f(2). class Bush { Bush(int i) {} Bush(double d) {} } Ahora si decimos: new Bush(). crea un nuevo objeto y llama el constructor por defecto. Pero si escribe un constructor. Sin embargo. el compilador no sintetizará por si solo. Sin el no se tendrá método a llamar para crear el objeto.2). si definimos alguno de los constructores (con o sin argumentos). el compilador hace algún trabajo clandestino para nosotros. el compilador dice “Esta obligado a tener algún constructor. el compilador dice “Ha escrito un constructor así es que usted sabe lo que esta haciendo. si no coloca uno por defecto es porque da a entender que lo quiere afuera.” La palabra clave this Si se tiene dos objetos del mismo tipo llamados a y b. b. a.. así es que deje que yo haga uno por usted”. El compilador reclamará que no encuentra un constructor que corresponde. 171 . Así es que los dos métodos llamados anteriormente se transforman en algo como: Banana. y este argumento es la referencia a el objeto que es manipulado.} public class DefaultConstructor { public static void main(String[] args) { Bird nc = new Bird().f(1). aún cuando no haya uno definido explícitamente. // default! } } ///:~ La línea new Bird().. */ } } Banana a = new Banana(). ¿Si hay solo un método llamado f(). Hay un primer argumento secreto pasado a el método f().f(b.f(a.

java // Simple use of the "this" keyword.out. Leaf increment() { i++. Se puede tratar esta referencia exactamente como otra referencia a objeto. no hay identificador para esta.. simplemente llamamos al método. return this.. muchas operaciones pueden ser fácilmente realizadas en el mismo objeto. Dado que esta referencia es pasada secretamente por el compilador.. Supongamos que esta dentro de un método y queremos tomar la referencia del objeto actual.increment().BruceEckel. El compilador hace esto por nosotros automáticamente. Llamando constructores desde constructores Cuando escribimos varios constructores para una clase. hay veces que se quiere llamar un constructor desde otro para evitar código duplicado. se debe tener presente que si estamos llamando a un método de la clase desde adentro de otro método de la clase.increment(). Por ejemplo. x. /* . */ } void pit() { pick(). La referencia actual this es automáticamente utilizada por el otro método. Se puede hacer esto utilizando la palabra clave this. para este propósito hay una palabra clave this. pero da una idea de que es lo que esta sucediendo.println("i = " + i).increment(). no necesita utilizar this.Esto es interno y no se puede escribir esta expresión y hacer que el compilador la acepte. } } ///:~ Dado que increment() retorna la referencia al objeto actual mediante la palabra clave this. se puede decir this. } public static void main(String[] args) { Leaf x = new Leaf(). } void print() { System. 172 Pensando en Java www. a menudo es utilizada en instrucciones return cuando queremos retornar la referencia al objeto actual: //: c04:Leaf.print(). Sin embargo. public class Leaf { int i = 0. Esta palabra clave -que puede ser utilizada solo dentro de un método-produce la referencia a el objeto que ha llamado al método. */ } } Dentro de pit().com . La palabra clave this es utilizada solo para aquellos casos especiales en donde necesitamos explícitamente utilizar esa referencia para el objeto actual.pick() pero no es necesario.. Entonces se puede decir: class Apricot { void pick() { /* .

System. String s = new String("null"). //! this(s). En un constructor. } void print() { //! this(11). Podemos resolverlo diciendo this.s para referirnos a el dato 173 . } } ///:~ El constructor Flower(String s. s = ss.println( "Constructor w/ String arg only.print().out. } Flower(String ss) { System. cuando decimos this. int petals) muestra. la palabra clave this tiene un significado diferente cuando le damos una lista de argumentos: este hace una llamada explícita a el constructor que se corresponde con la lista de argumentos. Flower(int petals) { petalCount = petals.Normalmente. int petals) { this(petals). s=" + ss).out. no se pueden llamar dos. hay aquí una ambigüedad.println( "petalCount = " + petalCount + " s = "+ s). // Not inside non-constructor! System.println("String & int args").out. la llamada al constructor debe ser la primer cosa que se haga o se obtendrá un mensaje de error del compilador. } Flower() { this("hi".println( "Constructor w/ int arg only.out.out.s = s.java // Calling constructors with "this. System.println( "default constructor (no args)"). Dado que el nombre del argumento s y el dato miembro s son el mismo. Además." public class Flower { int petalCount = 0. x. // Can't call two! this. y por si solo produce la referencia a el objeto actual. petalCount= " + petalCount). } Flower(String s. } public static void main(String[] args) { Flower x = new Flower(). De esta manera se tiene una forma directa de llamar a otros constructores: //: c04:Flower. cuando mientras que se puede llamar un constructor utilizando this. Este ejemplo también muestra otra forma en que this es utilizado. es en el sentido de “este objeto” o de “el objeto actual”. // Another use of "this" System. 47).

y se puede llamar a un método estático para la clase misma. Entonces. se puede llamar métodos no estático y acceder a campos que no son estáticos. Es como si se hubiera creado el equivalente a una función global (de C).BruceEckel. ¿Después de todo. simplemente “dejar” un objeto una vez que ha terminado con el no es seguro. Esto probablemente sea un argumento considerable. así es que si es o no “propiamente POO” se lo dejamos a los teóricos. 174 Pensando en Java www. sin un objeto. Algunas personas discuten que los métodos estáticos no son orientados a objetos desde que tienen la semántica de una función global. esto es para lo principal que un método estático es. dado que no hay this. A menudo se verá esta forma utilizada en código Java y en numerosos lugares en este libro. Sin embargo. Limpieza: finalización y recolección de basura Los programadores sabes acerca de la importancia de la inicialización. Ciertamente. No puede llamar a un método que no es estático desde un método estático2 (a pesar que la reversa es posible). Excepto que las funciones globales no son permitidas en Java. Claro. pero a menudo olvidan la importancia de la limpieza. Java tiene el recolector de basura para reclamar la memoria de los objetos que ya no se utilizan. El recolector de 2 El único caso en que esto es posible ocurre cuando se pasa una referencia a un objeto dentro del método estático.miembro. y si se encuentra que se está utilizando un montón de métodos estáticos probablemente haya que pensar una nueva estrategia. por medio de la referencia (que no es efectivamente this). En print() se puede ver que el compilador no dejara que se llame un constructor dentro de otro método que no sea el constructor.com . De hecho. puede entender completamente que significa hacer un método static . quien necesita limpiar un tipo int? Pero con las librerías. El significado de static Con la palabra clave this en mente. con un método estático no se envía un mensaje a un objeto. Esto significa que no hay this para un método particular. y colocar un método estático dentro de una clase permite acceder a otros métodos y campos estáticos. aún Smalltalk tiene el equivalente en sus “métodos de clases”. Pero típicamente si quiere hacer algo como eso simplemente cree un método no estático común. Ahora consideremos un caso muy inusual. static es pragmático y hay veces que se necesita verdaderamente. Supóngase que un objeto asigna espacio de memoria “especial” sin utilizar new .

mientras que los no siempre se recoge la basura de los objetos Java. He aquí como supuestamente funciona. Cuando el recolector de basura esta pronto para liberar el espacio asignado por el objeto. porque la recolección de basura tiene costos operativos.basura solo sabe como liberar memoria asignada con new . O. se debe realizar la actividad por uno mismo. puesto de otra forma: La recolección de basura no es destrucción Si se recuerda esto. así es que no sabe como liberar la memoria “especial” del objeto. primero llamara a finalize().. se estará fuera de problema. Java proporciona un método llamado finalize() que se puede definir en una clase. Esto es una pequeña dificultad porque algunos programadores. y solo en la siguiente recolección reclamara la memoria del objeto. pero si no se hace la imagen quedara. Esto es una cosa buena. Si no se borra esa imagen de la pantalla explícitamente. pueden en un principio confundir finalize() con el destructor en C++. 175 . la imagen será eliminada primero de la pantalla. Así es que un segundo punto para recordar es: Sus objetos pueden no ser recogidos por el recolector. supongamos que en el proceso de crear su objeto dibuja en la pantalla por si solo. Java no tiene destructores o conceptos similares. entonces si un objeto es recogido por el recolector de basura. dado que en C++ los objetos siempre son destruidos (en un programa libre de errores). Lo que esto significa es que si hay alguna actividad que deba ser realizada antes de que no se necesite mas un objeto. Por ejemplo. Se puede que el espacio asignado a un objeto nunca es liberado porque el programa nunca se acerca a el punto de agotarse el almacenamiento. así es que debe crear un método común para realizar esta limpieza. dará la habilidad de realizar importantes tareas de limpieza en el momento de la recolección de basura. Así es que si se elige utilizar finalize(). Pero es importante distinguir entre C++ y Java aquí. nunca lo borrará. y si nunca lo hacemos nunca incurrimos en ese gasto. Para manejar este caso. que es una función que es llamada siempre cuando un objeto es destruido. especialmente los programadores C++. Si se coloca algún tipo de funcionalidad dentro de finalize(). esa memoria será retornada al sistema operativo en grupo cuando el programa termina. Si un programa termina y el recolector nunca llegó a recoger el resto del espacio para alguno de los objetos.

El limpia lo que necesita por lo que finalize() es limitado a casos especiales. free() es una función de C y C++. debe ser solo acerca de memoria y su eliminación del espacio asignado. Luego de leer esto. Y es cierto. en donde su objeto puede asignar algún espacio en otra forma que creando un objeto. causando una laguna de memoria. ¿Pero como se puede observar. ¿Que tan bueno es esto? Un tercer punto a recordar es este: La recolección de basura es solo acerca de memoria. Esto suena muy directo. C y C++ son los únicos lenguajes soportados actualmente por métodos nativos. pero choca 176 Pensando en Java www. y a no ser que llame a free() esa memoria asignada no será liberada.com .¿Para que es finalize()? En este momento se puede pensar que nunca se utilizará finalize() como método de propósito general para limpieza. así es que necesita llamar un método nativo dentro de su método finalize(). no es el lugar para que se suceda una limpieza normal. Así es que cualquier actividad que este asociada con la recolección de basura. ¿Esto no significa que si un objeto contiene otros objetos finalize() deberá explícitamente liberar estos objetos? Bueno. la familia de funciones malloc() de C pueden ser llamadas para asignar memoria.BruceEckel. puede efectivamente llamar cualquier cosa. que son una forma de llamar código que no es Java desde Java (Los métodos nativos son discutidos en el apéndice B). Dentro de código que no es Java. ¿Así es que donde debe realizarse la limpieza normal? Se debe realizar limpieza Para limpiar un objeto. no-el recolector de basura se encarga de liberar todos los objetos de la memoria independientemente de como es creado el objeto. Claro. la única razón para que exista el recolector de basura es para recuperar memoria que el programa no utilizará mas. todo en Java es un objeto así es que como puede ser esto? Parecería que finalize() esta en su sitio por la posibilidad de que se haga algo al estilo de C para asignar memoria utilizando otro mecanismo que el normal en Java. mucho mas el método finalize(). el usuario de un objeto debe llamar un método para limpiarlo en el momento que se desee. Esto es. probablemente se tenga la idea de que no se utilizará finalize() mucho. pero dado que se puede llamar a subprogramas en otros lenguajes. Esto puede en un principio suceder cuando se utilizan métodos nativos.

java // Demonstration of the garbage // collector and finalization class Chair { static boolean gcrun = false. static int created = 0. Si quiere realizar algún tipo de limpieza además que la de liberación de memoria debe llamar explícitamente a un método apropiado en Java. Este tipo de error puede ser muy difícil de encontrar. que es el equivalente a el destructor de C++ sin la comodidad.out. que la presencia del recolector de basura no elimina la necesidad o utilidad de los destructores (y nunca se debería llamar finalize() directamente. Pero en Java. System. } if(i == 47) { System.println( "Comenzando a finalizar antes de que " + created + " Chairs hayan sido creadas").println("Creada la 47"). así es que no es un camino apropiado para una solución).out. sin embargo. la destrucción sucede cuando se cierra la llave del alcance en donde el objeto fue creado.e. Así es que desde un punto de vista simplista puede decir que a causa del recolector de basura. Una de las cosas para las que finalize() puede ser útil es para observar el proceso de recolección de basura. } public void finalize() { if(!gcrun) { // La primera vez que finalize() es llamado: gcrun = true.println( 177 . el destructor nunca es llamado y tenemos una laguna de memoria. int i. todos los objetos deberían de ser destruidos.out. Se verá cuando este libro progrese. static int finalized = 0. Java no permite que se creen objetos locales-siempre se tiene que utilizar new . static boolean f = false. Java no tiene destructor. En C++. todos los objetos son destruidos. O mejor dicho. El siguiente ejemplo muestra que está sucediendo y resume las descripciones previas de la recolección de basura: //: c04:Garbage. Chair() { i = ++created. Si el programador C++ olvida llamar a delete. no hay ”delete” a llamar para liberar el objeto dado que el recolector de basura libera la memoria asignada por nosotros. Si el objeto fue creado utilizando new (como en Java) el destructor es llamado cuando el programador llama a el operador delete de C++ (que no existe en Java). Si el objeto C++ es creado como local (i.un poco con el concepto de destructor de C++. mas la otra parte del objeto que nunca es limpiada. En contraste. if(created == 47) System. en la pila-no es posible en Java).

println("Chau!"). System."finalizando la silla #47.out. Ambas banderas son configuradas con finalize(). } } public class Garbage { public static void main(String[] args) { // Si la bandera no ha sido configurada.equals("all")) { System.runFinalization(). } System.BruceEckel.println("runFinalization():").out. new String("Para tomar espacio ").length > 0) { if(args[0]. // haga Chairs y Strings: while(!Chair.println( "Todas las " + finalized + " finalizadas").println( "Luego de que todas las Chairs han sido creadas:\n" + "total creadas = " + Chair. no se conoce exactamente cuando comenzará.finalized). Dos variables estáticas mas.f) { new Chair().equals("finalize") || args[0]. cada Chair tiene su propia (no estático) 178 Pensando en Java www.equals("all")) { System.com . } finalized++. el programa para de crear Chairs. // Argumentos opcionales fuerzan la recolección // de basura y finalización: if(args. } } ///:~ El programa anterior crea muchos objetos Chair. Una segunda bandera f es una forma de que Chair le diga al bucle de main() que debe detenerse de crear objetos. } if(args[0].out. created y finalized.created + ". así es que hay una bandera llamada gcrun para indicar cuando el recolector de basura ha comenzado a funcionar ya. total terminadas = " + Chair. y en algún punto luego de que el recolector de basura comienza a correr. Puesto que el recolector de basura no puede ejecutarse en cualquier momento.gc(). } } System. mantienen el rastro del número de Chairs creados contra el número que ha sido terminado por el recolector de basura. System. que es llamada durante lar recolección de basura.equals("gc") || args[0]. f = true.println("gc():"). Finalmente. if(finalized >= created) System.out.out. levantando una " + " bandera para parar la creación de sillas ").

cuando comience a ponerse nervioso por la cantidad de memoria disponible. Si System.f) { new Chair().runFinalization() es llamado. Si no llamamos System. en el bucle while(!Chair. El comportamiento de este programe y de la versión de la primera edición de este libro muestra que todos los temas que involucran la recolección de basura y la finalización. Sin embargo. obtendremos una salida como esta: 179 .runFinalization() que-en teoría-que cualquier objeto no finalizado sea finalizado. Esto no es necesariamente el caso con implementaciones previas del JDK. la bandera es puesta en true para detener el proceso de creación de Chair. En Java 1.gc() (para forzar la ejecución del recolector de basura). un método llamado System. Cuando se corra el programa. con mucho de la evolución que sucede puertas adentro. El siguiente programa muestra que la promesa que los finalize() se ejecutarán siempre. Además.f.1. la finalización sucede para todos los objetos. solo algunos de los objetos serán finalizados. se debe proporcionar un argumento en la línea de programa que puede ser “gc”.gc() es llamado. pero el diseño lo descarto siendo desaprobado.gc(). en el momento en que se lea esto. Y “all” que produce que ambos métodos sean llamados. se verá que no parece hacer ninguna diferencia si System. Todo esto sucede en el main(). “finalize”. Solo podemos esperar que las cosas hayan sido terminadas en Java 2. De hecho. a pesar que la documentación afirme otra cosa.runFinalizersOnExit() fue introducido que causa que los programas ejecuten todos los finalized() cuando salen. eventualmente. } Uno se puede preguntar como este bucle puede terminar. el comportamiento del programa puede haber cambiado una vez mas. La creación de un objeto String durante cada iteración es simplemente un espacio extra asignado para estimular al recolector de basura a llevárselo. Utilizando el argumento “finalize” se llama a System. o “all”. Si no causamos que System. Sin embargo el proceso finalize() puede.gc() sea llamado. cuando termina con el número 47. se verá que solo si System.variable i de tipo int que indica que número es. new String("To take up space"). dado que no hay nada dentro del bucle que cambie el valor de Chair. pero solo si explícitamente se fuerza a que suceda.gc() es llamado luego que todos los objetos son creados y descartados serán todos los finalize() llamados. Cuando el objeto Chair numero 47 es finalizado. Esto es otra pista de que los diseñadores de Java se están dando una paliza para solucionar el problema de recolección de basura y finalización. El argumento “gc” llamará a el método System.

Así es que parece que finalize() es solo para incomprensibles limpiezas de memoria que la mayoría de los programadores nunca utilizaran. hay un uso muy interesante de finalize() en el que no hay que confiarse en que sea llamado cada vez. El valor de finalize() es que puede ser utilizado para descubrir esta condición. En la máquina virtual de Java (JVM) no esta cerca de quedarse sin memoria. 180 Pensando en Java www. no se podrá confiar en que finalize() sea llamado. finalizará y destruirá todos los objetos que no se utilizan mas en ese momento. Por ejemplo. entonces se tendrá un error en el programa que será muy difícil de encontrar. class Book { boolean checkedOut = false.gc() es llamado. Si system. no todos los finalizadores son llamado en el momento que el programa se completa. La condición de muerto En general.com . Si algunas partes del objeto no son limpiadas adecuadamente. entonces se descubrirá el problema. y se debe crear funciones de limpieza separadas y llamarlas explícitamente. Book(boolean checkOut) { 3 Término acuñado por Bill Venners (www. Hay aquí un simple ejemplo de como se debe utilizar: //: c04:DeathCondition. creation After all Chairs have total created = 3881.java // Using finalize() to detect an object that // hasn't been properly cleaned up. Sin embargo.BruceEckel.com) durante un seminario que dimos juntos. Esto es la verificación de condición de muerto3 de un objeto. Si una de las finalizaciones sucede para revelar el error. entones no desperdiciará (sabiamente) tiempo recuperando memoria a través del recolección de basura. si el objeto representa un fichero abierto. que es lo que realmente importa. bye! after 3486 Chairs have been Setting flag to stop Chair been created: total finalized = 2684 De esta forma. incluso si no es llamado siempre.Created 47 Beginning to finalize created Finalizing Chair #47. Se debe recordar que ni la recolección de basura ni la finalización es garantida. ese fichero debe ser serrado por el programador antes que el objeto sea recogido por el basurero. En el momento que no estamos mas interesados en un objeto-cuando esta listo para ser limpiado-ese objeto estará en un estado donde su memoria puede ser liberada de forma segura.artima.

Sin finalize() para verificar la condición de muerto. podemos pensar que el heap en C++ es un patio donde cada objeto vigila fu propio pedazo de pasto. Como funciona el recolector de basura Si se esta acostumbrado a un lenguaje de programación donde ubicar objetos en el heap es costoso. Esto puede sonar un poco curioso al principio-que la liberación de espacio afecte el asignar espacio-pero es la forma en que algunos JVMs trabajan y esto significa que asignar espacio para objetos en el heap en Java puede ser apenas mas rápido que crear espacio en la pila en otros lenguajes. Pero aun si no es muy probable que el Book errante sea eventualmente descubierto mediante repetidas ejecuciones del programa (asumiendo que el programa asigna suficiente espacio para causar una recolección de basura). el recolector de basura puede tener un impacto en el aumento de la velocidad de la creación de objetos. // Force garbage collection & finalization: System. es un error muy difícil de encontrar. forget to clean up: new Book(true). Este estado real puede comenzar a 181 . // Drop the reference.println("Error: checked out"). } } public class DeathCondition { public static void main(String[] args) { Book novel = new Book(true). Por ejemplo. Sin embargo.gc() es utilizado para forzar la finalización (y se debe hacer esto durante el desarrollo del programa para acelerar la depuración).out. } } ///:~ La condición de muerto es cuando todos los objetos Book han sido registrados luego de que son recolectados.gc(). pero en main() un error del programador no verifica en uno de los libros. // Proper cleanup: novel. Debe notarse que System. } void checkIn() { checkedOut = false. } public void finalize() { if(checkedOut) System. se debe asumir naturalmente que el esquema de Java de ubicar todo (excepto primitivas) en el heap es costoso.checkIn().checkedOut = checkOut.

se necesita tener una pequeña mejor idea de la forma en que los distintos recolectores de basura trabajan. y si se trata de esa forma.BruceEckel. si se comienza en la pila y en el área de almacenamiento estático y se camina a través de las referencia se encontrarán todos los objetos vivos. es mas como una cinta transportadora que mueve hacia adelante cada ves que ubica un nuevo objeto. hay un costo operativo extra para arreglar cuentas pero no es nada como realizar una búsqueda para asignar espacio). se el contador de referencias es disminuido. está basada en la idea de que cualquier objeto no muerto debe ser al fin de cuentas ser fácil de seguir para atrás hasta encontrar una referencia que viva en la pila o en una asignación estática. Encontrar grupos que tengan referencias automáticas requiere trabajo extra para el recolector de basura. Para entender como trabaja esto. el heap de Java es un poco diferente. En algunas JVMs. y cada vez que una referencia es ligada a un objeto el contador de referencia es incrementado. El puntero del heap es simplemente movido adelante en un territorio virgen. El recolector de basura arregla las cosas y hace posible con altas velocidades. Para cada referencia que se encuentre. El truco es que mientras que el recolector de basura interviene y se compactan los objetos en el heap así que efectivamente se esta moviendo el puntero del heap mas cerca del comienzo de la cinta transportadora y lejos de una violación de paginación. un modelo de heap infinitamente libre para ser utilizado para asignar memoria. Esto significa que cada objeto contiene un contador de referencias. Esto significa que el espacio asignado es notablemente rápido. la recolección de basura no esta basada en contadores de referencias. buscando dentro de los objetos a 182 Pensando en Java www. En esquemas mas rápidos. así es que efectivamente es lo mismo que la asignación de espacio en el stack de C++ (Claro que. Cada vez que una referencia cae fuera del alcance o es puesta a null.ser abandonado un tiempo mas tarde y puede ser reutilizado. La recolección de basura se mueve a través de la lista completa de objetos y cuando encuentra uno con un contador de referencia en cero libera la memoria. debe trazar dentro de el objeto que apunta y seguir todas las referencias de ese objeto. El inconveniente es que si los objetos se refieren de forma circular a cada uno de los otros estos pueden tener contadores de referencia que no sean cero aún siendo basura. En lugar de eso. eventualmente se paginará un montón de memoria (que es un gran acierto de rendimiento) y luego se acabará. La cadena puede ir a través de varias capas de objetos. Este manejo de contadores de referencias es un pequeño pero constante costo que se sucede a lo largo del tiempo de vida del programa. Ahora se observa que el heap no es una cinta transportadora. Una técnica simple pero lenta es la de conteo de referencias. Esto.com . Los contadores de referencia son comúnmente utilizados para explicar un tipo de recolección de basura pero no parece ser utilizado en ninguna implementación de JVM.

Una de estas variantes es stop-andcopy. cada objeto vivo que es encontrado es copiado de un heap a otro. esa referencia) objeto debe ser cambiada. Solo cuando el proceso de marca es terminado es cuando la barrida ocurre. un recolector que copie puede seguir copiando la memoria de un lugar a otro. pero puede haber otras referencias que apuntan a este objeto que serán encontradas mas tarde durante la “marcha”. Hay aquí dos temas que hace estos “recolectores de copias” ineficientes. Cada objeto por el que se mueve debe estar vivo.e. y con consiguiente son automáticamente recolectados. A pesar de eso. la JVM utiliza un esquema de recolección de basura adaptable y lo que hace con los objetos es localizarlos dependiendo . Se puede ver que no hay problemas con grupos desprendidos auto referenciados-estos simplemente no se encuentran. hasta que se haya movido a través de toda la telaraña generada con las referencias a la pila o almacenamiento estático. los objetos muertos son liberados. Algunos JVMs negocian con esto asignando el heap en trozos mientras se necesita y simplemente copiando de un trozo a otro. Este sigue la misma lógica de comenzar desde la pila y del almacenamiento estático y rastrear a través de todas las referencias para encontrar objetos vivos. algunos JVMs detectan que no hay basura que se este generando y cambian a otro esquema diferente (esta es la parte que se “adapta”). En la estrategia descrita aquí. como los objetos son copiados dentro del nuevo heap son empaquetados final con final. El primero es la idea de que se tienen dos heaps y se quita de un lugar toda la memoria adelante y atrás entre estos dos heap separados. cada ves que se encuentra un objeto vivo es marcado para colocar una bandera en el. El segundo tema es la copia. Entonces. todas las referencias que apuntan a el (i. pero cuando se sabe que estamos generando poco o nada de basura es rápido. Para uso general. lo que es un despilfarro. de la variante utilizada en ese momento. Sin embargo. Esto significa que-por razones que se hacen evidentes mas adelante-el programa es primero detenido (esto no es un esquema recolección en segundo plano). Este otro esquema es llamado mark and sweep y es lo que las primeras versiones de . Durante la barrida. no 183 . Para prevenir esto. La referencia que va desde el heap o desde el área de almacenamiento estático a el objeto pueden ser cambiadas correctamente. Estas son corregidas cuando son encontradas (imaginemos una tabla que relaciona las viejas direcciones con las nuevas). mark and sweep es bastante lento. pero el objeto no es recolectado todavía. esto es compactados el nuevo heap (y permitiendo almacenar espacio simplemente juntándolo al final como fue descrito anteriormente).los cuales apunta. dejando atrás toda la basura. JVM de Sun utilizaban todo el tiempo. Sin embargo. Una vez que el programa comienza a estabilizarse puede generar poco o nada de basura. Claro que. Además. etc. manteniendo mucha mas memoria de la que realmente se necesita. cuando un objeto es movido de un lugar a toro.

En ese momento. siquiera en las primeras versiones de JVM de Sun. Cuando una clase debe ser cargada (típicamente. así es que como para resumir. la primera vez que se quiere crear un objeto de esa clase). solo los bloques creados desde la última recolección de basura son compactados. el fichero . y se incrementa el tamaño del ejecutable (los códigos de bytes son significativamente mas compactos que el código JIT) y esto puede causar paginado. Esto es donde la parte que se “adapta” entra. lo que definitivamente hace el programa mas lento. todos los demás bloques adquieren su contador de generación si tienen están referenciados desde algún lado. pero resulta que la recolección de basura no esta implementada de esta forma. Con bloques. una estrategia es simplemente compilar JIT todo. en lugar de esto. el programa es detenido cuando la recolección de basura se sucede. mark-and-sweep requiere que el programa sea detenido.sucede ninguna copia. El la literatura de Sun se puede encontrar muchas referencias a la recolección de basura como un proceso en segundo plano con prioridad baja. en la JVM descrita aquí la memoria es asignada en brandes bloques. Si se ubica un objeto grande. Esto maneja los casos normales de una gran cantidad de objetos temporales de vida corta. que combinado a través de la vida del programa se puede sumar. En un caso normal. la JVM mantiene un rastro de que tan exitoso es mark-and-sweep y si el heap comienza a estar fragmentado regresa a stop-and-copy. así si el recolector elige compactar un heap fragmentado deshaciéndose de los objetos dispersos. Una 184 Pensando en Java www. Cada bloque tiene un contador de generaciónpara mantener la pista de si esta vivo. “generación que se adapta stop-and-copy markand-sweep”.class es localizado y el código de bytes para esa clase es traído a memoria. Una especialmente importante involucra la operación del cargador y el compilador Just-In-Time (JIT). Un riguroso stop-and-copy se requiere para copiar todo objeto vivo del heap fuente a el nuevo heap después de que se pueda liberar el viejo. Periódicamente. lo que traslada un montón de memoria. La “stop-and-copy” se refiere a la idea de que este tipo de recolección no se realiza en segundo plano. La JVM realiza un monitoreo de la eficiencia del recolector de basura y si comienza a gastar mucho tiempo porque todos los objetos tienen vidas largas se cambia a mark-and-sweep. pero esto tiene dos inconvenientes: toma un poco mas de tiempo. En lugar de eso el recolector de basura de Sun se ejecuta cuando la memoria esta baja.BruceEckel.com . un barrido total es hecho-los objetos grandes no se copian a pesar de todo (solo obtienen su contador de generación) y los bloques que contienen objetos pequeños son copiados y compactados. tendrá su propio bloque. el recolector de basura puede utilizar bloques muertos para copiar objetos que recolecta. De forma similar. Hay un gran número de mejoras en la velocidad posibles en JVM. Como ha sido mencionado. Además.

tal vez no sea práctico forzar a el usuario a inicializarlo a un valor apropiado antes de que el dato se utilizado. Así es que se escribe: void f() { int i.out. class Measurement { boolean t. byte b. esta garantía se presenta de la forma de un error en tiempo de ejecución. que nunca es ejecutado puede nunca ser compilado JIT. significa que el código no es compilado JIT hasta que es necesario. En el caso de las variables que son definidas localmente para un método.estrategia alternativa es una lazy evaluation (evaluación perezosa)que . Sin embargo. float f. las cosas son un poco diferentes. Este código. pero es mas probable que sea un error del programador y un valor por defecto lo encubriría.println( "Valor inicial de los tipo de datos\n" + "boolean " + t + "\n" + "char [" + c + "] "+ (int)c +"\n"+ "byte " + b + "\n" + "short " + s + "\n" + "int " + i + "\n" + "long " + l + "\n" + "float " + f + "\n" + 185 . Inicialización de miembros Java deja de utilizar esta forma para garantizar que las variables sean inicializadas antes de ser utilizadas. Dado que cualquier método puede inicializar o utilizar ese dato. char c. no es seguro dejarlo con valor de basura. short s. Esos valores se pueden entender aquí: //: c04:InitialValues. i++. Forzando al programador a proporcionar un valor de inicialización hace que sea mas probable de encontrar el error. int i. el compilador puede darle a i un valor por defecto. double d. así es que se garantiza que cada dato miembro primitivo de una clase tenga un valor inicial. si una primitiva es un dato miembro de una clase.. long l. } se obtendrá un mensaje de error que indica que i puede no haber sido inicializada. Claro.java // Muestra los valores iniciales por defecto. void print() { System. Sin embargo.

int i = 999. short s = 0xff. /* En este caso se puede también decir: new Measurement(). .com . //.BruceEckel. double d = 3.print().0 El valor de char es cero. long l = 1. */ } } ///:~ La salida de este programa es: Valor inicial de los tipo de datos boolean false char [ ] 0 byte 0 short 0 int 0 long 0 float 0. 186 Pensando en Java www.14f. automáticamente son inicializados. d."double " + d). byte B = 47. Aquí las definiciones de campos en la clase Measurement son cambiados para proporcional valores iniciales: class Measurement { boolean b = true. lo que es impreso como un espacio.print(). float f = 3. Así es que al menos aquí no hay amenaza de trabajar con variables sin iniciar.14159. . Inicialización específica ¿Que sucede si se quiere dar un valor inicial a una variable? Una forma directa de hacer esto es simplemente asignar el valor en el momento en que se define la variable en la clase (Obsérvese que esto no se puede hacer en C++. char c = 'x'. Se puede ver que a pesar que los valores no son especificados. Se verá mas adelante que cuando se define una referencia a un objeto dentro de una clase sin inicializar a un nuevo objeto. a pesar de que todos los principiantes de C++ lo intentan siempre).0 double 0. } } public class InitialValues { public static void main(String[] args) { Measurement d = new Measurement(). esa referencia se le da un valor especial null (que es una palabra clave de Java).

//. y esto nos da una gran flexibilidad es la programación dado que podemos llamar métodos y realizar acciones en tiempo de ejecución para determinar valores iniciales. //. Tiene la limitación de que cada objeto del tipo Measurement tendrá el mismo valor de inicialización. Inicialización en constructor El constructor puede ser utilizado para realizar inicializaciones. que se sucede luego de que se entra en el constructor. // . Esta estrategia de inicialización es simple y directa. . pero estos argumentos no pueden ser otra clase de miembros que no hayan sido inicializados todavía. Si no se da a o un valor inicial y se intenta utilizar igualmente. } Este es un lugar donde el compilador. //. int j = g(i). se puede insertar una variable e inicializarla si se quiere: class Measurement { Depth o = new Depth(). Así es que.También se puede inicializar objetos no primitivos de la misma forma. También se puede llamar a un método para proporcionar un valor de inicialización: class CInit { int i = f(). Esto es algo para tener en mente. De esta forma. int i = f(). sin embargo: no se puede evitar la inicialización automática. se puede hacer esto: class CInit { int i = f().. por ejemplo: 187 . por su puesto.. Si Depth es una clase. } Este método puede tener argumentos. apropiadamente se queja de una referencia inversa. dado que cuanta con el orden de inicialización y no con la forma en la cual el programa es compilado. boolean b = true.. se obtendrá un error en tiempo de ejecución llamado excepción (Cubierto en el capítulo 10). ... A veces esto es exactamente lo que se necesita. pero en otros momentos se necesita mas flexibilidad. } Pero no se puede hacer esto: class CInit { int j = g(i)..

y es forzado para los objetos. // At end } public class OrderOfInitialization { public static void main(String[] args) { Card t = new Card(). incluyendo aquellos a los que se les da una inicialización explícita en el momento de la definición.out. // After constructor void f() { System. pero las variables son inicializadas antes que cualquier método sea llamado-incluso el constructor. . Orden de inicialización Dentro de una clase. .java // Demuestra el orden de inicialización. Counter() { i = 7.println("f()").println("Tag(" + marker + ")"). 2 da edición (disponible en este CD ROM y en www. // Before constructor Card() { // Indicate we're in the constructor: System. o antes de ser utilizado-la inicialización ya esta garantida 4. luego a 7.out.f().out.class Counter { int i. Esto es cierto con todos los tipos primitivos y las referencias a objetos. C++ tiene la lista de inicialización de constructor que hace que la inicialización se suceda entes de entrar en el cuerpo del constructor. el orden de inicialización esta determinado por el orden en que las variables están definidas dentro de la clase.println("Card()"). t3 = new Tag(33). // Cuando el constructor es llamado a crear el // objeto Tag. el compilador no trata de forzar la inicialización de elementos en el constructor en ningún lugar en particular. entonces i primero será inicializado a 0. // Shows that construction is done } 4 En contraste.com). Por ejemplo: //: c04:OrderOfInitialization. } // . se verá un mensaje: class Tag { Tag(int marker) { System.BruceEckel. Por esta razón. 188 Pensando en Java www.BruceEckel. // Reinitialize t3 } Tag t2 = new Tag(2). Vea Thinking in C++. } Tag t3 = new Tag(3). } } class Card { Tag t1 = new Tag(1).com . t. Las definiciones de variables pueden ser desparramados a través y entre las definiciones de métodos.

pero garantiza la inicialización correcta-¿Que sucedería si se definiera un constructor sobrecargado donde no se inicializa t3 y donde no hay una inicialización por “defecto” para t3 en su definición? Inicialización de datos estáticos Cuando el dato es del tipo static la misma cosa sucede. Pero la pregunta que surge es cuando el espacio asignado es inicializado. sin importar de cuantos objetos son creados.out.println("Mesa()"). toma el valor null hasta que se crea un nuevo objeto y la referencia se conecta a el. se verá igual que para los tipos no estáticos. toma el valor inicial estándar para la primitiva. t3 es inicializada nuevamente dentro del constructor. Si es una referencia a un objeto. así puede ser recolectado mas tarde). Esto parece no ser muy eficiente al principio. Un ejemplo aclara esta pregunta: //: c04:StaticInitialization. la referencia t3 es inicializada dos veces. } void f(int marker) { System. Además. las definiciones de los objetos Tag son intencionalmente desparramadas para probar que no son inicializadas después de que se entra al constructor o cualquier cosa se suceda. si es una primitiva y no se inicializa. Hay una sola lugar para los datos estáticos.println("f2(" + marker + ")"). 189 . b2. class Bowl { Bowl(int marker) { System. La salida es: Tag(1) Tag(2) Tag(3) Card() Tag(33) f() De esta forma. una antes y una después de la llamada al constructor (El primer objeto es descartado. Table() { System. } void f2(int marker) { System.out.println("Taza(" + marker + ")").out.println("f(" + marker + ")").java // Especificando valores iniciales en una // definición de clase. Si queremos colocar la inicialización en el punto de la definición.} ///:~ En Card.f(1). } } class Table { static Bowl b1 = new Bowl(1).out.

f(2). Se puede ver que Cupboard crea un Bowl no estático b3 antes de las definiciones estáticas. Cupboard() { System. new Cupboard().out. b4.println("f3(" + marker + ")").out. t3.println( "Creando un nuevo aparador en el principal"). La salida muestra que sucede: Taza(1) Taza(2) Mesa() f(1) Taza(4) Taza(5) Taza(3) Aparador f(2) Creando un nuevo aparador en el principal Taza(3) Aparador f(2) Creando un nuevo aparador en el principal Taza(3) Aparador f(2) f2(1) f3(1) 190 Pensando en Java www.BruceEckel. } static Table t2 = new Table(). static Bowl b4 = new Bowl(4). System. y Table y Cupboard crea miembros estáticos de Bowl desparramados entre las definiciones de clase. } public class StaticInitialization { public static void main(String[] args) { System. new Cupboard().println( "Creando un nuevo aparador en el principal").println("Aparador"). t2. } static Bowl b5 = new Bowl(5).f2(1).out. static Cupboard t3 = new Cupboard(). } void f3(int marker) { System.out.} static Bowl b2 = new Bowl(2).f3(1).com . } ///:~ Bowl permite ver la creación de la clase. } class Cupboard { Bowl b3 = new Bowl(3).

191 .b1 o a Table. si ya fueron definidos por un objeto creado previamente. lo que hace buscando a través de la ruta a las clases. Sin embargo. Por orden de inicialización los estáticos son primero. 3. static { i = 47. el proceso de construcción para un objeto Dog primero asigna la memoria suficiente para un objeto Dog en el heap. Si no se crea un objeto Table y nunca se hace referencia a Table. Considerando una clase llamada Dog: 1. cuando el objeto Class es leído por primera vez. o la primera vez que un método estático o campo estático de una clase Dog es accedido. Inicialización estática explícita. Java permite agrupar otras inicializaciones estáticas dentro de una “cláusula de construcción estática” (algunas veces llamada bloque estático) en una clase. Esta se ve de la siguiente forma: class Spoon { static int i. son inicializadas solo cuando el primer objeto Table es creado (o el primer acceso estático ocurre). Como se podrá ver en el Capítulo 6. De esta forma.La inicialización estática ocurre solo si en necesaria. 4.b2. Cuando se crea una clase Dog mediante la palabra clave new . automáticamente se configuran todas las primitivas en el objeto Dog a sus valores por defecto (cero para los números y sus equivalentes para boolean y char) y las referencias a null. el objeto estático Bowl b1 y b2 nunca serán creados. y segundo los objetos que no son estáticos. Es útil realizar un resumen del proceso de creación de un objeto.class. el intérprete Java debe localizar el fichero Dog. esto puede actualmente involucrar mucha actividad. 2. todos los inicializadores estáticos son ejecutados. especialmente cuando la herencia esta involucrada. 6. La primera vez que un objeto del tipo Dog es creado. Los constructores son ejecutados. la inicialización de los datos estáticos se realiza solo una vez. Las inicializaciones que ocurren en el punto de definición de campos son ejecutadas.class es leída (creando un objeto Class. del que se aprenderá mas tarde). Se puede ver la evidencia de esto en la salida. 5. los objetos estáticos no son iniciados nuevamente. Inmediatamente que la clase Dog. Luego se eso. Este almacenamiento es limpiado a cero.

Por ejemplo: //: c04:ExplicitStatic. es ejecutado solo una vez.out. } } class Cups { static Cup c1. } } public class ExplicitStatic { public static void main(String[] args) { System.f(99). // (2) } ///:~ Los inicializadores estáticos para Cups se ejecutan cuando ocurre un acceso a el objeto c1 ocurre en la línea marcada (1). class Cup { Cup(int marker) { System.c1. c2 = new Cup(2).} // .println("f(" + marker + ")"). // (1) } // static Cups x = new Cups(). static { c1 = new Cup(1).java // Java "Instance Initialization. como otras inicializaciones estáticas.out. la inicialización estática para Cups se sucede solo una vez. . . la primera vez que creamos un objeto de la clase o la primer vez que tiene acceso a un miembro estático de esa clase (aún si nunca creo un objeto de esa clase). la inicialización estática para Cups nunca ocurre. no importa si una de ambas líneas marcadas (2) se les quita el comentario. } void f(int marker) { System. He aquí un ejemplo: //: c04:Mugs.println("Copas").out. También. static Cup c2.println("Dentro del principal()"). Este código. o si la línea (1) es comentada. // (2) // static Cups y = new Cups(). Instancia de inicialización no estática Java proporciona una sintaxis similar para inicializar variables no estáticas para cada objeto.BruceEckel. Esto parece ser un método." class Mug { 192 Pensando en Java www.println("Copa(" + marker + ")").com . pero es solo una palabra clave static seguida por un cuerpo de método. } Cups() { System. Cups.java // Explicit static initialization // with the "static" clause.out.

out. } } public class Mugs { Mug c1. c2 = new Mug(2). c2 = new Mug(2).out.out. Inicialización de arreglos La inicialización de arreglos en C es propensa a errores y tediosa. 2 da edición por una completa descripción de aggregate initialization. System. Los arreglos son definidos y utilizados con paréntesis cuadrados [] llamados operador de indexación Para definir un arreglo simplemente colocamos a . System. tiene arreglos y estos son soportados con inicialización de arreglos. C++ usa aggregate initi lization para hacerla mucho mas segura 5. } que se ve exactamente como una cláusula de inicialización estática exceptuando por la palabra clave static perdida.println("Mug(" + marker + ")"). Esta sintaxis es necesaria para respaldar la inicialización de las clases anónimas interiores(vea Capítulo 8). 193 . 5 Véase Thinking in C++. todos del mismo tipo y empaquetados juntos bajo un único nombre que lo identifica. { c1 = new Mug(1). dado que todo es un objeto en Java.out.println("c1 & c2 initialized"). } public static void main(String[] args) { System.Mug(int marker) { System. continuación del nombre de tipo unos paréntesis cuadrados. Mug c2.out. Java no tiene a “conglomerados” como C++. Mugs x = new Mugs(). } void f(int marker) { System. } Mugs() { System.println("f(" + marker + ")").println("Adentro del método principal").println("Mugs()"). } } ///:~ Se puede ver en la cláusula de inicialización: { c1 = new Mug(1). Un arreglo es simplemente una secuencia de objetos o primitivas.out.println("c1 & c2 initialized").

El C y C++. Para los arreglos. como se demuestra aquí: //: c04:Arrays. pero se puede también utilizar un tipo especial de expresión de inicialización que debe ocurrir en el momento en que el arreglo es creado. int[] a2. } } ///:~ Se puede ver que a1 se le da un valor de inicialización y a a 2 no. 2. sin embargo. 5 }. 4.out. 2. a otro arreglo.length.length. for(int i = 0. Se puede colocar los paréntesis cuadrados luego del identificador para producir exactamente el mismo significado: int a1[]. es posible asignar un arreglo a otro en Java. 4. es probablemente una sintaxis mas sensible. a2 = a1. Esto se adapta a lo esperado por los programadores de . Del espacio asignado (el equivalente a utilizar new ) en este caso se ocupa el compilador.com . Esta inicialización especial es un grupo de valores rodeados por llaves. a 2 es asignado mas tarde-en este caso. public class Arrays { public static void main(String[] args) { int[] a1 = { 1.java // Arrays of primitives. por que siempre se define una referencia a un arreglo sin un arreglo? int[] a2. dado que el tipo es un “arreglo de enteros”.int[] a1. Lo que se esta haciendo realmente es copiando una referencia. la inicialización pueden aparecer en cualquier lugar del código. Esto nos lleva nuevamente al tema de las “referencias”. 3. Todo lo que tenemos en este punto es una referencia a un arreglo. El estilo anterior. Hay algo nuevo aquí: todos los arreglos tienen un miembro intrínsico (tanto si son arreglos de objetos o arreglos de primitivas) al que puede escrutar- 194 Pensando en Java www. Bueno. El compilador no permite indicar que tan grande el arreglo es. Este estilo será el utilizado en este libro. y no hay espacio asignado para el arreglo.BruceEckel. i++) a2[i]++. i < a2. 5 }. for(int i = 0. ¿Entonces. i++) System. 3.println( "a1[" + i + "] = " + a1[i]). Por ejemplo: int[] a1 = { 1. i < a1. así es que se puede decir: a2 = a1. Para crear espacio de almacenamiento para el arreglo debemos escribir una expresión de inicialización.

} } ///:~ Puesto que el tamaño del arreglo es elegido de forma aleatoria (utilizando el método pRand()). static int pRand(int mod) { return Math.println( "a[" + i + "] = " + a[i]). Aquí. i++) System. comienzan desde el elemento cero. Dado que los arreglos en Java. } public static void main(String[] args) { int[] a. Claro. System. a = new int[pRand(20)]. verificar cada arreglo tiene un costo en tiempo y en código y no hay forma de evitarlo. 195 . Int[] a = new int[pRand(20)]. Si se excede de esos tangos. y para tipos boolean. lo que es fuente de los mas infames errores. Java protege contra este tipo de problemas causando un error en tiempo de ejecución (una excepción.nextInt()) % mod + 1. i < a.*. new trabaja de la misma forma si se esta creando un arreglo de primitivas (new no creará un algo que no sea un arreglo de primitivas). import java. C y C++ tranquilamente aceptan esto y permiten pasar por arriba toda la memoria.length).java // Creating arrays with new. ¿Que sucede si no se conoce cuantos elementos necesitará en su arreglo cuando se esta escribiendo el programa? Simplemente se usa new para crear los elementos del arreglo.println( "length of a = " + a. el arreglo puede también ser definido e inicializado en la misma instrucción.length. como en C y C++. Claro que. el tema del Capítulo 10) si se sale de los límites. el elemento mas grande que puede indexar es length . Este miembro es length. Los diseñadores de Java pensaron que valía la pena renunciar a una mejor eficiencia a cambio de seguridad en la Internet y una mejora en la productividad a la hora de programar. es claro que la creación del arreglo sucede actualmente en tiempo de ejecución. Además. lo que significa que el acceso a arreglos puede ser una fuente de ineficiencia en un programa si ocurre en una coyuntura crítica.out.1 . esto es cero.out.abs(rand. for(int i = 0. Sin embargo. es false).util. se podrá ver en la salida del programa que un arreglo de elementos de tipos primitivos son automáticamente inicializados a valores “vacíos” (para números y char. //: c04:ArrayNew. public class ArrayNew { static Random rand = new Random().pero no cambiar-para averiguar cuantos elementos hay en el arreglo.

nextInt()) % mod + 1.println( "a[" + i + "] = " + a[i]). Obsérvese en la formación del objeto String dentro de la instrucción para imprimir. } } } ///:~ Aquí. Hay dos formas: //: c04:ArrayInit. se obtiene un solo arreglo de referencias. Considerando el tipo envoltura Integer. i++) { a[i] = new Integer(pRand(500)).length. System. i < a. se tendrá una excepción en tiempo de ejecución cuando se intente leer un índice en un arreglo vacío. public class ArrayInit { public static void main(String[] args) { Integer[] a = { new Integer(1).*. a[i] = new Integer(pRand(500)). Integer[] b = new Integer[] { 196 Pensando en Java www. lo que es una clase y no una primitiva. sin embargo.java // Array initialization. Si se olvida crear el objeto. y hasta que la referencia por si sola no sea inicializada para crear un nuevo objeto Integer no tendremos una inicialización completa.println( "length of a = " + a. public class ArrayClassObj { static Random rand = new Random(). Es posible también inicializar arreglos de objetos utilizando llaves que encierren una lista. System.out.java // Creating an array of nonprimitive objects.Si estamos tratando con un arreglo de objetos de tipos que no son primitivos. for(int i = 0. }. siempre que se llama a new para crear el arreglo: Integer[] a = new Integer[pRand(20)]. el tema de la referencia surge nuevamente porque lo que se esta creando es un arreglo de referencias.length).abs(rand. import java. //: c04:ArrayClassObj. } public static void main(String[] args) { Integer[] a = new Integer[pRand(20)]. new Integer(2).out. Aquí. new Integer(3). Se puede ver que la referencia al objeto Integer es convertida automáticamente para producir una representación del valor del String dentro del objeto.BruceEckel.util. static int pRand(int mod) { return Math.com . debemos siempre utilizar new .

println(x[i]). 197 . } public class VarArgs { static void f(Object[] x) { for(int i = 0. La coma final en la lista de inicializadores es opcional (Esta hace mas fácil de mantener largas listas). La segunda forma para inicializar arreglos proporciona una sintaxis conveniente para crear y llamar métodos que pueden producir el mismo efecto que una lista de argumentos de variablesde C (conocidas como "varagrs" en C). f(new Object[] {"one". new Double(11. new Float(3. Arreglos multidimencionales Java permite crear fácilmente arreglos multidimensionales: //: c04:MultiDimArray. "two".14). new A().11) }). se aprenderá como descubrir el tipo exacto del objeto así se puede hacer algo interesante con ellos.util.*. En el Capítulo 12 que cubre la identificación de tipo en tiempo de ejecución(RTTI run-time type identification).java // Using the array syntax to create // variable argument lists.java // Creating multidimensional arrays. se puede crear un método que tome un arreglos de tipos Object y se llame de esta forma: //: c04:VarArgs. Estas pueden incluir cantidades de argumentos al igual que tipos desconocidos. pero es mas limitado dado que el tamaño del arreglo es determinado en tiempo de compilación.length. i < x. } public static void main(String[] args) { f(new Object[] { new Integer(47). import java. y este programa utiliza conversión automática del tipo String para hacer algo útil con cada Object. class A { int i. i++) System. } } ///:~ Esto es útil a veces. new A() }).out. "three" }). }. f(new Object[] {new A(). new Integer(2). new VarArgs(). new Integer(3).new Integer(1). Dado que todas las clases son al fin de cuentas heredadas de una clase base común Object (un tema que se aprenderá cuando este libro progrese). } } ///:~ En este punto no hay mucho que se pueda hacer con estos objetos desconocidos.

// Array of nonprimitive objects: Integer[][] a4 = { { new Integer(1). { new Integer(5). j++) for(int k = 0. // 3-D array with fixed length: int[][][] a2 = new int[2][2][4].abs(rand.public class MultiDimArray { static Random rand = new Random(). j < a5[i]. 3. new Integer(2)}.length. new Integer(4)}.length. k++) prt("a2[" + i + "][" + j + "][" + k + "] = " + a2[i][j][k]). 6. { new Integer(3). i < a3. i < a2.println(s). i < a4. i++) for(int j = 0. for(int i = 0.length. }. for(int i = 0. static int pRand(int mod) { return Math. k < a2[i][j]. Integer[][] a5.length. j++) for(int k = 0.com . j < a3[i]. j < a2[i]. i < a3. j++) prt("a1[" + i + "][" + j + "] = " + a1[i][j]). i < a5. for(int i = 0. { 4. i++) for(int j = 0.length. }.length. for(int i = 0. new Integer(6)}. i < a1.length. j++) 198 Pensando en Java www.length.length. i++) for(int j = 0. } public static void main(String[] args) { int[][] a1 = { { 1. k++) prt("a3[" + i + "][" + j + "][" + k + "] = " + a3[i][j][k]). i++) for(int j = 0. k < a3[i][j]. }. i++) { a5[i] = new Integer[3]. 2. } static void prt(String s) { System.length. j < a3[i].length.BruceEckel. for(int i = 0.length. } for(int i = 0. j < a4[i]. a5 = new Integer[3][].length. for(int j = 0.nextInt()) % mod + 1. for(int j = 0. j < a1[i]. j++) prt("a4[" + i + "][" + j + "] = " + a4[i][j]). // 3-D array with varied-length vectors: int[][][] a3 = new int[pRand(7)][][]. j++) a3[i][j] = new int[pRand(5)].out. 5. i++) { a3[i] = new int[pRand(5)][]. }.length.

i++) for(int j = 0. j++) a3[i][j] = new int[pRand(5)].length.length. }. 2. }. Integer[][] a4 = { { new Integer(1). El tercer ejemplo muestra cada vector en el arreglo que construye la matriz puede ser de cualquier largo: int[][][] a3 = new int[pRand(7)][][].length. j++) prt("a5[" + i + "][" + j + "] = " + a5[i][j]). El primer ejemplo muestra un arreglo multidimensional de primitivas.length. demostrando la habilidad de juntar muchas expresiones new con llaves. } for(int i = 0. j < a5[i]. 5. i < a3. new Integer(6)}. for(int j = 0. }. j < a3[i]. Aquí. i++) { a3[i] = new int[pRand(5)][].a5[i][j] = new Integer(i*j). { 4. Se puede tratar con arreglos de tipos no primitivos en una forma similar. { new Integer(3). { new Integer(5). Cada grupo de paréntesis cuadrados nos posiciona dentro de el siguiente nivel del arreglo. El quinto ejemplo muestra como un arreglo de objetos de tipos no primitivos pueden ser armados pieza por pieza: 199 . int[][][] a2 = new int[2][2][4]. El segundo ejemplo muestra un arreglo tridimensional asignado con new. El segundo new dentro del bucle for completa los elementos pero deja el tercer índice indeterminado hasta que se ejecuta el tercer new . lo que es mostrado en el cuarto ejemplo. new Integer(4)}. }. } } ///:~ El código utilizado para imprimir utiliza length así es que no depende de tamaños de arreglos fijos. 6. } El primer new crea un arreglo con un primer elemento de largo aleatorio y el resto indeterminado. i < a5. for(int i = 0. new Integer(2)}. Se puede ver en la salida que los valores de arreglos son automáticamente inicializados a cero si no se les da un valor de inicialización explícito. 3. el arreglo entero es asignado de una sola vez. Se delimita cada vector en el arreglo con llaves: int[][] a1 = { { 1.

En Java.com . j < a5[i].com . herencia y como afectan a sus constructores en capítulos futuros. y agrega mucha de la seguridad necesitada en el manejo de memoria. el gasto es lo difícil de poner en perspectiva dado el enlentecimiento extra de los intérpretes de Java en el momento en que se escribió esto. i++) { a5[i] = new Integer[3]. Todos estos serán construidos.length. Algunos recolectores de basura pueden a veces limpiar otros recursos como gráficos y manejadores de ficheros. se tiene un completo control y seguridad. el constructor. En particular. j++) a5[i][j] = new Integer(i*j).BruceEckel. 200 Pensando en Java www. Dado que los constructores permiten garantizarla inicialización y limpieza (el compilador no permitirá que un objeto sea creado sin la correspondiente llamada al constructor). debe dar un duro golpe acerca de la importancia crítica apostada en la inicialización en el lenguaje. En C++. Sin embargo. el recolector de basura de Java simplifica enormemente la programación. En casos donde no se necesita un comportamiento como el del destructor. la destrucción es muy importante porque los objetos que son creados con new deben ser explícitamente destruidos. hay actualmente mas del constructor de lo que es mostrado aquí. cuando creamos nuevas clases utilizando herencia y composición la garantía de la construcción también se mantiene.BruceEckel. } El i*j es simplemente para colocar un valor interesante dentro del Integer. y temas similares se aplican a la limpieza inadecuada. y alguna sintaxis adicional es necesaria para soportar esto.Integer[][] a5. así es que el método de limpieza equivalente en Java no es necesario la mayoría del tiempo. una de las primeras observaciones que hizo acerca de la productividad en C fue la impropia inicialización de variables causa una significante porción de los problemas de programación. Se aprenderá acerca de la composición. el recolector de basura agrega un costo de tiempo de ejecución agregado. el recolector de basura automáticamente libera la memoria de todos los objetos. Ejercicios La solución de los ejercicios seleccionados pueden encontrarse en el doc umento electrónico The T h i n k i n g i n J a v a A n n o t a t e d S o l u t i o n G u i d e. Este tipo de errores son difíciles de encontrar. for(int j = 0. a5 = new Integer[3][]. for(int i = 0. Resumen Este mecanismo aparentemente elaborado para la inicialización. i < a5. Como Stroustrup diseño C++.length. disponible por una pequeña propina en www.

Créese una clase llamada Tank que pueda ser llenada y vaciada. Créese una clase llamada Dog con un método sobrecargado llamado baark(). Cree un objeto de esa clase. cree un objeto de su clase. Créese una clase con dos métodos. 7. llame el segundo constructor dentro del primero. Modifíquese el ejercicio 6 para que dos de los métodos sobrecargados tengan dos argumentos (de dos tipos diferentes). Utilizando this. observe cuando los mensajes de inicialización de la llamada al constructor son impresos.. Escríbase un finalize() que verifique esta condición de muerto. Créese una clase con un constructor por defecto (uno que no tome argumentos) que imprima un mensaje. 3. e imprima diferentes tipos de ladridos. 10. pero en orden inverso en relación a cada uno de los otros. 2. y con una condición de muerto que debe ser vaciada cuando el objeto es limpiado. 201 . En el main(). Complete el ejercicio 3 creando objetos para atribuir a las referencias del arreglo. Imprima el arreglo utilizando un bucle for. Créese una clase con dos constructores (sobrecargados). 6. Agregue un constructor sobrecargado al ejercicio 1 que tome un argumento del tipo String e imprima la cadena junto con su mensaje. Escríbase un main() que llame las diferentes versiones. verifíquese los posibles escenarios que pueden ocurrir cuando Tank es utilizado. llámese al segundo método dos veces: la primera ves sin utilizar this y la segunda vez utilizando this. 8. dependiendo de la versión del método sobrecargado se este llamado. 9. y luego cree un objeto de esa clase en el main() para verificar que el constructor por defecto es automáticamente sintetizado. Créese una clase con un método finalize() que imprima un mensajes. etc. Cree una clase sin un constructor. Créese un arreglo de objetos String y asigne una cadena a cada elemento. 12. 11. Créese un arreglo de referencias a objetos de la clase que se ha creado en el ejercicio 2. Explíquese el comportamiento del programa. 13. Verifíquese que esto funcione. En el interior del primer método. Cuando se ejecute el programa. Este método debe ser sobrecargado basándose en varios tipos de datos primitivos. Modifíquese el ejercicio 11 para que el finalize() sea llamado siempre. 5. chillidos. pero no cree los objetos para asignar dentro del arreglo.1. 4. En el main().

o "all". 19. 202 Pensando en Java www. El tamaño del arreglo es determinado por los argumentos del método. Repita el ejercicio 19 para un arreglo tridimensional. Experimente con Garbage. Ahora quite el comentario de la otra línea marcada (2) y verifique que la inicialización estática solo ocurre una vez. y los valores de inicialización son un rango determinado por los valores de comienzo y final que son también argumentos de estos métodos.java y verifique que la cláusula no es llamada. 16.com . y otro que sea inicializado por el constructor. Agréguese un método estático que imprima ambos campos y demuestre que son ambos inicializados antes de ser utilizados. imprímase esos valores para verificar que Java realiza una inicialización por defecto. Créese un segundo método que imprima el arreglo generado por el primer método. Repita el proceso y vea si se detecta cualquier patrón en la salida. Descríbase el uso de esta herramienta (otra mas que la especificada en este libro). 20. 15. Escriba un método que cree e inicialice un arreglo de dos dimensiones de tipo double. 22. Cree una clase con un tipo String que sea inicializado utilizando "instancia de inicialización". Cambie el código para que sea llamado System. Cree una clase con un campo del tipo String que sea inicializado en el punto de la definición.BruceEckel. 17. "finalize".runFinalization() después que System.gc() y observe los resultados. Créese una clase conteniendo un tipo int y un tipo char que no sea inicializado.14. 21. 18. Créese una clase que contenga una referencia no inicializada a una cadena. ¿Cuál es la diferencia entre los dos métodos? Créese una clase con una campo del tipo static String que sea inicializado por el bloque estático. En el main() verifique que los métodos creando e imprimiendo varios tamaños diferentes de arreglos. Comente la línea marcada (1) en ExplicitStatic. Demuestre que esa referencia es inicializada por Java a null.java ejecutando el programa utilizando los argumentos "gc".

y exponer solo lo que se quiera que el cliente programador utilice. dado que puede desbaratar el código del cliente programador. aún si a menudo existe una oposición intuitiva de las personas que programan otros lenguajes (especialmente C) y son utilizados para acceder a todo sin restricción. ¿Pero que si el creador de la librería quiere deshacerse de la vieja implementación y colocar una nueva? Cambiar alguno de esos miembros pueden hacer que el código del cliente programador deje de funcionar. Al final de este capítulo se estará totalmente convencido del valor del control de acceso en Java. Los niveles de control de acceso desde el "mas accesible" al "menos accesible" son public . el creador de la librería debe tener la libertad de realizar modificaciones y mejoras con la certeza de que el código del cliente programador no será afectada por esos cambios. Por ejemplo. La situación inversa es complicada. Esto puede ser alcanzado mediante una convención. Por la otra parte. Esto es exactamente correcto.5: Escondiendo la implementación Una consideración primaria en el diseño orientado a objetos es "separar las cosas que cambian de las cosas que se quedan iguales". y private. ¿En el caso de un miembro de datos. y saber que no necesita escribir nuevamente código si una nueva versión de la librería aparece. Esto es particularmente importante para librerías. protected. De esta forma el creador de la librería esta en un chaleco de fuerza y no puede cambiar nada. Java proporciona especificadores de acceso para permitir al creador de la librería indicar que esta disponible para el cliente programador y que cosa no esta. sin embargo. Del anterior párrafo se puede pensar que. El usuario (cliente programador) de esa librería debe ser capaz de confiarse de la parte que utiliza. el programador de la librería debe estar de acuerdo a no quitar métodos existentes cuando se modifica una clase de la librería. y no significa que sean utilizados directamente por el cliente programador. "friendly" (que no es una palabra clave). como diseñador de una librería se debe mantener todo lo que sea posible como "private". como puede el creador de la librería saber que datos miembros deben ser accedidos por el cliente programador? Esto es también verdad con métodos que solo son parte de la implementación de la una clase. 203 . Para solucionar este problema.

muchos de los ejemplos de este libro han existido en un único fichero y son diseñados para uso local.util. Luego. Paquete: la unidad de librería Un paquete es los que se obtiene cuando se utiliza la palabra clave import para traer una librería entera. como en: import java. se será capaz de entender el significado completo de los especificadores de acceso..util. dado que las clases pueden ser bajadas automáticamente en el proceso de ejecutar un programa. y para 204 Pensando en Java www.BruceEckel.ArrayList. import java. o simplemente se puede decir ArrayList gracias a el import. están disponibles. Con Java en la Internet. ninguna de las otras clases en java. esto puede suceder sin que el usuario lo sepa. se aprenderá como los componentes de una librería son colocados dentro de paquetes. Puesto que. y ser capaz de crear un nombre completo independientemente de las limitaciones de la Internet. Este potencial conflicto de nombres es donde es importante tener un completo control de los espacios de nombres en Java. Un método f() dentro de una clase A que no entra en conflicto con un f() que tiene la misma firma (lista de argumentos) en la clase B. Esto es controlado con la palabra clave package en Java.util. por ejemplo.util.*. la clase ArrayList es en java. Los nombres de todas las clases miembros son encapsulados de los demás. y no hay que molestarse con nombres de paquetes (en este caso el nombre de clase es colocado en el "paquete por defecto"). Esto es naturalmente una opinión. Esto trae la librería de utilidades entera que es parte de la distribución estándar de Java. Se mantiene la pregunta de cuanto de los componentes son reunidos juntos en una unidad consistente de librería.ArrayList (lo cual se puede hacer sin la instrucción import). La razón para importar todo esto es proporcionar un mecanismo para manejar "espacios de nombres".com .El concepto de una librería de componentes y el control de quien puede acceder a los componentes de esa librería no es completo sin embargo. Ahora se puede utilizar ArrayList sin calificarlo. se puede especificar el nombre completo java. Sin embargo. Hasta el momento. y el especificador de acceso son afectados si una clase esta en el mismo paquete o en un paquete separado. Así es que para comenzar este capítulo.util. ¿Pero que sucede con los nombres de clases? Supóngase que se esta creando un a clase stack que está instalada en una máquina que ya tiene una clase stack que ha sido creada por alguien mas.

si hay alguna. pero excluyendo la extensión de fichero . de otra forma el compilador se quejará. Esta no es la forma en que Java trabaja. Si se quiere indicar que todos esos componentes (que están en sus respectivos ficheros . dicho de otra forma. y ellas son las clases de "soporte" para la clase public principal. O. se manifiesta que esta unidad de compilación es parte de una librería llamada mipaquete.java se obtendrá un fichero de salida con exactamente el mismo nombre pero con la extensión . Existen compiladores de código nativo Java que generan un fichero ejecutable que pueda trabajar solo.class partiendo de un pequeño número de ficheros . pero es típico). Puede haber solo una clase public en cada unidad de compilación. e interpretar estos ficheros1 . Cuando se crea un fichero de código fuente para Java. Una librería también es un grupo de estos ficheros clase. 205 . si se está planeando crear librerías o programas en la misma máquina. podrían ser utilizado para el compilador para generar una forma intermedia (usualmente un fichero "obj") que es luego empaquetado junto con otros de su tipo utilizando un linker (para crear un fichero ejecutable) o un librarian (para crear una librería).class para cada clase en el fichero . es comúnmente llamado una unidad de compilación (a veces llamada unidad de interpretación). El resto de las clases en la unidad de compilación.java.class separados) pertenecen a un solo sitio.java y . Si se ha programado con un lenguaje compilado. Cada fichero tiene una clase que es pública (no es obligatorio tener una clase pública. De esta forma se puede terminar con bastantes ficheros . se esta diciendo que el nombre de clase público dentro de esta unidad de compilación esta bajo el paraguas con el nombre 1 No hay nada en Java que fuerce la utilización de un interprete. Cuando se compile un fichero .class.java. se deberá pensar acerca de prevenir conflictos con los nombres de clases. que pueden ser empaquetados y comprimidos dentro de un fichero JAR (utilizando el archivador jar de Java).java). así es que hay un componente por cada fichero. Un programa que trabaje es un montón de ficheros . y dentro la unidad de compilación puede se una clase pública que debe tener el mismo nombre que el fichero (incluyendo las mayúsculas. están ocultas de el mundo fuera del paquete dado que no son public . en el comienzo del fichero (si utiliza una instrucción package. El intérprete de Java es responsable por encontrar. Sin embargo. cargar.java.simplificar este enfoque se usará donde sea posible en el resto del libro. debe estar en la primer línea distinta de un comentario en el fichero). Cada unidad de compilación debe tener un nombre terminado en . Cuando decimos: package mipaquete. hay es donde la palabra clave package aparece.

. alguna de las clases públicas en mipaquete. y el nombre de esta clase debe ser MyClass (incluyendo las mayúsculas): package mipaquete. y las cosas pueden tornarse un poco desordenadas. Esta es una forma que Java recomienda el problema del desorden. Se puede ver que la convención para nombres de paquetes Java es utilizar letras minúsculas en todo el nombre. Esto se logra. . codificando el camino de la ubicación del fichero . la primer parte 206 Pensando en Java www. public class MyClass { // . Ahora. Por ejemplo. deben utilizar la palabra clave import para hacer el nombre o los nombres en mipaquete disponibles. inclusive para las primeras letras de las palabras intermedias.java.mipaquete. Juntar los ficheros del paquete en un único directorio soluciona dos problemas mas. import mypackage.class. La palabra clave import puede hacer esto mucho mas limpio. Esto funciona teniendo en cuenta que lo que las palabras package e import le permiten hacer. es dividir el único espacio de nombres global así no hay conflictos de nombres. pero por convención. se verá mas adelante la otra forma cuando la utilidad jar sea introducida.com . tomar ventaja de la estructura jerárquica del sistema operativo. dado que un paquete nunca realmente está "empaquetado" en un único fichero. la creación de nombres de paquetes únicos. por su importancia.MyClass m = new mipaquete. La alternativa es dar el nombre totalmente calificado: mipaquete. esto es. . Creando nombres de paquetes únicos Se debe observar que.*. una cosa lógica para hacer es colocar todos los ficheros . .class dentro del nombre del paquete. // . como diseñador de una librería. MyClass m = new MyClass(). un paquete puede ser creado por muchos ficheros . no importa cuantas personas se conectan a la Internet y escriben clases en Java.MyClass(). Para prevenir esto. y si alguien utilizar este nombre debe especificar el nombre completo o utilizar la palabra clave import en combinación con mipaquete (utilizando la elección dada previamente). supóngase que el nombre del fichero es MyClass. como se introduce en el Capítulo 2. y encontrar esas clases que pueden estar enterradas en algún parte dentro de la estructura de directorios.BruceEckel. Esto significa que puede ser una y solo una la clase pública en ese fichero.class de un paquete particular dentro de un directorio individual. El compilador fuerza esto. si alguien quiere utilizar MyClass o.

public class Vector { 207 .com .simple. La segunda parte de esta trampa es determinar el nombre del paquete dentro de un directorio en la máquina. Dado que los nombres de la Internet se garantizan son únicos. fue formalmente llevada a mayúsculas en los paquetes Java.class con el correspondiente nombre a la clase que esta tratando de crear (También busca algunos directorios estándares relativos a donde el interprete Java reside). consideremos mi nombre de dominio. edu. El intérprete Java procede de esta forma.simple. Comenzando de esa raíz.del nombre de paquete es el nombre de dominio del creador de la clase a la inversa. Si invertimos esto. así cuando el programa Java se ejecute y necesite cargar el fichero . Si decide publicar código Java vale la pena un pequeño esfuerzo para obtener un nombre de dominio.bruceeckel. si se sigue esta convención se garantiza que el nombre del paquete será único y nunca habrá un conflicto de nombres (Esto es.bar. hasta que pierda el nombre de dominio y lo tome alguien mas que comience a escribir código java con la misma ruta de nombres). Para entender esto. si no se tiene un dominio propio entonces se deberá crear una combinación única (como su nombre y apellido) para crear un nombre de paquete único. o el primer momento en que se accede a un miembro estático de la clase). Puedo además subdividir esto decidiendo que quiero crear una librería llamada simple. Primero.class (lo que se hace de forma dinámica.class reside.java // Creating a package. y a veces por el programa de instalación que instala Java o por alguna herramienta basada en Java de su máquina). Ahí es donde buscará el fichero .baz se convierte en foo\v a r\baz o foo/bar/baz o posiblemente otra cosa dependiendo de sus sistema operativo. (La extensión com. que es bruceeckel.bruceeckel. package com. el intérprete tomara el nombre de paquete y remplazará cada punto con una barra para generar un nombre de ruta partiendo de la raíz en CLASSPATH (así es que el paquete foo. encuentra la variable de ambiente CLASSPATH (la cual es definida por el sistema operativo.. com. en el alguna parte del programa que se necesite crear un objeto de esa clase en particular. Esto es entonces concatenado a las varias entradas de la ruta en CLASSPATH. Claro.bruceeckel establece mi nombre global para la clase. org. así es que terminaré el nombre de mi paquete: package com. debe poder encontrar el directorio donde el fichero . pero esto fue cambiado en Java 2 así es que el nombre del paquete entero es en minúsculas). CLASSPATH contiene uno o mas directorios que son utilizados como raíz para la búsqueda de ficheros . Ahora este nombre de paquete puede ser utilizado como un espacio de nombres paraguas para los siguientes dos ficheros: //: com:bruceeckel:simple:Vector.class. etc.

(Vector. buscando el subdirectorio com\bruceeckel\simple. package com.public Vector() { System.println( "com.out. } } ///:~ Cuando el compilador encuentra una instrucción import. la que es.D:\JAVA\LIB. El segundo fichero se ve en gran medida de la misma forma: //: com:bruceeckel:simple:List.D:\JAVA\LIB.java // Creating a package.. } } ///:~ Cuando se crean paquetes propios.bruceeckel.bruceeckel. Se debe colocar al nombre del fichero JAR en la ruta de clases.util. en mi máquina: CLASSPATH=. Así es que para un JAR llamado grape. puede ver el nombre de paquete com. import com. sin embargo.BruceEckel.C:\DOC\JavaT Se puede ver que CLASSPATH puede contener algunas alternativas de caminos de búsquedas.simple.*. se descubre que la instrucción package debe ser el primer código que no sea un comentario en el fichero.jar la ruta de clases debe incluir: CLASSPATH=. public class LibTest { public static void main(String[] args) { Vector v = new Vector().println( "com. no solamente la ruta donde se puede localizar.util.Vector").class para Vector y List.jar Una vez que la ruta de clases es configurada adecuadamente. ¿Pero que sucede acerca de la primer parte de la ruta? Se tiene cuidado de esto en la variable de ambiente CLASSPATH.. public class List { public List() { System.out.java // Uses the library. el siguiente fichero puede ser colocado en cualquier directorio: //: c05:LibTest.List"). entonces busca el fichero compilado con los nombres apropiados. Hay aquí una variación cuando se utilizan ficheros JAR. } } ///:~ Ambos ficheros fueron colocados en un subdirectorio en mi sistema: C:\DOC\JavaT\com\bruceeckel\simple Si se quiere dar marcha atrás en esto. List l = new List(). Vea 208 Pensando en Java www.bruceeckel.com .simple.C:\flavors\grape.bruceeckel.simple.class para List). comienza a buscar en los directorios especificados por CLASSPATH.bruceeckel.

Colisiones ¿Que sucede si dos librerías son importadas mediante un * y ellas incluyen los mismos nombres? Por ejemplo. Sin embargo. Una librería de herramientas a medida Con este conocimiento.Vector().simple. Se puede considerar.com). Dado que esto (junto con la ruta de clases) especifica completamente la localización de la clase Vector.util.*. aún si no configura la variable CLASSPATH se es capaz de compilar y ejecutar programas básicos. hasta que no se escriba el código que cause una colisión. La colisión se sucede si ahora intentamos crear un Vector: Vector v = new Vector(). supongamos que un programa hace esto: import com. cuando se instala.* a no ser que este utilizando algo mas de java. Para compilar y ejecutar el código fuente de este libro (disponible en el paquete del CD ROM disponible en este libro. y el lector puede no saberlo tampoco.util. necesitará hacer algunas modificaciones en la ruta de clases (esto es explicado en el paquete del código fuente). por ejemplo. Si se quiere un Vector estándar de Java. aquí no hay necesidad de la instrucción import java. o en www. Se puede encontrar que. por ejemplo.BruceEckel.util. se puede crear una librerías propias de herramientas para eliminar el código duplicado. Así es que el compilador se queja y obliga a ser explícito.bruceeckel. crear un alias para System.util.* también contiene la clase Vector.que ambas clases y los métodos deseados en Vector y List deben ser públicos. cuando comencé) que Sun hizo el JDK de Java 2 un poco mas inteligente.println() para reducir la escritura.util. se debe decir: java.out.Vector v = new java. import java. todo esta bien-esto es bueno porque de otra forma terminaríamos escribir mucho para prevenir colisiones que nunca sucederían. Esto puede ser parte de un paquete llamado tools: //: com:bruceeckel:tools:P.*.java 209 .util. se producirá una potencial colisión. Configurar la variable CLASSPATH ha sido una experiencia dura para usuarios Java principiantes (lo fue para mi. sin embargo. Dado que java. ¿A que clase Vector se refiere? El compilador no puede saberlo.

rintln("" + 100).*.// The P.rint & P.rintln()) o sin un fin de línea (P.rintln("Available from now on!"). en el ejemplo anterior.out.class puede ser utilizado en cualquier parte de su sistema con una instrucción import: //: c05:ToolTest.tools. Con alguna sobrecarga extra. que permite cambiar un modificador y obtener diferentes comportamiento sin cambiar otro código.java // Uses the tools library. import com. el fichero P. } } ///:~ Se puede utilizar esta versión corta para imprimir un String con un fin de línea(P. puede agregarse a el directorio tools (o su directorio personal util o tools).bruceeckel. P. luego continúe con com/bruceeckel/tools.println(100) . Luego de compilar. Si se llama a System. Utilizando import para cambiar el comportamiento Una característica que se pierde en Java es la compilación condicionalde C. P.rintln("" + 100L). se puede obtener la clase P para que realice esto de la misma forma (esto es un ejercicio del final de este capítulo). Así es que desde ahora en adelante.out. Pero esto lleva a una interesante observación.BruceEckel. public class ToolTest { public static void main(String[] args) { P.bruceeckel. // Obligarlo a ser una cadena P.print(s). } public static void rintln(String s) { System. La razón de que esa característica fuera quitada de Java probablemente porque es a menudo mas utilizada en C para solucionar 210 Pensando en Java www. Se puede adivinar que la ubicación de ese fichero debe ser en un directorio que comience con alguna de las rutas en CLASSPATH.com .println(s). esto funciona sin convertir el tipo a String.rintln shorthand.tools. } } ///:~ Debe notarse que todos los objetos pueden fácilmente ser obligados a formar parte de una representación del tipo String colocándolos en una expresión del tipo String. package com.rintln("" + 3. cuando se aparezca con una nueva utilizad. si se comienza con una cadena vacía se logra el truco.rint()). public class P { public static void rint(String s) { System.out.14159).

En el capítulo 10.err.bruceeckel. una segunda clase Assert es creada. Sin embargo. } public final static void is_false(boolean exp.debug. pero el método perr() trabajará bien por lo pronto. este rasgo no es necesario. La salida es impresa en el flujo de la consola de error estándarescribiendo en System. public class Assert { private static void perr(String msg) { System. que imprimen mensajes de error si fallan. hay otros usos valiosos para la compilación condicional. Dado que se pretende que Java sea automáticamente multiplataforma. Las herramientas de depuración son habilitadas durante el desarrollo y deshabilitadas cuando se distribuye el producto. String msg) { if(exp) perr("Assertion failed: " + msg). Cuando se quiera utilizar esta clase. Igual que una herramienta es muy provechoso durante la depuración.temas de plataformas cruzadas: diferentes porciones de código son compilados dependiendo de la plataforma en la que el código es compilado. Para quitar la aseveración y así saltar el código.println(msg). He aquí una clase que se puede utilizar para depuración: //: com:bruceeckel:tools:debug:Assert. } public final static void is_true(boolean exp.debug. package com.tools.java // Assertion tool for debugging.holub.tools. String msg) { if(!exp) perr("Assertion failed: " + msg). } public final static void is_true(boolean exp) { if(!exp) perr("Assertion failed"). agregue una línea en su programa: import com. Allen Holub (www.bruceeckel. Un uso muy común es para depurar código.err. } } ///:~ Esta clase simplemente encapsula pruebas booleanas.com) apareció con la idea de utilizar paquetes para imitar la compilación condicional. pero de un paquete diferente: 211 . se aprenderá acerca de herramientas mas sofisticadas para tratar con errores llamadas manejadores de error. } public final static void is_false(boolean exp){ if(exp) perr("Assertion failed"). el usa esto para crear una versión Java de un muy útil mecanismo de argumentaciónde C.*. por lo cual se puede decir “esto debería ser verdadero” o “esto debería ser falseo” y si la instrucción no esta de acuerdo con su argumentación estará fuera.

se cambia es código de la versión depurada a la versión productiva. se obtendrán un montón de misteriosos mensajes en tiempo de ejecución acerca de una clase particular imposible de encontrar.java // Demonstrating the assertion tool. He aquí un ejemplo: //: c05:TestAssert.tools.bruceeckel. and uncomment the // subsequent line to change assertion behavior: import com.*. Esta técnica puede ser utilizada por cualquier tipo de código condicional. // Comment the following. String msg) {} } ///:~ Ahora si cambia la anterior instrucción import a: import com. public class TestAssert { public static void main(String[] args) { Assert.bruceeckel. Assert.com .is_true((2 + 2) == 5).is_true((2 + 2) == 5. "2 + 2 == 5").bruceeckel. package com. aún si esa clase esta ubicada en el mismo directorio. Si se obtiene un mensaje como este.*.is_false((1 + 1) == 2).//: com:bruceeckel:tools:Assert.tools. se debe implícitamente especificar una estructura de directorio cuando se de un nombre de paquete. Assert. // import com. el cual debe ser localizado iniciando una búsqueda en las rutas de clases. Assert. porque a no ser que agregue a el nombre del paquete la ruta que lo contiene.tools. Advertencia de paquete Vale la pena recordar que cada vez que se cree un paquete. y si corre se sabrá donde se encuentra el problema.is_false((1 + 1) == 2. se debe comentar las instrucciones package.BruceEckel. public class Assert { public final static void is_true(boolean exp){} public final static void is_false(boolean exp){} public final static void is_true(boolean exp. El paquete debe vivir en el directorio indicado por este nombre. String msg) {} public final static void is_false(boolean exp.java // Turning off the assertion output // so you can ship the program. Ahora el programa no imprimirá mas argumentaciones. Experimentando con la palabra clave package puede haber un poco de frustración al comienzo. "1 +1 != 2").debug. } } ///:~ Cambiando el paquete que se importa.*. 212 Pensando en Java www.bruceeckel.tools.

En las siguientes secciones. “Hola. pero es comúnmente referido como “amigable”. El acceso amigable permite agrupar clases relacionadas juntas en un paquete así pueden fácilmente interactuar con cada una de las otras. El control de clase cuyo código tiene acceso a sus miembros.g. Esto significa que todas las otras clases en el paquete actual tienen acceso a un miembro amigable. Esto tiene sentido de tal manera que solo el código que un programador posee tendrá acceso amigable a otro código que posea. tanto si es un campo o un método. El código de otro paquete no puede mostrarse y decir. No hay formas mágicas de “quebrarlo”. Esto es un contraste distinto con C++. Cuando se colocan clases juntas en un paquete (de esta forma garantizar un muto acceso a sus miembros amigables. Además. hacerlos “amigos”) se “posee” el código en ese paquete. “Amigable” (“Friendly”) ¿Que si ni se indica para nada un especificador de acceso. De esta manera. protected. Se podría decir que el acceso amigable le da un significado o una razón para agrupar clases juntas en un paquete. los especificadores de acceso de Java public . pero para todas las clases fuera de este paquete el miembro parece ser private. soy un amigo de Bob y espero ver los miembros del tipo 213 . y private son ubicados al frente de cada definición de cada miembro de su clase. De una forma u otra. probablemente se quiera excluir clases que no deberían tener acceso a las clases que se encuentran en el paquete actual. comenzando con el acceso por defecto. pero en java se está coaccionado a organizar ellas en una forma sensible. Cada especificador de acceso controla el acceso para esa definición en particular solamente.Especificadores de acceso de Java Cuando es utilizado.puede pertenecer solo a un solo paquete. En muchos lenguajes la forma de organizar las definiciones en ficheros puede ser a la fuerza. se aprenderá acerca de todos los tipos de accesos. todos tienen un tipo de acceso especificado. Dado que una unidad de compilación-un fichero. se dice que los elementos amigables tienen acceso al paquete. e. todas las clases con una sola unidad de compilación es automáticamente amigable con cada una de las otras. en donde el especificador de acceso controla todas las definiciones que siguen hasta el siguiente especificador de acceso. como en los ejemplos anteriores de este capítulo? El acceso por defecto no tiene palabra clave.

package c05. como se verá en el capítulo 13. Esto es la estrategia mas civilizada en términos de POO. Cookie. Si no se tiene un ‘. puede acceder a este. La única forma de otorgar acceso a un miembro es: 1.’ como una de las rutas en la ruta de clases. 214 Pensando en Java www.println("bite").java // Uses the library. Estos pueden acceder a miembros amigables solo si las dos clases están en el mismo paquete. Entonces todos. cuando la herencia sea introducida. Hacer el miembro public . public class Cookie { public Cookie() { System.println("Cookie constructor").out. Supongamos que se define un paquete dessert que contiene la siguiente unidad de compilación: //: c05:dessert:Cookie. Ahora si se crea un programa que utilice Cookie: //: c05:Dinner. 4. colocar la otra clase en el mismo paquete.out. Pero no nos preocupemos de eso por ahora. Proporcionar métodos “accessor/mutator” (también conocidos como métodos “get/set”) que leen y cambian el valor. Como se verá en el capítulo 6.java debe residir en un subdirectorio llamado dessert. Java no buscará allí. Dejar el miembro sin especificador y hacerlo amigable. No debe cometerse el error de pensar que Java verá el directorio actual como un punto de partida para una búsqueda.BruceEckel.com . 2. en un directorio bajo co5 (que indica el capítulo 5 de este libro) que debe encontrarse bajo un directorio de la ruta de clases. y es fundamental para JavaBeans.protected. una clase heredada puede acceder a un miembro protected de la misma forma que un miembro público (pero no como miembros private). significa que la declaración de miembro que se encuentra inmediatamente después de public esta disponible para cualquiera.java // Creates a library.dessert. public: acceso a la interfase Cuando se utilice la palabra clave public . 3. Entonces la otra clase puede acceder al miembro. en cualquier lugar. } void bite() { System. amigable y private de Bob. } } ///:~ Recuerde. en particular para el cliente programador que utiliza la librería.

import c05.println("Pie.java dado que bite() es amigable solo con el paquete dessert. class Cake { public static void main(String[] args) { Pie x = new Pie(). Otras 215 .out. en el mismo directorio: //: c05:Pie. Java procesa los ficheros como este como partes implícitas de un paquete por defecto para ese directorio. el miembro bite() es inaccesible desde adentro de Dinners. La razón por la cual están disponibles en Cake. aún cuando parece que quiebra las reglas: //: c05:Cake.bite(). } public static void main(String[] args) { Cookie x = new Cookie().out. y con todo Cake es capaz de crear un objeto Pie y llamar su método f() (Nótese que debe tener ‘. los métodos internos de esa clase. Ellas son amigables-esa parte es correcta. } } ///:~ En un segundo fichero. x.’ en la ruta de clases para poder compilar estos ficheros)! Podría pensar que Pie y f() son amigables y por consiguiente no estar disponibles para Cake. class Pie { void f() { System. //! x. // Can't access } } ///:~ se puede crear un objeto Cookie.*. public class Dinner { public Dinner() { System. dado que su constructor es público y la clase es del tipo public (Se verá mas del concepto de clase publica mas adelante).println("Dinner constructor").dessert.f(). El paquete por defecto Es sorprendente descubrir que el siguiente código copila. private: no se puede tocar eso! La palabra clave private indica que nadie puede tener acceso a ese miembro exceptuando la clase en particular.java es porque están en el mismo directorio y no tienen un nombre de paquete explícito. } } ///:~ Se puede inicialmente ver estas como ficheros completamente ajenos. Sin embargo.f()"). y por lo tanto amigables para todos los otros ficheros en ese directorio.java // Accesses a class in a // separate compilation unit.java // The other class.

y es privado. no se puede crear un objeto Sundae mediante su constructor. así es que private permite que se cambie libremente ese miembro sin preocuparse si afectará a otra clase en el mismo paquete. Cualquier método del que se este seguro que es solo un método que “ayude” a esa clase puede hacerse privado. } } public class IceCream { public static void main(String[] args) { //! Sundae x = new Sundae(). El acceso “amigable” por defecto de paquete frecuentemente proporciona un nivel de encubrimiento adecuado. en lugar de eso se debe llamar a el método m a keASundae() para que lo haga2.makeASundae(). En el ejemplo anterior. así es que es como si aún se haya aislado la clase contra uno mismo.java // Demonstrates "private" keyword.BruceEckel. y como resultado. especialmente en lo concerniente a hilos múltiples (Como se verá en el capítulo 14).clases en el mismo paquete no pueden acceder a miembros privados. Aquí vemos un ejemplo del uso de private: //: c05:IceCream. no se necesitará inicialmente pensar si se utilizará la palabra clave private a menudo dado que es tolerable salir del paso sin ella (Esto es un contraste distinto con C++). Esto es bueno. recuerde que un miembro “amigable” es inaccesible a el usuario de ese paquete. no es improbable que un paquete pueda ser creado por muchas personas que colaboran juntas.com . para asegurar que no se utilice accidentalmente en cualquier parte del paquete y prohibir de uno mismo de 2 Hay otro efecto en este caso: dato que el constructor por defecto es el único definido. } } ///:~ Esto muestra un ejemplo en el cual private es conveniente: se debe controlar como un objeto es creado y prevenir que alguien con acceso directo a un constructor en particular (o a todos ellos). Por el otro lado. el uso consistente de priv ate es muy importante. 216 Pensando en Java www. normalmente solo se pensará en acerca del acceso para los miembros que explícitamente se quieren hacer públicos para el cliente programador. class Sundae { private Sundae() {} static Sundae makeASundae() { return new Sundae(). De esta forma. previene la herencia de esta clase (un tema que introduciremos en el capítulo 6). Sundae x = Sundae. Sin embargo. dado que el acceso por defecto es el único que normalmente es utilizado (y el único que se obtendrá si se olvida de agregar un control de acceso).

se debe ser conciente que no se necesita entender esta sección para continuar este libro hasta herencia (Capítulo 6). el creador de la clase base desea tomar un miembro particular y conceder acceso a las clases derivadas pero no a el mundo en general. los únicos miembros a los cuales se tiene acceso son los miembros públicos del paquete original (Claro.out. Esto es lo que protected hace. si se realiza una herencia en el mismo paquete.dessert. } public static void main(String[] args) { ChocolateChip x = new ChocolateChip(). se debería hacer todos los campos privados. que toma una clase existente y agrega nuevos miembros a esa clase sin tocar la clase existente. public class ChocolateChip extends Cookie { public ChocolateChip() { System. la siguiente clase no puede acceder a el miembro “amigable”: //: c05:ChocolateChip. Si volvemos a referirnos a el fichero Cookie. solo porque una referencia a un objeto es privada dentro de una clase. Sin embargo. no significa que algún otro objeto pueda tener una referencia pública a el mismo objeto (Véase el Apéndice A por temas de aliasing). he aquí una breve descripción y un ejemplo de como utilizar protected. A no ser que se desee exponer la implementación de capas mas bajas (la cual es una situación muy rara en la que se debe pensar). Lo mismo es cierto para campos privados dentro de una clase. Para heredar de una clase existente. A veces. Hacer un método privado garantiza que existe esa opción.println( "ChocolateChip constructor"). Primero. import c05. Pero por un tema de amplitud. Si se crea un nuevo paquete y se hereda de una clase de otro paquete.java. que se refiere como la clase base.*.java // Can't access friendly member // in another class.cambiar o quitar el método. Se puede también cambiar el comportamiento de los miembros existentes de la clase. se tiene el acceso normal de paquete a todos los miembros “amigables”). protected: “un tipo de amistad” El especificador de acceso protected requiere un salto adelante para ser entendido. se dice que la clase nueva se extiende de una clase existente utilizando extends de la siguiente forma: class Foo extends Bar { El resto de la definición de la clase se ve igual. La palabra clave protected trata con el concepto de herencia. 217 .

BruceEckel. pero entonces cualquiera pude tener acceso y tal vez eso no es lo que se quiere.println("bite"). que es separar la interfase de la implementación. Claro.bite().out. Estamos ahora en el mundo de la programación orientada a objetos. } } entonces bite() sigue con acceso “amigable” sin el paquete dessert. se puede hacer este público.println("Cookie constructor"). Si la estructura es utilizada en un grupo de programas. La envoltura de datos y métodos dentro las clases en combinación con la implementación oculta es a menudo llamada encapsulación3 . pero el cliente programador no puede hacer nada mas que enviar mensajes a la interfase pública. pero es también accesible para cualquier clase que sea heredada de Cookie. El control de acceso coloca límites dentro de un tipo de datos por dos razones importantes. } protected void bite() { System. protegido o privado) sin necesitar modificaciones en el código del cliente.out.//! x. “amigable”. entonces se puede cambiar cualquier cosa que no sea pública (e. // Can't access bite } } ///:~ Una de las cosas interesantes acerca de herencia es que si un método bite() existe en la clase Cookie. Interfase e implementación El control de acceso es a menudo referido como implementación oculta. como se 3 Sin embargo. El resultado es un tipo de datos con determinadas características y comportamientos. 218 Pensando en Java www. donde una clase es actualmente descrita como “una clase de objetos”. Sin embargo.g. entonces también existe en la clase heredada de Cookie. Si se cambia la clase Cookie como sigue: public class Cookie { public Cookie() { System. no es público. no esta disponible en esta. Pero dado que bite() es “amigable” en un paquete del exterior. a menudo las personas se refieren a la implementación oculta sola como encapsulación.com . La primera es para establecer lo que el cliente programador puede y no puede utilizar. Esto nos lleva directamente a la segunda razón. Se pueden realizar mecanismos dentro de la estructura sin preocuparse que el cliente programador accidentalmente trate con los interiores como parte de la interfase que utilizará.

La ventaja es que el usuario de una clase puede entonces leer comenzando desde arriba y ver primero que es importante para él (los miembros públicos. . La clase es el concepto fundamental de POO en Java. 219 . /* . */ } . /* . los comentarios de documentación soportado por javadoc (descrito en el capitulo 2) reducen la importancia de la legibilidad del código por parte del cliente programador. Es una de las palabras claves que no serán colocadas en negrita en este libro-comienza a molestar en una palabra tan repetida como “class”. . la palabra clave class fue utilizada para describir un nuevo tipo de dato. que miembros están disponibles) en una forma útil. Cualquier objeto que pertenece a esta clase compartirá estas características y comportamientos. . */ } . . Esto es. Esto es el punto focal en la totalidad del lenguaje: la creación de nuevos tipos de datos que son solo cajas conteniendo datos y métodos. . Simula-67. En el lenguaje de POO original. . . En el momento en que se lea esto. los navegadores serán una esperada parte de cualquier buena herramienta de desarrollo. . . { /* { /* { /* . . } { { { ) ) ) /* . Además. dado que a ellos puede acceder desde afuera del fichero). y parar de leer cuando se encuentra un miembro que no es público. sigue viendo el código fuente-la implementación-dado que esta ahí en la clase. . .. Por un tema de claridad. seguidos por los del tipo protected. amigables y por último los del tipo private.describe una clase de peces o una clase de pájaros. */ } */ } */ } . lo que ya es parte de la implementación interna: public class X { public void pub1( ) public void pub2( ) public void pub3( ) private void priv1( private void priv2( private void priv3( private int i.e. La clase es una descripción de la forma en que todos los objetos se verán y actuarán. // . La misma palabra clave ha sido utilizada por la mayoría de los lenguajes orientados a objetos. */ } Esto lo hará parcialmente fácil de leer porque la interfase y la implementación siguen mezcladas. se prefiere un estilo para crear clases que colocan los miembros del tipo public en el comienzo. Mostar la interfase a el consumidor de una clase es realmente la tarea de el navegador de clase(class browser una herramienta cuyo trabajo es ). . mostrar a todos las clases disponibles y mostrar lo que se puede hacer con ellas (i.

se obtendrá un error en tiempo de compilación si no esta de acuerdo. se coloca la palabra clave public en algún lado antes de abrir la llave del cuerpo de la clase. el nombre de fichero es el que se quiera.java o WIDGET. substituyéndola por una diferente.java. hay un grupo extra de restricciones: 1. Esto controla incluso si el cliente programador puede crear un objeto de la clase.*. El nombre de la clase pública debe corresponder exactamente con el nombre del fichero que contiene la unidad de compilación. y se tiene en mente que en algún momento se cambiarán totalmente las cosas incluyendo la clase en su totalidad. Si se tiene mas de una clase pública dentro de una unidad de compilación. La idea es que cada unidad de compilación tenga una sola interfase pública representada por esa clase pública. 2. Sin embargo.BruceEckel. Para controlar el acceso a una clase. el compilador dará un mensaje de error. se necesita asegurar que ningún cliente programador se hizo dependiente de los detalles de implementación ocultos dentro de mylib. y no widget. ¿Que sucede si se tiene una clase dentro de mylib que simplemente se esta utilizando para realizar las tareas ejecutadas por Widget o alguna otra clase pública en mylib? Si no se quiere tomar la molestia de crear la documentación para el cliente programador.java. aunque no es típico.Acceso a la clase En Java. incluyendo las mayúsculas y las minúsculas. el especificador debe aparecer antes de la palabra clave class. los especificadores de acceso pueden ser utilizados para determinar que clases dentro de una librería estarán disponibles para los usuarios de esa librería. 3. Si es posible. Si se quiere que una clase este disponible para el cliente programador. Entonces se puede decir: public class Widget { Ahora si el nombre de la librería es mylib cualquier cliente programador puede acceder a Widget diciendo: import mylib. Así es que para Widget. En este caso.com . Puede tener tantas clases “amigables” de soporte como se quiera.Widget. tener una unidad de compilación sin clase pública. o import mylib. Solo puede haber una clase pública por unidad de compilación (fichero). Si se desea dar esa flexibilidad. Nuevamente. el nombre del fichero debe ser Widget. Para lograr esto se 220 Pensando en Java www.

java // Demonstrates class access specifiers. } } // Only one public class allowed per file: public class Lunch { void test() { // Can't do this! Private constructor: //! Soup priv1 = new Soup(). } // (2) Create a static object and // return a reference upon request. dentro de un miembro estático de la clase. // Make a class effectively private // with private constructors: class Soup { private Soup() {} // (1) Allow creation via static method: public static Soup makeSoup() { return new Soup().deja la palabra clave public fuera de la clase. pero esto es un caso especial. } 4 Actualmente. // (The "Singleton" pattern): private static Soup ps1 = new Soup(). se puede hacer que todos los constructores sean del tipo private.access(). Así es que solo se tienen dos posibilidades para el acceso a la clase: “amigable” o public . una clase interna puede ser privada o protegida.f(). Se puede ver que una clase no puede ser private (esto la haría inaccesible para todos menos para la clase). public static Soup access() { return ps1. 221 . o protected 4 . Si no se desea que nadie pueda acceder a la clase. Soup. Esto será introducido en el capítulo 7. de crear un objeto de esa clase5. Sandwich f1 = new Sandwich(). 5 Se puede también hacer esto heredando (Capítulo 6) de esa clase. Soup priv2 = Soup.makeSoup(). muchos de los métodos han retornado o void o un tipo primitivo. en cuyo caso se convierte en amigable (Esa clase solo puede ser utilizada dentro del paquete). así es que la definición: public static Soup access() { return ps1. de esta manera se previene que nadie excepto la persona que esta escribiendo la clase. } public void f() {} } class Sandwich { // Uses Lunch void f() { new Lunch(). } } ///:~ Hasta ahora. He aquí un ejemplo: //: c05:Lunch.

Resumen En cualquier relación es importante tener límites que sean respetados por todas las partes involucradas. Sin embargo. Recuerde que si no se crea explícitamente al menos un constructor. no lo creará automáticamente. todos los ficheros dentro del mismo directorio que no tienen la declaración explícita del paquete son implícitamente parte del paquete por defecto de ese directorio). La segunda opción utiliza lo que llamamos patrón de diseño. así es que hay uno y solo uno. un método estático es creado que para crear un nuevo Soup y retorna una referencia a este. se establece una relación con el usuario de esa librería-el cliente programador-que es otro programador. Hasta el momento lo mas común ha sido void. el cliente programador puede acceder a ese miembro estático aún cuando no pueda crear un objeto de esa clase.com . Primero. nadie podrá crear un objeto de esa clase.BruceEckel. Este patrón particular es llamado un “singleton” porque permite que un solo objeto sea creado. ¿Si se hace privado. Este método retorna una referencia a un objeto de la clase Soup.Puede resultar un poco confusa al comienzo. Esto significa que un objeto de esa clase puede ser creado por cualquier otra clase dentro del paquete. pero no fuera del paquete (Se debe recordar que. 222 Pensando en Java www. y no se puede llegar a el a no ser a través de el método público access(). Pero se puede retornar también una referencia a un objeto. Cuando se crea una librería. La palabra antes que el nombre del método (access) indica lo que el método retorna. si no se coloca un especificador de acceso para el acceso a la clase esta es “amigable” por defecto. www. Esto puede ser útil si se desea operaciones extras en el objeto Soup antes de retornar.com. Escribir el constructor por defecto. Como se ha mencionado previamente. si un miembro estático de la clase es público. que es cubierto en Thinking in Patterns with Java que se puede bajar en . El objeto de la clase Soup es creado como un miembro del tipo static private de Soup. Pero ahora.BruceEckel. como se hace para que alguien utilice esa clase? El anterior ejemplo muestra dos opciones. que es lo que sucede aquí. pero uno armando una aplicación o utilizando la libraría para armar otra librería mas grandes. La clase Soup muestra como prevenir la ceración directa de una clase haciendo que todos los constructores sean del tipo private. el constructor por defecto (un constructor sin argumentos) será creado. que significa que no retorna nada. o si se desea contar cuantos objetos Soup se han creado (quizás para restringir la población).

Se puede crear una clase de una forma al inicio. se cometerán errores. En Java. Los especificadores de acceso en Java les da un control de mucho valor a el creador de la clase. La segunda y mas importante razón para controlar el acceso es permitir al diseñador de la librería cambiar el manejo interno de la clase sin preocuparse acerca de como afectará esto al cliente programador. Si se sabe esto. sin embargo. y descubrir que reestructurando su código se proporcionará mucha mas velocidad. la primera es mantener las manos de los usuarios fuera de las herramientas que no deben tocar. Cuando se tiene la habilidad de cambiar la implementación de las capas bajas. los clientes programadores pueden hacer cualquier cosa que quieran con todos los miembros de una clase. y la palabra clave import le da un control completo de los nombres así es que el tema de los conflictos de nombres es fácilmente solucionado. Esto simplifica el entendimiento de la clase. la forma como un grupo de clases es empaquetada dentro de una librería. No importa que cuidadoso sea el plan de diseño. pero no son parte de la interfase que los usuarios necesitan para solucionar sus problemas en particular. no solo se puede mejorar el diseño mas adelante. primero. la palabra clave package.Sin reglas. el nombre del esquema de paquete. pero también se tiene la libertad de cometer errores. Todo desnudo ante el mundo. como creador de una clase. En este capítulo se ve como las clases de arman para formar librerías. es la habilidad de asegurar que ningún usuario se vuelva dependiente de alguna parte de la implementación de las capas mas bajas de una clase. se puede lograr esto sin forzar al usuario a volver a escribir su código. Hay dos razones para controlar el acceso a los miembros. herramientas que son necesarias para las maquinaciones internas de los tipos de datos. se puede cambiar la implementación de capas bajas con el conocimiento de que ningún cliente programador será afectado por los cambios porque no pueden acceder a esa parte de la clase. Así es que el crear métodos y campos del tipo private es un servicio para los usuarios porque ellos pueden fácilmente ver que es importante para ellos y que deben ignorar. Sabiendo que es relativamente seguro 223 . Se estima que un proyecto de programación C comienza a estropearse en algún lugar entre 50K y 100K líneas de código a causa de que C es un único “espacio de nombres” así es que los nombres comienzan a colisionar. la forma en como la clase controla el acceso a sus miembros. El usuario de la clase puede claramente ver exactamente que puede utilizar y que debe ignorar. y segundo. aún si se prefiere que no se manipule algunos de los miembros. Mas importante. Si la interfase y la implementación son separadas claramente y protegidas. produciendo un costo operativo extra de administración.

coloque los fragmentos de código concernientes a mypackage en un grupo de ficheros Java que compilen y corran. y “amigables”. 3. La interfase pública de una clase es lo que el usuario verá.com . 224 Pensando en Java www. Cree una clase con datos y métodos miembro public . 8.*.cometer estos errores significa que se será mas experimental. En la sección etiquetada “paquete: la unidad de librería”. 9. se aprende rápido. Cree una segunda clase en el mismo fichero con un método que manipule los datos protegidos en la primera clase. Si no se tiene la interfase correcta en un primer momento. tome los fragmentos de código. Escriba un programa que cree un objeto ArrayList sin importar explícitamente java. private. y se terminan los proyectos mas rápido. En la sección marcada como “Acceso a la clase” se encuentran fragmentos de código descritos en mylib y Widget. En la sección etiquetada “Colisiones”.BruceEckel. así es que es la parte mas importante de la clase a tener “correcta” durante el análisis y el diseño. Aún cuando se permita alguna libertad de cambio. Se debe ser consciente de que las clases en el mismo directorio son parte del paquete por “defecto”. Generalice la clase P definida en este capítulo agregando todas las versiones sobrecargadas de rint() y rintln() necesarias para manejar todos los tipos básicos de Java. Ejercicios La solución de los ejercicios seleccionados pueden encontrarse en el documento electrónico The T h i n k i n g i n J a v a A n n o t a t e d S o l u t i o n G u i d e. 5. 6. disponible por una pequeña propina en www. Cree un objeto de esta clase y vea que tipo de mensajes de compilación se obtienen cuando se intenta acceder a los miembros de la clase. protected. mientras que no se puede eliminar algo que los clientes programadores ya estén utilizando en su código.com . Verifique que bite() no es público.BruceEckel. colóquelos dentro de un programa y verifíquese que las colisiones ocurren de hecho. se puede agregarmas métodos. Cambie la instrucción import en TestAsser. 2. 7. Cambie la clase Cookie como se especifica en la sección “protected: un tipo de amistad”. 1. 4. Cree esta librería.java para habilitar y deshabilitar el mecanismo de argumentación.util. Cree una clase con datos del tipo protected.

12. ///: c05:local:PackagedClass.out. Hacer la clase Foreing parte del paquete co5. Siguiendo la forma del ejemplo Lunch.luego créese un Widget en una clase que no sea parte del paquete mylib.local. Cree otro programa en un directorio diferente que utilice la nueva clase. Cuando ConnectionManager se ejecute sin objetos.java package c05.class (producido compilando com. } } ///:~ ¿Explique por que el compilador genera un error. Cree un nuevo directorio y edite la ruta de clases para incluir ese directorio nuevo.println( "Creating a packaged class").bruceeckel.foreign. debe retornar una referencia nula.local. } } ///:~ Luego créese el siguiente fichero en un directorio distinto de co5: ///: c05:foreign:Foreign. class PackagedClass { public PackagedClass() { System.P.java) en su nuevo directorio y luego cambie los nombres del fichero. Pruebe las clases con un main(). public class Foreign { public static void main (String[] args) { PackagedClass pc = new PackagedClass(). El programador cliente no debe ser capas de crear explícitamente objetos Connection. Copie el fichero P. cree una clase llamada ConnectionManager que maneje un arreglo fijo de objetos Connection.local cambiaría algo? 225 . la clase P dentro y los nombres de métodos (Se debe también agregar salida adicional para ver como trabaja). 10.java package c05. y solo puede obtenerlos mediante un método estático en la clase ConnectionManager.java.*. import c05. Cree el siguiente fichero en el directorio co5/local (es de suponer en su ruta de clases): 11.tools.

Se reutiliza código creando nuevas clases. Esto es llamado composición. Como todo en Java. La primera es muy directa: Simplemente se crean objetos de su clase existente dentro de la nueva clase.com . porque la nueva clase esta compuesta con clases existentes. Literalmente se toma la forma de la clase existente y se le agrega código sin modificar la clase existente. Queda claro que la mayoría de la sintaxis y comportamiento son similares para la composición y la herencia (lo cual tiene sentido porque son dos formas de crear nuevos tipos partiendo de tipos existentes). La herencia es una de las piedras angulares de la programación orientada a objetos. debemos ser capaces de hacer mas que copiar y cambiar código. Se crea una nueva clase como un tipo de una clase existente. Este acto mágico es llamado herencia. supongamos que se quiere un objeto que almacene varios objetos 226 Pensando en Java www. se utilizan las clases existentes que alguien ya ha armado y depurado. Por ejemplo. Pero para ser revolucionario. y no funcionan muy bien. pero en lugar de crearlas escarbando. no su forma. y tiene implicaciones adicionales que serán exploradas en el Capítulo 7. El truco es utilizar las clases sin ensuciar el código existente. Sintaxis de la composición Hasta ahora. En este capítulo se verán dos estrategias para lograr esto. la composición ha sido utilizada muy frecuentemente. La segunda estrategia es mas sutil. se aprenderá acerca de estos mecanismos de reutilización de código. En este capítulo.BruceEckel. Esta es la estrategia utilizada en lenguajes procesales como C. Simplemente colocamos referencias a objetos dentro de nuevas clases. Simplemente se reutiliza la funcionalidad del código. la solución gira alrededor de la clase.6: Reutilizando clases Una de las mas convincentes características de Java es la reutilización de código. y el compilador realiza la mayoría del trabajo.

out. y es llamado en situaciones especiales cuando el compilador quiere una tipo String pero tiene uno de estos objetos. void print() { System.out. System.println("valve3 = " + valve3). } public static void main(String[] args) { SprinklerSystem x = new SprinklerSystem(). Esto no tiene sentido para el porque solo se puede “agregar” una cadena a otra cadena. se colocan referencias dentro de la nueva clase. System.println("valve1 = " + valve1).out. valve4. se puede asumir-Java siendo tan seguro y cuidadoso como lo es-que el compilador puede automáticamente construir objetos para cada una de las referencias en el código mas arriba.out. valve2.println("f = " + f). pero se pueden definir las primitivas directamente: //: c06:SprinklerSystem.out. En cualquier momento que se desee este comportamiento para una clase que se cree solo se debe escribir un método toString() para esa clase.println("source = " + source). por ejemplo. un par de primitivas y un objeto de otra clase.println("i = " + i). } public String toString() { return s.println("valve2 = " + valve2). } } ///:~ Uno de los métodos definidos en WaterSource es especial: toString(). se puede combinar las dos cadenas y pasar la cadena del tipo String a System. valve3.java // Composition for code reuse. WaterSource() { System.out.println(). el compilador distingue que esta tratando de agregar un objeto String (“source = “) a un WaterSource. System. int i. WaterSource source. System. class WaterSource { private String s. Luego de hacer esto.println("source = " + source).out. float f. Para los objetos que no son primitivos.print(). 227 .out. } } public class SprinklerSystem { private String valve1.String. System.println("WaterSource()").out. Se aprenderá mas adelante que todo objeto no primitivo tiene un método toString(). así es que dice “convertiré source en una cadena llamando a toString()!”. x. En un primer vistazo. System. Así es que la expresión: System.println("valve4 = " + valve4). s = new String("Constructed").out.

com . como se indicó en el capítulo 2. s3. Justo antes de que se necesite utilizar el objeto.llamando el constructor por defecto para WaterSource para inicializar source. Tiene sentido que el compilador no solamente cree un objeto por defecto para cada referencia porque se puede incurrir en una perdida de tiempo innecesaria en muchos casos. Bath() { System. En el momento en que el objeto es definido. class Soap { private String s. } public String toString() { return s.java // Constructor initialization with composition. } } public class Bath { private String // Initializing at point of definition: s1 = new String("Happy"). y si se intenta llamar métodos para cualquiera de ellos se obtendrá una excepción.out. s4. Todos estas estrategias son mostradas aquí: //: c06:Bath. la sobrecarga en situaciones donde el objeto no necesita ser creado cada vez. Si es actualmente muy bueno (y útil) que se puede imprimir igualmente sin lanzar una excepción. Soap castille.BruceEckel. float toy. Esto es también llamado lazy initialization (inicialización perezosa)Esta puede reducir . La salida de la instrucción para imprimir es de hecho: valve1 = valve2 = valve3 = valve4 = i = 0 f = 0. 228 Pensando en Java www. Si se quiere inicializar las referencia se puede hacer lo siguiente: 1. s = new String("Constructed"). s2 = "Happy". En el constructor para esa clase. Pero las referencias a objetos son inicializados a null.println("Soap()"). Soap() { System.out. 2. int i.println("Inside Bath()").0 source = null null null null null Las primitivas que son campos en una clases son automáticamente inicializados a cero. 3. Esto significa que siempre serán inicializados antes que el constructor sea llamado.

println("toy = " + toy). Esto quiere decir que siempre esta heredando cuando se crea una clase.println("s3 = " + s3). no se garantiza que se ejecute ninguna inicialización cuando se envíe un mensaje a una referencia a un objeto-exceptuando por la inevitable excepción en tiempo de ejecución. System.println("s4 = " + s4).println("s2 = " + s2). Cuando no se inicializa en el punto de la definición. System. System. System. } void print() { // Delayed initialization: if(s4 == null) s4 = new String("Joy").println("s1 = " + s1). porque a no ser que usted explícitamente herede de alguna otra clase. toy = 3. castille = new Soap(). } } ///:~ Note que in el constructor Bath una instrucción es ejecutada antes que cualquier inicialización tenga lugar. System.print().println("castille = " + castille). La sintaxis para la composición es obvia.out. b.out.out.println("i = " + i).s3 = new String("Joy").14 castille = Constructed Cuando print() es llamado llena en s4 así es que todos los campos son inicializados propiamente en el momento en que son utilizados.out. se dice “Esta nueva 229 .out. System. i = 47.14f. } public static void main(String[] args) { Bath b = new Bath(). implícitamente se hereda de la clase raíz estándar de Java llamada Object. He aquí la salida para el programa: Inside Bath() Soap() s1 = Happy s2 = Happy s3 = Joy s4 = Joy i = 47 toy = 3. System. pero para realizar herencia hay una forma inconfundiblemente diferente.out. Sintaxis de la herencia La herencia es una parte integral de Java (y de los lenguajes de POO en general). Cuando se hereda.out.

dilute(). public void append(String a) { s += a.out. pero antes de abrir las llaves del cuerpo de la clase. } public void apply() { append(" apply()"). no importa si la clase de la cual es parte es 230 Pensando en Java www. y esto es a menudo recomendado codificar de esta forma así su código de prueba es envuelto con la clase. } public static void main(String[] args) { Cleanser x = new Cleanser().apply(). } } public class Detergent extends Cleanser { // Change a method: public void scrub() { append(" Detergent.println("Testing base class:"). x. System. Cuando se realiza esto.scrub(). Primero. x. x. Cada vez que tenga un montón de clases en un programa. las cadenas son concatenadas a s utilizando el operador +=.scrub(). x.scrub(). en el método append() en la clase Cleanser. ambos.main(args). } } ///:~ Esto deja en evidencia algunas características.clase es como esa vieja clase”. x.dilute(). Se puede crear un main() para cada una de sus clases. } public void dilute() { append(" dilute()").BruceEckel. x.com . // Call base-class version } // Add methods to the interface: public void foam() { append(" foam()"). class Cleanser { private String s = new String("Cleanser"). } public void scrub() { append(" scrub()"). x.scrub()").println(s). } public void print() { System. Cleanser.foam(). que es uno de los operadores (junto con ‘+’) que los diseñadores de Java “sobrecargan” para trabajar con tipos Strings. x. super. solo el main() para la clase invocada en la línea de comandos será llamado (con tal de que main() sea público.apply(). Cleanser y Detergent contienen un método main(). } // Test the new class: public static void main(String[] args) { Detergent x = new Detergent().print(). Segundo.out. automáticamente se obtienen todos los datos y métodos miembro de la clase base. se coloca la palabra clave extends seguido por el nombre de la clase base. He aquí un ejemplo: //: c06:Detergent. x.print(). Se enuncia esto en el código dando el nombre de la clase como es usual.java // Inheritance syntax & properties.

Pero también se puede decir java Cleanser para invocar C leanser. Como se ha visto en scrub(). es posible tomar un método que ha sido definido en la clase base y modificarlo. cualquiera podría utilizar esos métodos sin tener acceso específico. De esta forma la expresión super. apply(). de debe querer llamar el método de la clase base dentro de la nueva versión. sin embargo. Y si no se necesita quitar el main() cuando termina la prueba. se aprenderá acerca de esto mas tarde). dado que producirá una llamada recursiva.pública). en clases particulares se deberá hacer ajustes pero esto es en líneas generales. incluso si se ven definidos explícitamente en Detergent. pero esa parte no es el principal punto). Detergent no tendrá problemas. dilute(). por ejemplo. Así es que en este caso. Cuando se hereda no se esta restringido a utilizar los métodos de la clase base. Se puede agregar nuevos métodos a la clase derivada exactamente de la misma forma en la que se colocan métodos en una clase: simplemente definiéndolos. pasándole los mismos argumentos de la línea de comando (sin embargo. 231 . Dado que Detergent es derivada deCleanser (mediante la palabra clave extends) automáticamente toma todos esos métodos en su interfase. algo que no se desea. como una reutilización de la interfase(la implementación también viene con ella. Se puede ver que Cleanser tiene un grupo de métodos en su interfase: append(). Esta técnica de colocar un main() en cada clase permite probar unidades fácilmente para cada clase. scrub() y print(). Así es que para planear herencia. Aquí. dentro de este paquete. que permite el acceso solo a los miembros del paquete. De esta manera. aún si Cleanser no es una clase pública.main() llama a Cleanser. Recuerde que si deja de indicar algún especificador de acceso el miembro por defecto será “amigable”. cuando se diga java Detergent. En este caso.scrub() llama la versión de la clase base del método scrub(). como regla general se debe hacer todos los campos del tipo private y todos los métodos del tipo public (los miembros protegidos también permiten acceso a las clases derivadas. Claro.main() explícitamente. Pero dentro de scrub() no puede simplemente llamar a scrub(). Detergent. Se puede pensar en herencia. se puede pasar los argumentos en un arreglo del tipo String).main() será llamado. Es importante que todos los métodos en C leanser sean del tipo public .main(). Para solucionar este problema Java tiene la palabra clave super que se refiere a la “super clase” que es la clase de la cual se ha heredado. se puede dejar para pruebas posteriores. entonces. El método foam() es un ejemplo de esto. si una clase de otro paquete donde se herede de Clenaser podría acceder solo a los miembros públicos. se puede ver que Detergent.

println("Cartoon constructor"). Es simplemente eso. } } public class Cartoon extends Drawing { Cartoon() { System. foam()). Pero la herencia no es solamente una copia de la interfase de la clase base. que tiene el conocimiento apropiado y los privilegios para realizar la inicialización de la clase base.java // Constructor calls during inheritance.e.com .En Detergent.main() se puede ver que para un objeto Detergent se puede llamar todos los métodos que están disponibles en Cleanser de la misma forma que en Detergent (i. } } ///:~ La salida para este programa muestra las llamadas automáticas: Art constructor Drawing constructor Cartoon constructor 232 Pensando en Java www.. Este subobjeto es el mismo que si se creara un objeto de la clase base. Cuando se crea un objeto de la clase derivada. el subobjeto de la clase base es envuelto con el objeto de la clase derivada. El siguiente ejemplo muestra este trabajo con tres niveles de herencia: //: c06:Cartoon. esta contiene con ella un subobjeto de la clase base. puede ser un poco confuso intentar imaginar el objeto resultante producido de una clase derivada.println("Art constructor"). Claro. desde afuera.BruceEckel. se ve como que la nueva clase tiene la misma interfase que la clase base y tal vez algunos métodos y campos adicionales.out.out. Inicializando la clase base Dado que hay ahora dos clases involucradas-la clase base y la clase derivada-en lugar de simplemente una. class Art { Art() { System. Java automáticamente agrega llamadas a el constructor de la clase base. llamando el constructor de la clase base.out. De afuera. es esencial que el subobjeto de la clase base sea inicializado correctamente y solo hay una manera de garantizar esto: realizar la inicialización en el constructor. } public static void main(String[] args) { Cartoon x = new Cartoon().println("Drawing constructor"). } } class Drawing extends Art { Drawing() { System.

out. esto es. constructors and arguments.out.out. Capturando excepciones del constructor base Como se habrá notado. Es fácil para el compilador llamar estos porque no hay preguntas acerca de que argumentos pasar.println("Game constructor"). } public static void main(String[] args) { Chess x = new Chess(). Si su clase no tiene argumentos por defecto. el compilador se quejará de que no puede encontrar un constructor de la forma Game(). Además. } } ///:~ Si no se llama el constructor de la clase base en BoardGame(). } } public class Chess extends BoardGame { Chess() { super(11). o no se quiere llamar un constructor de la clase base que tenga argumentos. no tienen argumentos. } } class BoardGame extends Game { BoardGame(int i) { super(i). el compilador fuerza que se coloque la llamada al constructor base primero en el cuerpo del constructor de la clase derivada. Constructores con argumentos El ejemplo anterior tiene constructores por defecto. System. Como se verá en el 233 .println("BoardGame constructor"). Aún si no se crea un constructor para Cartoon(). el compilador sintetizará un constructor por defecto para que se llama a la clase base del constructor. se debe explícitamente escribir las llamadas a el constructor de la clase base utilizando la palabra clave super y la lista de argumentos apropiada: //: c06:Chess.Se puede ver que la construcción se sucede de la base “hacia afuera”.println("Chess constructor"). la llamada a el constructor de la clase base debe ser la primer cosa que haga en el constructor de la clase derivada (El compilador lo recordará si esto se realiza mal). class Game { Game(int i) { System. así es que la clase base es inicializada antes que los constructores de la clase derivada puedan acceder a el. System. Esto significa que nada mas puede ir antes que esto.java // Inheritance.

println("Knife constructor").println("Plate constructor").out.println( "DinnerPlate constructor"). class Plate { Plate(int i) { System.out. junto con la inicialización del constructor necesario: //: c06:PlaceSetting. utilizando herencia y composición.out.println("Utensil constructor").BruceEckel. } } class Knife extends Utensil { Knife(int i) { super(i).out. esto impide que un constructor de una clase derivada capture cualquier excepción que provenga de la clase base. } } class Utensil { Utensil(int i) { System.capítulo 10.out.com .out.println("Spoon constructor"). } } // A cultural way of doing something: class Custom { 234 Pensando en Java www. el siguiente ejemplo muestra la creación de una clase mas compleja.java // Combining composition & inheritance.println("Fork constructor"). System. } } class Fork extends Utensil { Fork(int i) { super(i). System. } } class DinnerPlate extends Plate { DinnerPlate(int i) { super(i). System. } } class Spoon extends Utensil { Spoon(int i) { super(i). System. Combinando composición y herencia Es muy común utilizar la composición y la herencia juntos. Esto puede ser inconveniente por momentos.

util. Knife kn. pl = new DinnerPlate(i + 5). sp = new Spoon(i + 2). permitiendo que el recolector de basura reclame la memoria cuando sea necesaria. así es que se debe recordar prestar atención a esto. System. o si será llamado.java // Asegurando una limpieza adecuada.out. pero hay momentos en que las clases deben realizar algunas actividades durante su vida que requieren limpieza. se debe explícitamente escribir un método especial para hacerlo.println( "PlaceSetting constructor"). y que se haga correctamente al comienzo del constructor. y asegurarse que el los clientes programadores sepan que deben llamar a este método. Además de todo esto -como se describe en el capitulo 10 (“Manejo de errores con excepciones”).println("Custom constructor"). Garantizando la limpieza adecuada Java no tiene el concepto de C++ del destructor.*. 235 . Así es que si a veces se desea limpiar todo para una clase. kn = new Knife(i + 4). DinnerPlate pl. Fork frk.se deberá cuidar contra una excepción colocando la limpieza en una cláusula finally . se puede conocer cuando el recolector de basura va a ser llamado. La razón probablemente es que en Java la práctica es simplemente olvidar los objetos en lugar de destruirlos. import java.out. PlaceSetting(int i) { super(i + 1). frk = new Fork(i + 3).Custom(int i) { System. Muchas veces esto esta bien. no se asegura que inicialice los objetos miembros. } } ///:~ Mientras que el compilador fuerza la inicialización de las clases bases. } public static void main(String[] args) { PlaceSetting x = new PlaceSetting(9). Considere un ejemplo de un sistema de diseño asistido por computadora que dibuje dibujos en la pantalla: //: c06:CADSystem. Como se ha mencionado en el capítulo 4. un método que es automáticamente llamado cuando un objeto es destruido. } } public class PlaceSetting extends Custom { Spoon sp.

BruceEckel. } } class Circle extends Shape { Circle(int i) { super(i).end = end.println("Borrando un círculo").com .println("Constructor combinado").out.out.println("Dibujando un triángulo").println("Borrando una línea: " + start + ".println("Constructor de formas").start = start.out. } void cleanup() { System.out. System. } } class Triangle extends Shape { Triangle(int i) { super(i). j*j). this. super. } void cleanup() { System. c = new Circle(1). System.println("Dibujando un círculo").println("Borrando un triángulo"). int end) { super(start).out. end.println("Limpieza de formas"). " + end). t = new Triangle(1). CADSystem(int i) { super(i + 1).class Shape { Shape(int i) { System. System. System. } void cleanup() { System. for(int j = 0. } void cleanup() { System. } } public class CADSystem extends Shape { private Circle c.println("Dibujando una línea: " + start + ". this.cleanup().out.cleanup().out. private Triangle t. " + end). super.cleanup(). super. } 236 Pensando en Java www. j++) lines[j] = new Line(j. Line(int start.out. } } class Line extends Shape { private int start. j < 10.out. private Line[] lines = new Line[10].

} } } ///:~ Todo en este sistema es un tipo de Shape (que es simplemente un tipo de Object dado que es heredado implícitamente de la clase raíz).cleanup().todas tienen constructores que “dibujan”. es posible dejar un bloque try de varias formas no usuales).cleanup(). no importa como se sale del bloque try (con manejo de excepción.out. try { // Código para manejo de excepciones. en el orden reverso de creación (En general. y no serán introducidas oficialmente hasta el capítulo 10: try y finally . super. Cada clase redefine el método de limpieza de Shape además de llamar la versión de la clase base utilizando super. La palabra clave try indica que el bloque que sigue (delimitado por llaves) es una región protegida. } finally { x. que significa que tiene un tratamiento especial. esto requiere que los elementos de la clase base sigan disponibles).1. Las clases Shape específicas -Circle. como se demuestra aquí. la cláusula finally está diciendo “siempre se debe llamar cleanup() para x. i--) lines[i]. a pesar que cualquier método llamado durante la vida del objeto puede ser responsable por hacer algo que necesite limpieza.. se debe seguir la misma forma que se impone por un compilador C++ con sus destructores: Primero hay que realizar todos los trabajos de limpieza específicos en su clase. c. se deberá simplemente dejar al recolector de basura que realice el 237 . no importa que suceda”. i >= 0. En el main() se puede ver dos palabras claves que son nuevas.cleanup(). Se puede ver que en el método de limpieza se debe prestar atención a el orden de llamada para la clase base y los métodos para limpieza de los objetos miembros en caso de que un subobjeto dependa de otro. for(int i = lines.. Aquí. // El orden de la limpieza es el reverso // del orden de la inicialización t.cleanup(). Puede haber muchos casos en donde el tema de la limpieza no sea un problema. Uno de esos tratamientos especiales es el código en la cláusula finally seguido de la región seguro que siempre es ejecutada.println("CADSystem. Luego se llama a el método de limpieza de la clase base.length .cleanup()").void cleanup() { System. } public static void main(String[] args) { CADSystem x = new CADSystem(47). En general.cleanup(). Cada clase tiene su propio método cleanup() para restaurar las cosas a la forma que eran antes que el objeto existiera. Triangle y Line. Estas palabras claves serán explicadas en el capítulo 10.

0f).println("doh(float)"). se debe programar métodos propios de limpieza y no confiar en finalize() (como ha sido mencionado en el capítulo 4.out. Lo mejor es no confiar en la recolección de basura para nada mas que el reclamo de memoria.println("doh(char)").java // Sobrecargar un nombre de método de una // clase base en una clase derivada no // oculta la version de la clase baseclass Homer { char doh(char c) { System.doh(new Milhouse()). Orden de la recolección de basura No hay mucho en lo que confiarse cuando comienza la recolección de basura. dado que ellos trabajan diferente en ese lenguaje. // doh(float) used b. return 1. } } ///:~ 238 Pensando en Java www. De esta manera la sobrecarga trabaja independientemente de si el método fue definido en este nivel o en la clase base: //: c06:Hide.com . Ocultar nombres Solo los programadores C++ pueden sorprenderse del ocultar nombres. Se lo es. b.doh('x'). } float doh(float f) { System.0f. b.BruceEckel. b. redefinir ese método en la clase derivada no ocultará nada de las versiones de la clase base. puede reclamar objetos en el orden que quiera.doh(1. Java puede ser forzado a llamar a todos los finalizadores). Si una clase base de Java tiene un método que ha sido sobrecargado varias veces. Si se quiere que se suceda una limpieza. return 'd'.trabajo. Pero cuando se deba realizar explícitamente. El recolector de basura puede no ser llamado nunca.out. } } class Milhouse {} class Bart extends Homer { void doh(Milhouse m) {} } class Hide { public static void main(String[] args) { Bart b = new Bart().doh(1). diligencia y atención son requeridos.

Como se verá en el siguiente capítulo. Elegir composición o herencia Ambas. y cuando utilizar uno u otro. Esto puede ser confuso de otra forma (que es lo que C++ desaprueba para prevenir lo que probablemente es un error). pero el usuario de su nueva clase ve que la interfase que ha definido para la nueva clase en lugar de la interfase del objeto incrustado. esto hace la interfase mas fácil de entender.java // Composition with public objects. Esto es. Surge la pregunta acerca de la diferencia entre los dos. así es que es una cosa muy segura de hacer. se incrustan objetos del tipo private de clases existentes dentro de su nueva clase. Un objeto car es un buen ejemplo: //: c06:Car. Los objetos miembro utilizan la implementación oculta ellos mismos. es mucho mas común pasar por encima métodos utilizando la misma firma y tipo de retorno que la clase base. La composición es generalmente utilizada cuando se quiere que las características de una clase existente dentro de su nueva clase. incrustar un objeto así se puede utilizar para implementar funcionalidad en su nueva clase. Cuando el usuario sabe que esta ensamblando un manojo de partes. crear objetos miembro del tipo public . public void open() {} public void close() {} } public class Car { 239 . class Engine { public void start() {} public void rev() {} public void stop() {} } class Wheel { public void inflate(int psi) {} } class Window { public void rollup() {} public void rolldown() {} } class Door { public Window window = new Window(). A veces esto tiene sentido para permitir el usuario de la clase acceder directamente a la composición de su nueva clase. esto es. composición y herencia permiten colocar subobjetos dentro de la nueva clase. pero no su interfase. Para realizar este objetivo.

public Door left = new Door(). En un mundo ideal. i < 4. Sin embargo.inflate(72). Con una pequeña reflexión.rollup().window. protected Ahora que ha sido introducido en el concepto de herencia. Se puede así conceder acceso controlado a los que hereden la clase a través de métodos protegidos: //: c06:Orc. public Wheel[] wheel = new Wheel[4]. La relación es un es expresada con herencia. import java. car. } public static void main(String[] args) { Car car = new Car().java // The protected keyword. 240 Pensando en Java www. // 2-door public Car() { for(int i = 0. Ella dice “Esto es privado tanto como al usuario de la clase le importe.wheel[0]. pero disponible para todo el que herede de esta clase o cualquiera en el mismo paquete”.com . se podrá ver que no tiene sentido componer un auto utilizando un objeto vehículo -un auto no contiene un vehículo. y la relación tiene un es expresada con composición. car. la palabra clave protected finalmente tiene significado. esto significa que esta tomando una clase de propósito general y especializándola para una necesidad particular.*.BruceEckel. En general. pero en proyectos reales hay veces que se quiere ocultar cosas de la mayoría del mundo y permitirle acceso a miembros de clases derivadas. La palabra clave protected es un cabeceo hacia el pragmatismo. hacer los miembros públicos ayuda al cliente programador a entender como utilizar la clase y requiere menos complejidad de código para el creador de la clase. se toma una clase existente y se crea una versión especial de ella. Cuando se hereda. protected en Java es automáticamente “amigable”. El mejor camino a tomar es dejar los datos miembros privados -se deberá siempre preservar su derecho de cambiar la implementación de las capas mas bajas. los miembros del tipo private siempre serán duros y rápidos.left. i++) wheel[i] = new Wheel().util.public Engine engine = new Engine(). es un vehículo. } } ///:~ Porque la composición de un auto es parte del análisis del problema (y no simplemente parte del diseño de capas bajas). Esto es. hay que tener en cuenta que esto es un caso especial y que en general se deberá hacer los campos privados. right = new Door().

si se comienza a hacer “crecer” su proyecto como una criatura orgánica que evoluciona en lugar de construir todo de una como un rascacielos. simplemente se importa el paquete (Esto es cierto para herencia y composición). la herencia es tratar de expresar una relación que dice “Esta nueva clase en un tipo de esa vieja clase”. } public Villain(int ii) { i = ii. } protected void set(int ii) { i = ii. protected int read() { return i. } } public class Orc extends Villain { private int j. } public void change(int x) { set(x). pero se seguirá sin saber todas las respuestas cuando se comience con un proyecto. en algún punto después que las cosas se estabilizan se necesitara echar una nueva mirada a su jerarquía con un ojo puesto en colapsarla en una estructura razonable. Es importante darse cuenta que el desarrollo de programas es un proceso creciente. A pesar de que la herencia para experimentar puede ser una técnica útil. j = jj. El programa no deberá preocuparse de tirar bits por todos lados. Se puede hacer todo el análisis que se desee. public Orc(int jj) { super(jj). Desarrollo creciente Una de las ventajas de la herencia es que soporta desarrollo creciente permitiendo la introducción de nuevo código sin causar errores en el código existente. Esto también aísla nuevos errores dentro del nuevo código. Por herencia de una clase existente y funcional y agregando métodos y datos miembro (y redefiniendo métodos existentes). Si se sucede un error. No se necesita el código fuente de los métodos ara reutilizar el código. } } ///:~ Se puede ver que change() tiene acceso a set() porque esta es protected. A lo sumo. deja el código existente -que alguien más puede seguir utilizando. Recuerde que por debajo de todo. 241 .class Villain { private int i. que es mucho mas corto y fácil de leer que si se ha modificado el cuerpo del código existente.sin tocar y sin errores. } public int value(int m) { return m*i. en lugar de eso mediante la creación y manipulación de objetos de varios tipos expresar un modelo en términos que provengan del espacio del problema. tal como el aprendizaje humano. se sabe que es en el código nuevo. Es mas sorprendente como son separadas limpiamente las clases. Se tendrá mucho mas éxito -y una realimentación inmediata.

el código trabaja para Instrument y cualquier cosa derivada de Instrument. Esto significa que puede decir de forma acertada que un objeto Wind es también un tipo de Instrument. y 242 Pensando en Java www. class Instrument { public void play() {} static void tune(Instrument i) { // . Esta descripción no es solo una forma caprichosa de explicar herencia -esta es soportada directamente por el lenguaje. Como un ejemplo.tune(flute). y no hay método para que tune() pueda llamar para un Instrument que esté en Wind. Instrument.main() el método tune() es llamado dando una referencia a Wind. i. // Upcasting } } ///:~ Lo que es interesante en este ejemplo es el método tune().Conversión ascendente (upcasting) El aspecto mas importante de la herencia no es proporcionar métodos para una nueva clase. El siguiente ejemplo muestra como el compilador soporta esta noción: //: c06:Wind. consideremos una clase base llamada Instrument que represente un instrumento musical. una clase derivada llamada Wind. Esta relación puede ser resumida diciendo “La nueva clase es un tipo de clase existente”. Si la clase Instrument tiene un método play().. Dado que herencia significa que todos los métodos en la clase base están disponibles también en la clase derivada. hasta que se de cuenta que el objeto Wind es también un objeto Instrument.util.com . import java.BruceEckel. suena extraño que un método que solo acepte un tipo acepte fácilmente otro tipo. } } // Los objetos Wind son instrumentos // porque ellos tienen la misma interfase: class Wind extends Instrument { public static void main(String[] args) { Wind flute = new Wind().java // Inheritance & upcasting. todos los mensajes que se puedan enviar a la clase base también pueden ser enviados a la clase derivada. Sin embargo en Wind. Es la relación expresada entre la nueva clase y la clase base. Dado que Java es determinado acerca de la prueba de tipos.*. así será para los instrumentos Wind. que acepta una referencia a Instrument..play(). Dentro de tune().

Una de las formas mas claras de determinar cuando debe utilizar composición o herencia es preguntar si siempre se realizara una 243 . no significa que se deba utilizar en todos lados donde sea posible.el acto de convertir una referencia Wind en una referencia Instrument es llamada upcasting (conversión ascendente). Por el contrario. y basada en la forma en que los diagramas son dibujados tradicionalmente: con la raíz en la parte superior de la página creciendo hacia abajo (Claro. Revisión de composición contra herencia En programación orientada a objetos. El diagrama para Wind. la mejor forma de crear y utilizar código es simplemente empaquetar datos y métodos juntos en una clase.java es entonces: Se realizar una conversión de una clase derivada a una clase base moviéndose arriba en el diagrama de herencia. Esta es por lo que el compilador permite la conversión ascendente sin una conversión explícita o alguna notación especial. Se utilizarán también clases existentes para crear nuevas clases con composición. solo cuando la herencia es útil. así que comúnmente se dice que realizamos una conversión ascendente (upcasting). y utilizar objetos de esa clase. se debe utilizar con moderación. no ganarlos. pero debe contener al menos los métodos de la clase base. Esta puede contener mas métodos que la clase base. la clase derivada es un subconjunto de la clase base. Menos frecuentemente. Realizar una conversión ascendente es siempre seguro dado que lo estamos haciendo desde un tipo mas específico a un tipo mas general. Lo único que puede ocurrir a la interfase de clase durante la conversión es que pierda métodos. Esto es. se utilizará herencia. Se puede también realizar la inversa de la conversión ascendente. pero esto involucra un dilema que es el objeto del capítulo 12. llamada conversión descendente (downcasting). ¿Por que “upcasting”? La razón para el término es histórica. se puede dibujar los diagramas en la forma en que sean útiles). Así es que a pesar que la herencia consigue un gran énfasis cuando se aprende POO.

El siguiente capítulo (polimorfismo) proporciona una de las razones mas convincentes para realizar una conversión ascendente. Un valor debe ser dado en el momento en que es definido como constante. el 244 Pensando en Java www.conversión ascendente de su nueva clase a la clase base. entonces la herencia es necesaria. Si se debe realizar una conversión ascendente. Las siguientes secciones discuten los tres lugares donde la palabra clave final puede ser utilizada: para datos. esto es. es posible realizar un mal manejo de la palabra clave final. se le esta permitido a el compilador “plegar” el valor constante en cualquier cálculo en el que sea utilizado. La palabra clave final La palabra clave final en Java tiene diferentes significados muy sutiles dependiendo del contexto. estos tipos de constantes deben ser primitivas y son expresados utilizando la palabra clave final. no puede ser nunca cambiada para apuntar a otro objeto. final hace referencia a una constante. pero en general indica “Esto no puede ser cambiado”. Una vez que la referencia es inicializada a un objeto.com . el calculo puede ser realizado en tiempo de compilación. Puede ser una constante en tiempo de compilación que se quiere que no cambie nunca.BruceEckel. Una constante es útil por dos razones: 1. En el caso de la constante en tiempo de compilación. pero si no necesita realizar la conversión entonces se debe mirar mas de cerca si se necesita herencia. eliminando algún costo operativo en tiempo de ejecución. pero si se recuerda preguntar “¿Necesito realizar una conversión?” tendrá una buena herramienta para decidir entre composición y herencia. Un campo que es al la ves del tipo static y final tiene solamente un lugar donde es almacenado así es que no puede ser cambiado. Con una primitiva. Dado que estas dos razones son muy diferentes. Datos del tipo final Muchos lenguajes de programación tienen una forma de indicarle al compilador que ese dato es “constante”. Es posible que se quiera prevenir cambios por dos razones: diseño y eficiencia. Cuando se utiliza final con referencias a objetos en lugar de primitivas el significado se hace un poco confuso. Puede ser un valor inicializado en tiempo de ejecución que no se quiere cambiar. Sin embargo. En Java. métodos y clases. 2.

// Error: can't change value fd1. 2. i < fd1.print("fd1"). } public static void main(String[] args) { FinalData fd1 = new FinalData(). 4. //! fd1. System. fd1. public void print(String id) { System. sin embargo.out. // Cannot be compile-time constants: final int i4 = (int)(Math.random()*20).println( id + ": " + "i4 = " + i4 + ". i5 = " + i5). escribir una clase propia cuyos objetos tengan el efecto de ser constantes). final Value v2 = new Value(). i++) fd1. // Error: Can't //! fd1.print("fd1"). // Object isn't constant! fd1. 5. He aquí un ejemplo que muestra los detalles de final: //: c06:FinalData.not final for(int i = 0. 6 }. fd1. estáticas para 245 . class Value { int i = 1.random()*20). static final Value v3 = new Value(). Esta restricción incluye arreglos. static final int i5 = (int)(Math.a. fd2. FinalData fd2 = new FinalData(). // Object isn't constant! //! fd1.v2 = new Value(). static final int VAL_TWO = 99. } public class FinalData { // Can be compile-time constants final int i1 = 9.i1++. Value v1 = new Value().v1 = new Value(). 3.java // The effect of final on fields. // Arrays: final int[] a = { 1. que son también objetos.a = new int[3].a[i]++. // OK -.i++.out.v2.println("Creating new FinalData"). VAL_THREE es la forma mas común que se verán las constantes definidas: del tipo public así pueden ser utilizadas fuera del paquete. } } ///:~ Dado que i1 y VAL_TWO son primitivas del tipo final con valores en tiempos de compilación. // change reference //! fd1. // Typical public constant: public static final int VAL_THREE = 39. Java no proporciona una forma para hacer cualquier objeto arbitrario una constante (Se puede.v3 = new Value(). pueden ambos ser utilizados como constantes en tiempo de compilación y no son distintos de alguna forma importante.objeto puede ser modificado a si mismo.length.print("fd2").

También debe notarse que i5 no se conoce en tiempo de compilación. He ahí un ejemplo: 246 Pensando en Java www. Sin embargo. un campo final dentro de una clase puede ahora ser diferente para cada objeto y a pesar de eso mantener su calidad de inmutable. Las variables v 1 a v 4 demuestran el significado de una referencia del tipo final. constantes en tiempo de compilación) son nombradas con todas las letras mayúsculas por convención. Hacer las referencias del tipo final parece menos útil que hacer las primitivas del tipo final. y el compilador se asegura de esto. Solo porque algo es del tipo final no significa que su valor es conocido en tiempo de ejecución. En todos los casos. Esto es lo que significa final para una referencia. Esto es demostrado inicializando i4 y i5 en tiempo de ejecución utilizando números generados de forma aleatoria. así es que no esta en mayúsculas. se puede volver a enlazar v 2 a un nuevo objeto.enfatizar que solo hay una. Esto es porque es estático y es inicializado una vez cuando es cargado y no cada vez que un objeto es creado. y final para indicar que es una constante. que es solo otro tipo de referencias (Que yo sepa no hay forma para hacer a si mismas las referencias a arreglos del tipo final). el final en blanco debe ser inicializado antes de utilizarse. que es donde la convención se origina). solo porque v 2 es final no significa que se pueda cambiar este valor. precisamente porque es final. Se puede ver el mismo significado es verdadero para un arreglo. La diferencia es mostrada en la salida de una ejecución: fd1: i4 = 15. Esta diferencia muestra solo cuando los valores son inicializados en tiempo de ejecución. i5 = 9 Creating new FinalData fd1: i4 = 15. los finales en blanco proporcionan mas flexibilidad en el uso de la palabra clave final dado que. pero el valor para i5 no cambia cuando se crea el segundo objeto FinalData . i5 = 9 Se puede ver que los valores de i4 para fd1 y fd2 son únicos.BruceEckel. Esta parte del ejemplo muestra también la diferencia entre hacer un valor final del tipo estático o no estático. con palabras separadas por infraguiones (exactamente igual que las constantes en C.com . Finales en blanco (blank finals) Java permite la creación de tipos finales en blanco (blank finals). Sin embargo. Debe notarse que las primitivas del tipo final static con valores constantes iniciales (esto es. que son campos que son declarados como final pero no se les de aun valor de inicialización. Como se puede ver en el main(). por ejemplo. dado que los valores en tiempo de compilación son tratados igual por el compilador (Y presumiblemente la optimización no existe). i5 = 9 fd2: i4 = 10.

} public static void main(String[] args) { FinalArguments bf = new FinalArguments(). Argumentos finales Java permite hacer los argumentos del tipo final declarándolos como en una lista de argumentos. } BlankFinal(int x) { j = x. class Gizmo { public void spin() {} } public class FinalArguments { void with(final Gizmo g) { //! g = new Gizmo(). } } ///:~ Se esta forzado a realizar asignaciones a campos con una expresión en el punto de la definición del campo o en cada constructor.spin(). // Illegal -.without(null). // Initialized final final int j. bf. } } ///:~ 247 .java // "Blank" final data members.with(null). Esto significa que dentro del método no se puede cambiar donde apunta el argumento con la referencia: //: c06:FinalArguments.g not final g. Esta forma garantiza que el campo final sea inicializado antes de utilizarse. // Initialize blank final p = new Poppet(). bf. } // Can't change // You can only read from a final primitive: int g(final int i) { return i + 1.//: c06:BlankFinal. // Initialize blank final p = new Poppet(). } public static void main(String[] args) { BlankFinal bf = new BlankFinal(). // Blank final final Poppet p.g is final } void without(Gizmo g) { g = new Gizmo().java // Using "final" with method arguments. class Poppet { } class BlankFinal { final int i = 0. // OK -. } // void f(final int i) { i++. // Blank final reference // Blank finals MUST be initialized // in the constructor: BlankFinal() { j = 1.

Sin embargo. Porque no se puede acceder a un método del tipo private. es mejor no confiar en que el compilador es capas de hacer esto y hacer un método final solo si es realmente pequeño o si se quiere evitar explícitamente la sobre escritura.java 248 Pensando en Java www. final y private Cualquier método del tipo private en una clase es implícitamente final. Esto significa que el compilador de Java es capas de detectar estas situaciones y elegir sabiamente cuando ejecutar en línea un método final. no se estará sobrescribiendo el método.com . dado que cualquier mejora será mermada por la cantidad de tiempo gastado dentro del método. La segunda razón para métodos del tipo final es la eficiencia. dado que si trata de sobreescribir un método privado (que es implícitamente del tipo final) parecería funcionar: //: c06:FinalOverridingIllusion. saltar al código del método y ejecutarlo. exactamente como se puede con un argumento que no es del tipo final. regresar y limpiar todos los argumentos de la pila y luego ocuparse del valor de retorno) y en lugar de eso remplazar la llamada al método con una copia del código actual en el cuerpo del método.BruceEckel. Claro. cuando es código comienza a inflarse y probablemente no se vea ninguna ganancia en el rendimiento con la llamada en línea. Cuando el compilador ve una llamada a método del tipo final puede (a su discreción) saltar la estrategia normal de insertar código para realizar el mecanismo de llamada al método (moviendo argumentos a la pila. se está permitiendo al compilador convertir todas las llamadas a el método en llamadas en línea. pero no cambiarlo. si un método es grande. Este tema puede causar confusión. El método f() y g() muestra que sucede cuando los argumentos de primitivas son del tipo final: se puede leer el argumento. solo se estará creando un nuevo método). Se puede agregar el especificador final a un método del tipo private pero no le dará al método significado extra. Si se define un método como final. Esto se realiza por razones de diseño cuando se quiere estar seguro que el comportamiento del método se mantendrá durante la herencia y no pueda ser sobrescrito. La primera es colocar un “bloqueo” en el método para prevenir que cualquier clase heredada pueda cambiar su significado. Métodos finales Hay dos razones para métodos del tipo final.Se observa que se puede seguir asignando una referencia null a un argumento que es final sin que el compilador lo note. no se puede sobreescribir (aún cuando el compilador no de un mensaje de error si se trata de sobreescribir el método.

out.f().println("OverridingPrivate.g()"). class WithFinals { // Identical to "private" alone: private final void f() { System. } } class OverridingPrivate2 extends OverridingPrivate { public final void f() { System. // But you can't call the methods: //! op. op2.f()").g(). } // Also automatically "final": private void g() { System.println("WithFinals. // You can upcast: OverridingPrivate op = op2.// It only looks like you can override // a private or private final method.g().println("WithFinals.f()"). //! op. no es parte de la interfase de la clase base. //! wf.out.out. protected o “amigable” en la clase derivada. } } ///:~ La “sobre escritura” solo puede suceder si algo es parte de la interfase de la clase base.g()"). // Same here: WithFinals wf = op2.f(). } public void g() { System. } } public class FinalOverridingIllusion { public static void main(String[] args) { OverridingPrivate2 op2 = new OverridingPrivate2(). op2.g()"). Es simplemente código oculto fuera del interior de la clases.g().out. pero si crea un método del tipo public . se debe ser capaz de realizar una conversión ascendente a un objeto a su tipo base y llamar al mismo método (el punto de esto será mas claro en el siguiente capítulo).println("OverridingPrivate. Si un método es privado.f().println("OverridingPrivate2.out. Esto es. } } class OverridingPrivate extends WithFinals { private final void f() { System.f()"). } private void g() { System. no hay conexión con 249 . y solo sucede que tiene ese nombre. //! wf.out.println("OverridingPrivate2.

n. se declara que no se quiere heredar de esta clase o permitir que nadie mas pueda hacerlo. //: c06:Jurassic. Sin embargo. y si se desea asegurarse que alguna actividad relacionada con objetos de esa clase sean lo mas eficientes posibles. n. Se puede agregar el especificador finala un método en una clase final. Dado que un método private es inalcanzable y en efecto invisible. Alternativamente. Las mismas reglas se aplican con final a los datos independientemente de si la clase es definida como final. void f() {} } //! class Further extends Dinosaur {} // error: Cannot extend final class 'Dinosaur' public class Jurassic { public static void main(String[] args) { Dinosaur n = new Dinosaur(). SmallBrain x = new SmallBrain(). Definiendo la clase como final simplemente previene la herencia-nada mas. dado que previene la herencia todos los métodos en una clase final son implícitamente final.el método que puede ser que tenga el nombre de la clase base.f(). pero no agrega ningún significado. Así es que el compilador tiene las mismas opciones de eficiencia que si se declara un método final. habrá que manejar un tema de eficiencia.com . } } ///:~ Se puede ver que los miembros pueden ser finales o no. dado que no hay forma de sobrescribirlos. class SmallBrain {} final class Dinosaur { int i = 7. no afecta en nada a no ser por la organización del código de la clase para la cual fue definido. n. 250 Pensando en Java www.j++. por seguridad o por razones de seguridad no se quiere que se puedan crear subclases. int j = 1. Clases finales Cuando se dice que una clase entera es final (a causa de que su definición es precedida por la palabra clave final).java // Making an entire class final. por alguna razón el diseño de su clase en tal que nunca se necesitará realizar cambios. como se elija. En otras palabras.i = 40.BruceEckel.

Esto es irónico por dos razones. En particular. esto incurre en una sobrecarga importante en el rendimiento que probablemente anula cualquier ganancia proporcionada por final. pero los diseñadores de alguna forma decidieron que eso no era apropiado. Desafortunadamente. otra clase importante de la librería estándar. sin embargo otra apología para el valor del diseño y al desarrollo del código (se puede ver que en Java 2 la librería Hashtable es reemplazada con HashMap). donde el comportamiento es mucho mas civilizado. todos los métodos no hubieran sido declarados final. Primero. que dice que Stack es un Vector. A veces esto es verdad.Precauciones con final Se puede ser propenso a hacer un método final cuando se esta diseñando una clase. es obvio que algunas clases fueron diseñadas por personas totalmente diferentes (Se puede ver que los nombres de los métodos en Hashtable son mucho mas cortos comparados con los de la clase Vector.1 fue comúnmente utilizada y parece haber tenido mas utilidades.0/1. Se puede sentir que la eficiencia es muy importante cuando se utiliza la clase y que nadie quiera sobreescribir sus métodos de cualquier forma. Es también interesante hacer notar que Hashtable. Como se ha mencionado en alguna parte de este libro. La librería estándar de Java es un buen ejemplo de esto. Si se define un método como final se debe prevenir la posibilidad de reutilizar su clase a través de la herencia en proyectos de otros programadores simplemente porque uno no se imagina que pueda ser utilizada de esa forma. Esto es precisamente el tipo de cosas que no suelen ser obvios para los consumidores de la librerías de clases. Es fácil de imaginar que se pueda querer heredar o sobreescribir esta clase en otra útil. como lo es addElement() y elementAt() son synchronized. Stack es heredada de Vector. Como se verá en el capítulo 14. la clase Vector de Java 1. no tiene método finales. Pero hay que ser cuidadoso con lo que se asume. Esto favorece a la teoría que los programadores son regularmente malos a la hora de adivinar donde se suelen suceder las optimizaciones. otra pieza de evidencia). Segundo. si en nombre de la eficiencia. Java 2 contiene librerías que remplazan Vector con ArrayList. hay una abundancia de código nuevo que fue estrito que utiliza el viejo contenedor de la librería). muchos de los métodos mas importantes de Vector. Esto simplemente es malo que un diseño defectuoso dentro de la librería estándar con la que debemos lidiar todos nosotros (Afortunadamente. En general. Cuando las cosas son inconsistentes simplemente crea mas trabajo para el usuario. es difícil anticipar que clase pueda ser utilizada. 251 . especialmente en una clase de propósito general. lo que realimente no es verdad desde un punto de vista lógico.

En C++. pero la carga también ocurre cuando un campo estático o un método estático es accedido. Todos los objetos estáticos y el bloque de código estático será inicializado en el orden textual (esto es. class Insect { int i = 9. Las cosas del tipo static . Insect() { prt("i = " + i + ".x1 initialized"). Esto es seguido por la inicialización. se puede decir que “El código de la clase es cargado en el punto en que se necesita por primera vez”. Java no tiene este problema porque tiene otra estrategia para cargarse.com . Dado que todo en Java es un objeto. para obtener una imagen completa de lo que sucede.BruceEckel. Considerando el siguiente código: //: c06:Beetle. Inicialización con herencia Es útil ver la totalidad del proceso de inicialización. claro.Inicialización y carga de clase En los lenguajes mas tradicionales. Como se aprenderá mejor en el siguiente capítulo. incluida la herencia. y luego el programa comienza. el orden en el que se escribe en la definición de la clase) en el punto de carga. } static int x1 = prt("static Insect. muchas actividades se hacer simples. j = 39. j = " + j). static int prt(String s) { 252 Pensando en Java www. int j. El punto en el que se utiliza por primera ves es también cuando una inicialización del tipo estático se sucede. los programas son cargados todos como una parte del proceso de carga. el código compilado para cada clase existe en su propio fichero por separado. Esto a menudo no es hasta que el primer objeto de la clase es construido. En general. y esta es una de ellas. Esto no es cargado hasta que el código es necesitado. hay problemas si algo del tipo static espera otra cosa del tipo static para ser válido antes que el segundo sea inicializado. por ejemplo.java // The full process of initialization. En el proceso de inicialización en estos lenguajes se debe ser cuidadoso controlando de tal forma que el orden de inicialización de las partes estáticas no causen problemas. son inicializadas solo una vez.

En la clase base. Esto sucederá si se esta creando un objeto o no de la clase base (Se puede intente comentar la creación del objeto para probar esto).x2 initialized Beetle constructor i = 9.main() (un método estático). } } ///:~ La salida de este programa es: static Insect. Beetle b = new Beetle(). Esto es importante porque la inicialización estática de la clase derivada puede depender de que un miembro de la clase base se inicializado propiamente.println(s). Beetle() { prt("k = " + k). las clases necesarias han sido todas cargadas así es que el objeto puede ser creado. La construcción de la clase base realiza el mismo proceso en el mismo orden que el constructor de la clase derivada.x1 initialized static Beetle. Primero. Entonces el constructor de la clase base será llamado. 253 . con lo cual es entonces cargada. } static int x2 = prt("static Beetle. En este caso la llamada es automática. todas las primitivas en este objeto son configuradas a sus valores por defecto y las referencias a objetos con iniciadas a null-esto sucede con una violenta puesta a cero del área de memoria del objeto a cero. se advierte que tiene una clase base (esto es lo que la palabra clave extends indica). En el procedo de cargarla. prt("j = " + j). return 47.x2 initialized"). la segunda clase base será cargada y así sucesivamente.k initialized").k initialized k = 47 j = 39 La primera cosa que sucede cuando se ejecuta Java en Beetle es que se intenta acceder Beetle. } } public class Beetle extends Insect { int k = prt("Beetle. En este punto. j = 0 Beetle. Después que el constructor de la clase base se completa.System.class). pero se puede también especificar la llamada al constructor de la clase base (como la primera operación del constructor Beetle()) utilizando super.out. así es que primero se busca el código compilado para la clase Beetle (esto sucede que esta en un fichero llamado Beetle. public static void main(String[] args) { prt("Beetle constructor").

generalmente se quiere rediseñar la jerarquía de clases antes de permitir que otros programadores comiencen a depender de ella. Modifique el ejercicio 1 de tal forma que A y B tengan constructores con argumentos en lugar de los constructores por defecto.com . disponible por una pequeña propina en www. Finalmente. Cree dos clases. Escriba un 2. se puede realizar una conversión ascendente a la base. Resumen Herencia y composición permiten crear un nuevo tipo a partir de tipos existentes. 254 Pensando en Java www. Herede una nueva clase llamada C de A.BruceEckel. A y B. 1. con constructores por defecto (listas de argumentos vacías) que se anuncien a si mismos. el resto del cuerpo de constructor es ejecutado. cuando se comienza a diseñar generalmente se prefiere la composición al principio y utilizar herencia solo cuando es claramente necesaria. La meta es una jerarquía en donde cada clase tenga un uso específico y no sean ni muy grandes (que abarquen mucha funcionalidad que es muy difícil de reutilizar) tampoco de manera irritante pequeña (no se puede utilizar por si mismo o sin agregar funcionalidad). A pesar de que la reutilización de código a través de la composición y la herencia es útil para un desarrollo rápido del proyecto. se puede cambiar el tipo exacto. y herencia cuando se quiere reutilizar la interfase. y de esta manera el comportamiento. se puede cambiar el comportamiento del objeto compuesto en tiempo de ejecución. Ejercicios La solución de los ejercicios seleccionados pueden encontrarse en el documento electrónico The T h i n k i n g i n J a v a A n n o t a t e d S o l u t i o n G u i d e. que es crítico para realizar polimorfismo. A pesar del fuerte énfasis de la herencia en la programación orientada a objetos. utilizando el artificio de la herencia con el tipo de miembro. Composición tiende a ser mas flexible. de aquellos objetos miembro en tiempo de ejecución. sin embargo. Además.las variables de la instancia son inicializadas en orden textual.BruceEckel.com . y cree un objeto de clase C dentro de C. como se verá en el siguiente capítulo. Por lo tanto. Dado que la clase derivada tiene la interfase de la clase base. se utiliza composición para reutilizar tipos existentes como parte de la implementación de las capas mas bajas del nuevo tipo. No cree un constructor para C. Cree un objeto de la clase C y observe los resultados. Típicamente.

255 . Cree una clase simple. Cree una clase con un método que sea sobrecargado tres veces. y Component3. Use inicialización “lazy” para crear una instancia de este objeto. En los constructores de la clase derivada. y (b) llamados antes del constructor de la clase derivada. 3.constructor para C y realizar todas las inicializaciones con el constructor de C. En los métodos Car. 5. Todas las clases deben tener constructores por defecto que impriman un mensaje acerca de la clase. Fuera del paquete. agréguese una nueva sobre escritura del método. Cree una clase dentro de un paquete. Herede una nueva clase. y derive la clase con ambos. 15.java y comente el constructor para la clase Chess. Tome el fichero Chess. uno constructor por defecto y otro que no sea por defecto. Pruebe que por defecto el compilador crea por usted constructores. Cree una clase llamada Root que contenga una instancia para cada una de las clases (las que se deben crear también) llamada Component1 . Derive de Root una clase Stem que contenga una instancia de cada “componente”. llama al constructor de la clase base. Dentro de una segunda clase. 12. 11. 6. Herede una nueva clase a partir de la clase Detergent. 14. Sobre escriba scrub() y agregue un nuevo método llamado sterilize(). Cree una clase base con un solo constructor que no sea por defecto. defina un campo para un objeto de la primer clase. Pruebe que los constructores de la clase base son (a) siempre llamados. Explique que sucede. y muestre cual de todos esos cuatro métodos está disponible en la clase derivada.java y comente el constructor para la clase Cartoon. Agregue una organización de métodos cleanup() adecuados para todas las clases del ejercicio 11. 4. intente llamar el método protegido y explique los resultados. Tome el fichero Cartoon. Modifique el ejercicio 10 así cada clase solo tienen constructores que no sean por defecto. 8. 7. Component2. Explique que sucede.java agregue un método service() de Engine y llame a este método en main(). Ahora herede de la clase y llame a el método protegido desde adentro de un método de la clase derivada. 9. 10. La clase debe contener un método protegido. 13.

Desde esta. Modifique el ejercicio 16 de tal forma que Frog sobre escriba las definiciones de métodos de la clase base (proporcione nuevas definiciones utilizando las mismas firmas). Coloque métodos apropiados en la clase base. En el main(). Herede desde esta clase y intente sobreescribir ese método. y demuestre que todos los métodos siguen trabajando. y que no puede ser cambiada luego de que es inicializada. Cree una clase final e intente heredar de ella. 22.16. En Beetle.BruceEckel. Cree una clase llamada Amphibian. 17. 23. Observe que sucede en main(). 21. seguido del mismo formato que la clase existente. Cree una clase con un campo estático final y un campo final y demuestre la diferencia entre los dos. herede una clase llamada Frog. Cree una clase con una referencia final en blanco a un objeto. 19. Cree una clase con un método final. Demuestre la garantía que la referencia final debe ser inicializada antes de utilizarse. 20. cree un Frog y realice una conversión ascendente de este a Anphibian. Rastréela y explique la salida.java. herede un tipo específico de escarabajo (beetle) de una clase Beetle. Realice la inicialización del final en blanco dentro de un método (no del constructor) justo antes de utilizarlo.com . 18. Pruebe que la carga de una clase se realiza solo una vez. 256 Pensando en Java www. Pruebe que esa carga puede ser producida por la creación de una primera instancia de la clase o al acceder a un miembro estático.

siempre y cuando ambos sean derivados del mismo tipo base. Esta habilidad es crítica porque permite que muchos tipos (derivados del mismo tipo base) sean tratados como si fueran de un solo tipo. En el capítulo anterior. Revisión de conversión ascendente En el capítulo 6 se puedo ver como un objeto puede ser utilizado como su propio tipo o como un objeto de su tipo base. En este capítulo. para desacoplar que de como.7: Polimorfismo Polimorfismo es la tercera característica esencial de la programación orientada a objetos. La implementación oculta separa la interfase de la implementación haciendo los detalles del tipo private. El método polimórfico llamado permite un tipo para expresar su distinción de otro. con ejemplos simples que aclararán todo menos el comportamiento polimórfico del programa. Este tipo de mecanismos de organización tiene sentido para alguien una base de programación procesal. luego de la abstracción de datos y la herencia. Tomar una referencia a un objeto y tratarla como una referencia a su tipo base es llamado conversión 257 . La encapsulación crea nuevos tipos de datos combinando características y comportamientos. Este promete otra dimensión de separación de la interfase de la implementación. y un mismo código trabajara todos esos diferentes tipo de la misma forma. Pero el polimorfismo maneja el desacople en términos de tipos. se aprenderá acerca de polimorfismo (también llamado enlazado dinámico o enlazado diferido o enlazado en tiempo de ejecución) comenzando por lo básico. Polimorfismo permite mejorar la organización del código y la legibilidad de la misma forma que facilita la creación de programas extensibles que pueden “crecer” no solo durante la creación original del proyecto sino que también cuando se desean nuevas herramientas. se puede ver como se permite el tratamiento del objeto por su tipo o por su tipo base. Esta distinción es expresada a través de diferencias en el comportamiento de los métodos que de llaman a través de la clase base. tipo similar.

Porque alguien intencionalmente se olvida del tipo de objeto? Esto es lo que sucede cuando se realiza una 258 Pensando en Java www.tune() acepta una referencia a Instrument.println("Wind. } } // Los objetos Wind son instrumentos // porque ellos tienen la misma interfase: class Wind extends Instrument { // Redefinición del método de la interfase: public void play(Note n) { System. El realizar una conversión ascendente de Wind a Instrument puede “estrechar” la interfase. porque Wind se hereda de Instrument.. se puede que sucede si una referencia a Wind es pasada a tune( ).out. class Note { private int value.play()"). sin conversión necesaria. En el main(). B_FLAT = new Note(2). } public static final Note MIDDLE_C = new Note(0).play(Note. pero también cualquier cosa derivada de Instrument. que es mostrado en el siguiente código: //: c07:music:Music. class Instrument { public void play(Note n) { System. } } public class Music { public static void tune(Instrument i) { // .out.MIDDLE_C).java // Herencia & upcasting.println("Instrument. } public static void main(String[] args) { Wind flute = new Wind(). la interfase en Instrument debe existir en Wind.com . C_SHARP = new Note(1). i. tune(flute). // Upcasting } } ///:~ El método Music. porque la forma en que los árboles de herencia son dibujados es con la clase base en la parte mas alta.ascendente. Se puedo ver también que se origina un problema. } // Etc.. private Note(int val) { value = val. Olvidándose del tipo de objeto ¿Este programa puede ser extraño.BruceEckel.play()"). Esto es aceptable. pero no se puede tener menos que la interfase completa de Instrument.

out.play(Note.out.java // Sobrecargar en lugar de realizar upcasting.out.play(Note. Si seguimos este razonamiento y agregamos instrumentos Stringed y Brass: //: c07:music2:Music2. class Note { private int value. } } public class Music2 { public static void tune(Wind i) { i.println("Instrument. } } class Stringed extends Instrument { public void play(Note n) { System. } 259 .play()"). } public static void main(String[] args) { Wind flute = new Wind(). C_SHARP = new Note(1).play()"). private Note(int val) { value = val. Stringed violin = new Stringed().MIDDLE_C).MIDDLE_C).play()").println("Wind.play(Note.println("Stringed. y se ve mejor si tune() simplemente toma una referencia Wind como su argumento. tune(frenchHorn). } } class Wind extends Instrument { public void play(Note n) { System. class Instrument { public void play(Note n) { System. } // Etc. Brass frenchHorn = new Brass(). no se necesita escribir un nuevo tune() para cada tipo de Instrumento en su sistema.out. // No upcasting tune(violin).println("Brass.MIDDLE_C). tune(flute). B_FLAT = new Note(2). } public static final Note MIDDLE_C = new Note(0). } } class Brass extends Instrument { public void play(Note n) { System.play()"). Esto plantea un punto esencial: Si se hace esto. } public static void tune(Stringed i) { i.conversión ascendente. } public static void tune(Brass i) { i.

Enlazado de llamadas a método Conectar una llamada a un método a el cuerpo de un método es llamado enlazado. ayuda examinar el tema de enlazado (binding). ¿No sería mas agradable si se pudiera simplemente escribir un solo método que tome la clase base y su argumento. Así es que como es posible que el compilador conozca que esta referencia a Instrument apunta a Wind en este caso y no a Brass o Stringed? El compilador no puede hacerlo. si hay alguno).com . Agregado el echo de que el compilador no dará ningún mensaje de error si se olvida sobrecargar algún método y la totalidad del trabajo con los tipos se vuelve inmanejable.MIDDLE_C). Para lograr un entendimiento mas profundo de este tema. y nada de la clase derivada específica? Esto es. muchos programadores que tienen una base de programación procesal tienen pequeños problemas con la forma en que el polimorfismo trabaja. i. Los compiladores C solo tienen un tipo de llamada a métodos.} ///:~ Esto trabaja.play().BruceEckel...java pueden verse ejecutando el programa. 260 Pensando en Java www. y esto es enlazado con anticipación. La vuelta La dificultad con Music. } Este recibe una referencia Instrument. Esto claramente es la salida deseada. Sin embargo. esto es llamado enlazado con anticipación (early binding) Es posible que no se haya escuchado el . término antes porque nunca ha sido una opción en lenguajes procesales. Vea el método tune(): public static void tune(Instrument i) { // . Cuando se realiza un enlazado antes que el programa se ejecute (por el compilador y el enlazador.play(Note. La salida es Wind. se tiene un gran trabajo para hacer. pero tiene un gran inconveniente: Debe escribir métodos específicos para cada nueva clase Instrument que se agregue. no sería agradable si se pudiera olvidar que hay clases derivadas y escribir el código para manejarse solo con la clase base? Esto es exactamente lo que el polimorfismo permite hacer. pero esto no parece tener sentido que vaya a funcionar de esa forma. pero también significa que si quiere agregar un nuevo método como tune() o un nuevo tipo de Instrumento. Esto significa mucho mas programación en primer lugar.

pero desafortunadamente puede confundir a los programadores principiantes y 261 . o mas bien indica al compilador que el enlazado dinámico no es necesario. el compilador no sabe el tipo de objeto. Enlaces a última hora son llamados también enlaces dinámicos (dynamic binding) o enlaces en tiempo de ejecución(run-time binding). que significa que el enlace sucede en tiempo de ejecución basado en el tipo de objeto. y no como un intento de mejorar el rendimiento. El ejemplo de programación orientada a objetos clásico es el de la “curva”. Tal vez mas importante.La parte confusa de los programas anteriores gira alrededor del enlazado con anticipación porque el compilador no puede saber el método correcto a llamar cuando es solo una referencia a Instrument. Sin embargo. O puesto de otra forma. para prevenir que alguien sobre escriba el método. en muchos casos no se realizar ninguna diferencia de rendimientos global en el programa. Esto es comúnmente utilizado porque es fácil de visualizar. Cuando un lenguaje implementa enlazado a última hora. Esto es. Los mecanismos de enlazado a última hora varían de lenguaje a lenguaje. se “envía un mensaje a un objeto y se deja que el objeto se figure que es lo correcto para hacer”. así es que es mejor utilizar final como una decisión de diseño. La solución es llamada enlazado a última hora(late binding). debe tener algún mecanismo para determinar el tipo del objeto en tiempo de ejecución y llamar al método apropiado. Todos los métodos de enlazado de Java utiliza enlazado a última hora a menos que sea declarado como final. pero el mecanismo de llamada a el método lo encuentra y llama al cuerpo del método apropiado. Esto permite al compilador generar al compilador general código ligeramente mas eficiente para las llamadas a métodos finales. pero se puede imaginar que algún tipo de información debe ser instalada en los objetos. ¿Por que se declararía un método final? Como se ha notado en el último capítulo. Esto significa que por lo general no se necesita hacer ninguna decisión acerca de si se debe suceder un enlazado a última hora debe ocurrir -esto sucede automáticamente. Produciendo el comportamiento adecuado Una vez que se conoce como todos los métodos se enlazan en Java sucede de forma polimórfica mediante enlazado a última hora. efectivamente “desactiva” el enlazado dinámico. se puede escribir el código para comunicarse con la clase base y saber que todas las clases derivadas trabajaran correctamente utilizando el mismo código.

Supóngase que se llama un método de la clase base (que han sido sobre escrito en la clase derivada): s. y aún esta bien porque un Ciculo es una Curva por herencia. que parecería ser un error (asignación de un tipo a otro).BruceEckel. Cuadrado . El siguiente ejemplo lo coloca de una forma ligeramente diferente: //: c07:Shapes. Nuevamente.dibujar() adecuado es llamado gracias a el enlazado a última hora (polimorfismo).hacer que piensen que la programación orientada a objetos es para programación gráfica el cual no es claro el caso. etc. una referencia a una Curva -así es que ¿como puede el compilador saber hacer otra cosa? Y a pesar de todo el Circulo. La razón de que el ejemplo trabaje tan bien es la facilidad para decir “un circulo es un tipo de curva” y entender los diagramas que muestran las relaciones: La conversión puede ocurrir en una instrucción tan timple como: Curva s = new Circulo().com . El ejemplo de las curvas es la clase base llamada Curva y vario tipos derivados: Circulo. Aquí. class Shape { 262 Pensando en Java www. después de todo. Así es que el compilador esta de acuerdo con la instrucción y no emite un mensaje de error. un objeto Circulo es creado y la referencia que resulta es inmediatamente asignada a Curva. se debe esperar que el método dibujar() de Curva sea llamado porque esto es.dibujar().java // Polymorphism in Java. Triangulo.

erase()").out.println("Triangle.println("Square.random() * 3)) { default: case 0: return new Circle(). La clase principal Shape contiene un método estático llamado randShape() que produce una referencia a un objeto del tipo Shape seleccionado de forma aleatoria cada vez que se lo llama. } } class Triangle extends Shape { void draw() { System. i++) s[i].println("Circle.out. Se puede ver que la conversión 263 .println("Circle.draw()"). } void erase() { System.out.draw()"). i++) s[i] = randShape(). } } public static void main(String[] args) { Shape[] s = new Shape[9]. Las clases derivadas sobre escriben estas definiciones para proporcionar un único comportamiento para cada tipo específico de forma.draw()"). todas las curvas pueden ser dibujadas y borradas. } void erase() { System.out. // Fill up the array with shapes: for(int i = 0. case 2: return new Triangle().draw().out.erase()"). i < s. } void erase() { System. } } ///:~ La clase base Shape establece la interfase común para todas las cosas heredadas de Shape -esto es. i < s. } } public class Shapes { public static Shape randShape() { switch((int)(Math.println("Square.length.out.erase()"). case 1: return new Square().void draw() {} void erase() {} } class Circle extends Shape { void draw() { System. } } class Square extends Shape { void draw() { System.length.println("Triangle. // Make polymorphic method calls: for(int i = 0.

o Triangle y la envía fuera de los métodos como tipo de retorno. pero no sabe nada mas específico acerca de ellas (y tampoco el compilador). Sin embargo. Así es que cada vez que se llama a este método nunca se tendrá la posibilidad de saber que tipo específico es. Considere que sucede si se toma el ejemplo de los instrumentos y se agrega mas métodos en la clase base y en algunos de las clases nuevas. dado que siempre se entrega una referencia Shape plana. main() contiene un arreglo de referencias Shape que es llenado mediante llamadas a randShape().draw() Square. Los métodos que manipulan la interfase de la clase base no necesitarán ser cambiados en absoluto para acomodarse a las nuevas clases. Shape. En este punto se debe conocer que se tienen Curvas.draw() Claro que.draw() Square. cuando se mueve a través del arreglo y se llama a draw() para cada uno. como se puede ver en el ejemplo de salida: Circle. que toman una referencia a un Circle.ascendente se sucede en cada una de las instrucciones return. He aquí un diagrama: 264 Pensando en Java www. se pueden agregar la cantidad de tipos que se deseen a el sistema sin cambiar el método tune(). cada vez que se ejecute se tendrán resultados diferentes.com . muchos de todos los métodos seguirán el modelo de tune() y se comunicarán solamente con la interfase de la clase base.draw() Circle. Square. dado que las curvas son elegidas de forma aleatoria cada vez. Como un programa es extensible porque se puede agregar nuevas funcionalidades heredando nuevos tipos de datos de una clase base en común.draw() Triangle. Gracias al polimorfismo. Todas las llamadas a draw() son echas a través de enlazado dinámico. El punto de elegir las curvas de forma aleatoria es persuadir a entender que el compilador puede no tener especial conocimiento que le permita hacer la llamada correcta en tiempo de ejecución.draw() Square.draw() Circle.draw() Triangle.draw() Circle. En un programa orientado a objetos bien diseñado.BruceEckel. el comportamiento correcto del tipo mágicamente se sucede. Extensibilidad Ahora vamos a regresar a el ejemplo de los instrumentos musicales.

out.println("Viento.Todas estas nuevas clases funcionan correctamente con el viejo.println("Instrumento. import java.ejecutar()"). class Instrument { public void ejecutar() { System. } public void ajuste() {} } 265 . } public void ajuste() {} } class Viento extends Instrumento { public void ejecutar() { System.*.java // An extensible program. Aún si ajuste() se encuentra en un fichero separado y nuevos métodos son agregados a la interfase de Instrumento.util.out. He aquí una implementación del siguiente diagrama: //: c07:music3:Music3. método ajuste() sin modificar.ejecutar()"). } public String que() { return "Instrumento". ajuste() trabaja correctamente sin compilar nuevamente. } public String wque() { return "Viento".

println("Viento.. } public void ajuste() { System. } public void ajuste() {} } class Metal extends Viento { public void ejecutar() { System. así es que los nuevos // tipos agregados al sistema seguirán trabajando: static void ajustar(Instrumento i) { // .ajustar()"). int i = 0.println("Percusion.out. i++) afinar(e[i]). i < e.println("Cuerdas.println("Metal.com . } public void ajuste() {} } class Stringed extends Instrumento { public void ejecutar() { System.ejecutar()"). i. afinarTodo(orquesta). // Upcasting mientras se agrega al arreglo: orquesta[i++] = new Viento().ejecutar()"). } static void ajustarTodo (Instrumento[] e) { for(int i = 0.out. } public String que() { return "Madera"..ejecutar()"). orquesta[i++] = new Percusion(). } } ///:~ Los nuevos métodos son que(). orquesta[i++] = new Cuerdas(). 266 Pensando en Java www.length. que retorna una referencia a una cadena con una descripción de la clase. } public static void main(String[] args) { Instrumento[] orquesta = new Instrumento[5]. } public String que() { return "Cuerdas". orquesta[i++] = new Madera().ejectuar()"). } } class Madera extends Viento { public void ejecutar() { System.BruceEckel. y ajustar() que proporciona alguna forma de ajustar cada instrumento. } public String que() { return "Percusion".ejecutar().out.out.out.class Percusion extends Instrumento { public void ejecutar() { System. orquesta[i++] = new Metal(). } } public class Music3 { // No importa el tipo.println("Metal.

En el main().out. } class InstrumentoX { public void ejecutar(int NotaX) { System. Puesto de otra forma. } } public class VientoError { public static void afinar(InstrumentaX i) { // . el polimorfismo es una de las técnicas mas importantes para permitir al programador “separar las cosas que cambian de las cosas que permanecen iguales”.out.ejecutar()"). Sobrescribir frente a sobrecargar Si observamos de forma diferente el primer ejemplo de este capítulo. } public static void main(String[] args) { VientoX flauta = new VientoX(). y de la misma forma trabaja correctamente.println("VientoX. Se puede ver que el método ajuste() es felizmente ignorante de todos los cambios de código que se suceden a su alrededor. El compilador permite sobrecargar métodos sin quejarse.java // Accidentalmente cambia la interfase. cuando se coloca algo dentro del arreglo de Instrumento se está realizando automáticamente una conversión ascendente a Instrumento. } } class VientoX extends InstrumentoX { // OOPS! Se cambia la interfase del método: public void ejecutar(NotaX n) { System. En el siguiente programa. lo que significa que no se esta sobrescribiendo el método.ejecutar(NotaX.. He aquí un ejemplo: //: c07:WindError. afinar(flauta). i. Esto es exactamente lo que se supone que el polimorfismo proporciona. Los cambios den código no causan daños a partes del programa que suelen no ser afectadas. Pero el comportamientos probablemente no es lo que se desea. en lugar de esto se está sobrecargando. la interfase del método ejecutar() es cambiada en el proceso de sobre escritura.ejecutar(NotaX n)"). C_SOSTENIDO = 1. C_BEMOL = 2.println("InstrumentoX.. // No es el comportamiento correcto! } 267 .C_MEDIO). class NotaX { public static final int C_MEDIO = 0.

En InstrumentoX. que será diferenciado del nombre de clase. ciertamente parece ser una llamada a un método polimórfico. asume que se esta sobrecargando y no sobrescribiendo que era lo que se pretendía. Debe notarse que si se sigue la convención de nombres del estándar de Java. Clases y métodos abstractos En todos los ejemplos de instrumentos. Pero en VientoX. el identificador del argumento será notaX (con ‘n’ minúscula). Puesto que NotaX contiene definiciones del tipo int.com .BruceEckel. y dado que no ha sido sobrecargada la versión de la clase base es utilizada. Este establece una forma básica.ejecutar() Esto. El compilador. aun cuando NotaX es un nombre de clase. Una vez que se entienda que esta sucediendo. que lo distinguiría del nombre de clase. Esto es. Esto es porque el intento de Instrumento es crear una interfase común para todas las clases derivadas de esto. el Instrumento i envía el mensaje ejecutar(). el InstrumentoX i. pero hay que imaginarse la dificultad de puede ser encontrar el error si esta sepultado en un programa de considerable tamaño. esto significa que la versión int del ahora sobrecargado método play() es llamada. se puede solucionar el problema de forma bastante simple. La salida es: InsturmentoX. ejecutar() se toma una referencia a NotaX que es un identificador n (A pesar de que se podría decir ejecutar(NotaX NotaX) sin un error).} ///:~ Aquí hay otro aspecto confuso presentado aquí. sin embargo. esta puede ser utilizada como identificador sin problema. el método ejecutar() toma un int que identifica la NotaX. se estará haciendo algo mal. los métodos en la clase base Instrumento eran siempre métodos “ficticios”. En ajustar. La única razón para establecer esta interfase común es que pudo haberse expresado diferente para cada subtipo. De esta manera esto aparece como que el programador intenta sobreescribir ejecutar() pero equivocándose un poco al escribirlo. En ajustar. 268 Pensando en Java www. Si estos métodos son alguna vez llamados. así es que se puede decir que es común con todas las clases derivadas. sin uno de los miembros NotaX (C_MEDIO) como argumento. Otra forma de decir esto es llamar a Instrumento una clase base abstracta(o simplemente una clase abstracta Se puede crear una clase abstracta ).

el compilador entrega un mensaje de error).cuando se quiera manipulando un grupo de clases a través de esta interfase común. ¿Si una clase abstracta esta incompleta. esto es el análogo de las funciones virtuales puras. y no una implementación en particular. lo que probablemente no es lo que se quiera). solo tiene una declaración y no un cuerpo de método. que es lo que el compilador supuestamente hace cuando alguien intenta hacer un objeto de esa clase? No se puede crear de forma segura un objeto de una clase abstracta. y se quiera prevenir alguna instancia de la clase. 1 Para los programadores C++. Esto es útil cuando se tiene una clase que no tiene sentido que tenga algún método abstracto. y probablemente se quiera prevenir a el usuario de hacer esto. se deben proporcionar definiciones de métodos para todos los métodos abstractos de la clase base. Esto es. Si una clase contiene uno o mas métodos abstractos. Una clase que contiene un método abstracto es llamada una clase abstracta . Todos los métodos de la clase derivada que se corresponden con la firma de la declaración de la clase base serán llamados mediante el mecanismo de enlazado dinámico (sin embargo. así es que crear un objeto Instrumento no tiene sentido. si el nombre del método es el mismo que la clase base pero el argumento es diferente. se tiene que sobrecargar. los objetos de esa clase nunca tendrán significado. Esto se puede lograr haciendo que todos los métodos en Instrument impriman errores. Es siempre mejor capturar los problemas en tiempo de compilación. pero esto retrasa la información hasta el momento de ejecutar y requiere una prueba exhaustiva y confiable por parte del usuario. De esta forma el compilador se asegura de la pureza de la clase abstracta. Si se hereda de una clase abstracta y se quiere crear objetos del nuevo tipo. Java proporciona un mecanismo para hacer esto llamado el método abstracto1 . Esto es un método que esta incompleto. Es posible crear una clase como abstract sin incluir algún método abstracto. como se ha visto en la última sección. Instrumento trata de expresar solo la interfase. Si no se hace (y se puede elegir no hacerlo). la clase debe ser calificada como abstract (De otra forma. entonces la clase derivada es también abstracta y el compilador lo forzará a calificar esa clase con la palabra clave abstract. 269 . y no se necesitamos preocuparnos de desaprovecharla. Ha aquí la sintaxis para la declaración de métodos abstractos: abstract void f(). así es que se obtendrá un mensaje de error del compilador. Si se tiene una clase abstracta como Instrumento.

public String que() { return "Instrumento".java // Clases abstractas y métodos.*.println("Viento. import java. } public abstract void ajuste(). Así es como se vería: Vemos aquí el ejemplo orquesta modificado para utilizar clase y métodos abstractos: //: c07:music4:Music4.out. } public String que() { return "Viento". abstract class Instrumento { int i.ejecutar ()"). Solo algunos de los métodos serán abstractos. // espacio asignada para cada uno public abstract void ejecutar().com . } public void ajuste() {} 270 Pensando en Java www. dado que hacer una clase abstracta no fuerza a hacer todos los métodos abstractos. } class Viento extends Instrumento { public void ejecutar() { System.util.BruceEckel.La clase Instrument puede fácilmente ser convertida en una clase abstracta.

i < e.out. } static void afinarTodo(Instrument[] e) { for(int i = 0. } public void ajuste() {} } class Cuerdas extends Instrumento { public void ejecutar() { System.ejecutar()").println("Madera. } } class Madera extends Viento { public void ejecutar() { System. 271 . orquesta[i++] = new Cuerdas(). orquesta[i++] = new Percusion()..out.out.println("Metal.ajuste()"). así es que los nuevos // tipos agregados al sistema seguirán trabajando bien: static void afinar(Instrumento i) { // . } public String que() { return "Stringed". i++) afinar(e[i]).println("Metal. orquesta[i++] = new Metal().out. } public String que() { return "Percusion".out. } public String que() { return "Madera". } public void ajuste() {} } class Metal extends Viento { public void ejecutar() { System.. } } ///:~ Se puede ver que no hay realmente cambios a no ser en la clase base.ejecutar(). afinarTodo(orquesta).println("Percusion. // Upcasting mientras se agrega al arreglo: orquesta[i++] = new Viento(). orquesta[i++] = new Madera(). int i = 0. } } public class Music4 { // No importan los tipos.ejecutar()").println("Cuerdas.ejecutar()"). } public void ajuste() { System. i.ejecutar()").length.} class Percusion extends Instrumento { public void ejecutar() { System. } public static void main(String[] args) { Instrumento[] orquesta = new Instrumento[5].

y le indican al usuario y al compilador como se pretende que sea utilizada. Eso tiene sentido porque el constructor tiene un trabajo especial: ver que el objeto sea creado adecuadamente. y no a aquellos de la clase base (cuyos miembros son típicamente privados). Orden de llamada a constructores El orden de llamada a constructores fue brevemente discutido en el capítulo 4 y nuevamente en el capítulo 6.com . Esto es también verdadero cuando se involucra polimorfismo. de otra forma el objeto no será construido en su totalidad. class Meal { Meal() { System.out. encadenando hacia arriba la jerarquía de herencia así es que un constructor para cada clase base es llamado. Por consiguiente es esencial que todos los constructores sean llamados. Será silenciosamente llamado el constructor por defecto si no se llama explícitamente un constructor de la clase base en el cuerpo de la clase derivada. el compilador se quejará (En el caso donde una clase no tiene constructores. los constructores son diferentes de otro tipo de métodos.BruceEckel. es importante entender la forma en que los constructores trabajan en jerarquías complejas y con polimorfismo. Aún cuando los constructores no son polimórficos (a pesar que se pueda tener un tipo de “constructor virtual”. } 272 Pensando en Java www.java // Orden de llamadas a constructor. Una clase derivada tiene acceso a sus propios miembros solamente. Solo el constructor de la clase base tiene el conocimiento adecuado y accede a inicializar sus propios elementos.Es útil crear clases abstractas y métodos porque ellos hacen la abstracción de la clase explícita. y polimorfismo en el orden de construcción: //: c07:Sandwich. Echemos un vistazo en un ejemplo que nos muestra los efectos de la composición. pero esto fue antes que el polimorfismo fuera introducido. Constructores y polimorfismo Como es usual. Esto es porque el compilador fuerza la llamada del constructor para cada porción de la clase derivada. el compilador automáticamente sintetizará un constructor por defecto). Si no hay constructores por defecto. como se verá en el capítulo 12).println("Meal()"). Entender esto ayudará a evitar enredos desagradables. Un constructor para la clase base es siempre llamado en el constructor para la clase derivada. herencia.

que releja tres niveles de herencia (cuatro.println("Lettuce()").out. Sandwich() { System.} } class PortableLunch extends Lunch { PortableLunch() { System. Lettuce l = new Lettuce(). El constructor de la clase base es llamado. si cuenta la herencia implícita de Object) y tres objetos miembro. Cheese c = new Cheese(). Cuando un objeto Sandwich es creado en el main(). hasta que la última clase derivada es alcanzada.out. Este paso es repetido de forma recursiva de tal forma que la raíz de la jerarquía es construido primero. } } ///:~ Este ejemplo crea una compleja clase por afuera de otras clases y cada clases tiene un constructor que se anuncia a si mismo.println("PortableLunch()"). } } class Lettuce { Lettuce() { System.out.. La clase importante es Sandwich. 2. Los inicializadores de miembros son llamados en el orden de su declaración. seguido de la siguiente clase derivada. etc.println("Bread()").println("Lunch()"). la salida es: Meal() Lunch() PortableLunch() Bread() Cheese() Lettuce() Sandwich() Esto significa que el orden de las llamadas a el constructor para un objeto complejo es el siguiente: 1.out. } } class Sandwich extends PortableLunch { Bread b = new Bread(). } public static void main(String[] args) { new Sandwich().println("Sandwich()"). } } class Lunch extends Meal { Lunch() { System.out.println("Cheese()").} class Bread { Bread() { System.out. } } class Cheese { Cheese() { System. 273 .

sin embargo. Entonces cuando se esta en el constructor de la clase derivada. class DoBaseFinalization { public static boolean flag = false. Cuando se sobrescribe finalize() en una clase heredada.3. Cuando se hereda. Dentro del constructor. como se verá en la siguiente sección. sin embargo. La única forma de garantizar esto es que el constructor de la clase base sea llamado primero. es importante recordar llamar a la versión de finalize() de la clase base. la construcción ya se ha realizado. así es que todos los miembros de todas las partes del objeto han sido construidos. dado que de otra forma la finalización de la clase base no se sucederá. debe inicializar todos los objetos miembro (esto es. Characteristic(String c) { s = c. todos los miembros a los cuales se puede acceder en la clase base son inicializados. esto no se puede manejar en todos los casos.println( "Creating Characteristic " + s). ayudará a estar seguro de que todas los miembros clase y objetos del objeto actual han sido inicializados. se debe ser capaz de asumir que todos los miembros que se están utilizando han sido armados.BruceEckel. objetos colocados en la clase utilizando composición) en el punto de definición en la clase (por ejemplo. se conoce todo acerca de la clase base y se puede acceder a cualquier miembro sea público o protegido de la clase base. El orden de las llamadas a los constructores es importante. Desafortunadamente. Si se sigue esta práctica.com . En un método normal. y de esta manera es recolectada su basura y el finalizado independiente de que sea un miembro de la clase o no. cuando quiera que sea posible. El siguiente ejemplo prueba esto: //: c07:Frog. System. “El conocimiento de que todos los miembros son válidos” dentro del constructor es también la razón de que. se debe sobrescribir finalize() en la clase derivada si una limpieza especial debe suceder como parte de la recolección de basura. Cada miembro es un objeto independiente.java // Prueba de finalización con herencia. uno no se preocupa nunca de finalizar los objetos miembro de esa clase. } protected void finalize() { 274 Pensando en Java www. Esto significa que se deba ser capaz de asumir que todos los miembros de la clase base son válidos cuando se esta en la clase derivada. El cuerpo del constructor de la clase derivada es llamado. c. Con la herencia. b. } class Characteristic { String s. Herencia y finalize() Cuando se utiliza composición para crear una nueva clase. y l en el ejemplo anterior).out.

println("Animal()"). } } public class Frog extends Amphibian { Frog() { System.finalize().println( "LivingCreature finalize"). LivingCreature() { System.System.println("Frog finalize").out.finalize(). // Llama a la versión LAST de la clase base! if(DoBaseFinalization.flag) super. if(DoBaseFinalization.println( "finalizing Characteristic " + s).out.out.finalize(). } protected void finalize() throws Throwable { System. } protected void finalize() throws Throwable { System.println("Amphibian()"). } } class Animal extends LivingCreature { Characteristic p = new Characteristic("has heart").println("Frog()").println("LivingCreature()").flag) super. if(DoBaseFinalization.out. } } class LivingCreature { Characteristic p = new Characteristic("is alive").println("Animal finalize").println("Amphibian finalize").finalize(). } protected void finalize() throws Throwable { System. 275 . if(DoBaseFinalization. } } class Amphibian extends Animal { Characteristic p = new Characteristic("can live in water").length != 0 && args[0].flag) super.out. Animal() { System.out.flag = true.out.flag) super.out. Amphibian() { System.equals("finalize")) DoBaseFinalization. } protected void finalize() throws Throwable { System. } public static void main(String[] args) { if(args.out.

Pero si agrega el argumento “finalize” en la línea de comandos. que ningún finalizador es llamado para la clase base de Frog (el objeto miembro es finalizado. la salida es: Not finalizing bases Creating Characteristic is alive LivingCreature() Creating Characteristic has heart Animal() Creating Characteristic can live in water Amphibian() Frog() Bye! Frog finalize finalizing Characteristic is alive finalizing Characteristic has heart finalizing Characteristic can live in water Se puede ver. Esta bandera es fijada basada en un argumento colocado en la línea de comandos. // Fuerza la llamada de los finalizadores: System. Cada método finalize() sobrescrito debe tener acceso como mínimo a los miembros protegidos dado que el método finalize() en la clase Object es del tipo protected y el compilador no permitirá reducir el accedo durante la herencia (“amigable” es menos accesible que protected).out.main(). En Frog. se obtiene: Creating Characteristic is alive LivingCreature() Creating Characteristic has heart Animal() Creating Characteristic can live in water 276 Pensando en Java www. // Instantly becomes garbage System. ciertamente.finalize().gc(). los objetos miembro de Chara cteristic son siempre finalizados.else System.gc() dispara la recolección de basura y de ese modo la finalización.println("Bye!"). } } ///:~ La clase DoBaseFinalization simplemente mantiene una bandera que le indica a clase en la jerarquía donde llamar a super. así se puede ver el comportamiento con y sin la finalización de la clase base. Recuerde que la recolección de basura -y en particular la finalización. así es que hay que hacer cumplir esto.com . Cada clase en la jerarquía también contiene un objeto miembro de la clase Characteristic . el la bandera DoBaseFinalization es fijada y un único objeto Frog es creado. Sin una finalización de la clase base. Se puede ver que independientemente de donde los finalizadores del la clase base sean llamados. la llamada a System. new Frog().puede no sucederse para cualquier objeto en particular.out.BruceEckel.println("Not finalizing bases"). como se esperaba).

Ese no es el caso exactamente. que es el orden inverso de inicialización. ¿Qué sucede si se esta dentro de un constructor y se llama a un método comprometido dinámicamente del objeto que se construye? Dentro de un método común se puede imaginar que sucederá -la llamada comprometida dinámicamente es resuelta en tiempo de ejecución porque el objeto no puede conocer cuando pertenece a la clase donde esta el método o a alguna clase derivada de esta. el efecto puede ser mas bien inesperado. sin embargo. El mejor orden para utilizar es el mostrado aquí. pero no se puede saber que clases han sido heredadas. así es que no se deben destruir prematuramente. se debe realizar la finalización de la clase derivada primer. Con la clase base. el trabajo del constructor es traer a el objeto a la existencia (lo que es apenas una hazaña mediocre). Dentro de cualquier constructor. Conceptualmente. Comportamiento de los métodos polimórficos dentro de los constructores La jerarquía de llamadas a constructores plantea un dilema interesante. luego la finalización de la clase base. técnicamente el orden de finalización de los objetos no esta especificado. Para ser consistente se puede pensar que esto sucederá dentro de los constructores. se tiene control sobre el orden de finalización. Una llamada a un método comprometido dinámicamente. Esto es porque la finalización de la clase base puede llamar algunos métodos en la clase base que requiere que los componentes de la clase base sigan vivos. Siguiendo la forma utilizada en C++ por los destructores. y puede esconder alguna dificultad para encontrar errores. sin embargo. Sin embargo. llega “hasta el exterior” de la 277 . el objeto entero puede ser parcialmente formado -se puede conocer solo que objetos de la clase base han sido inicializados. Si se llama a un método comprometido dinámicamente dentro de un constructor. la definición sobrescrita para ese método es utilizada.Amphibian() Frog() bye! Frog finalize Amphibian finalize Animal finalize LivingCreature finalize finalizing Characteristic is alive finalizing Characteristic has heart finalizing Characteristic can live in water A pesar de que el orden de los objetos miembro y finalizadores es el mismo orden en que han sido creados.

} } public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5). System.println("Glyph() after draw()"). radius = " + radius). radius = 5 Cuando el constructor de Glyph llama a draw().out. El proceso actual de inicialización es: 278 Pensando en Java www.BruceEckel.println( "RoundGlyph. el método draw() es abstracto. radius = 0 Glyph() after draw() RoundGlyph.draw(). Este llama a un método en una clase derivada. Pero se debe ver la salida: Glyph() before draw() RoundGlyph. } } class RoundGlyph extends Glyph { int radius = 1. Pero el constructor de Gliph llama este método.out.draw(). y esto es la clave para solucionar el misterio. tratando de entender por que el programa no funciona. Es 0. RoundGlyph(int r) { radius = r.com . el valor de radius no es si quiera el valor por defecto 1. Se puede ver el problema en el siguiente ejemplo: //: c07:PolyConstructors.RoundGlyph(). abstract class Glyph { abstract void draw(). Si se hace esto dentro de un constructor. radius = " + radius).java // Constructors and polymorphism // don't produce what you might expect.jerarquía de herencia. } void draw() { System.draw(). así es que esta diseñado para ser sobrescrito. se esta forzado a sobrescribirlo en RoundGlyph . y la llamada para en RoundGlyph. Esto probablemente puede resultar en un punto o nada de nada dibujado en la pantalla. Glyph() { System. que parece ser el objetivo.out. El orden de inicialización descrito en la sección previa no es bastante completo. la llamada a un método que puede manipular miembros que no han sido inicializados todavía -una receta segura para desastres. draw(). Claro que. } } ///:~ En Glyph .RoundGlyph(). y se estará abandonado con la mirada fija.println( "RoundGlyph. System.out.println("Glyph() before draw()").

Todo lo demás se pone a cero. Los inicializadores de miembros son llamados en el orden de la declaración. el método sobrescrito draw() es llamado (si. 2. Diseñando con herencia Una vez que se ha aprendido polimorfismo. quien encuentra un valor de radius cero. antes que el constructor de RoundGlyph sea llamado). “Hágase lo menos posible para llevar a un objeto en un estado correcto. Se ha realizado una cosa lógicamente correcta. Los constructores de la clase base son llamados como se ha descrito anteriormente. las cosas comienzan a complicarse innecesariamente. Esto puede agobiar los diseños. Los únicos método seguros para llamar dentro de un constructor son aquellos que son finales en la clase base (Esto también se aplica a métodos privados. Así es que si se olvida inicializar esa referencia se obtendrá un error en tiempo de ejecución.1. cuando no es tan obvio lo que se utilizará. En este punto. debido a el paso 1. Los errores como este pueden ser fácilmente enterrados y se toma un largo tiempo descubrirlos. El cuerpo del constructor de la clase derivada es llamado. una buena guía para los constructores es. Pero la composición es también mas flexible dado que es posible elegir dinámicamente un tipo (y su comportamiento) cuando utilizamos 279 . Hay una razón para esto. Por otro lado. de hecho si se elige herencia primero cuando se esta utilizando una clase para hacer una nueva clase. se debería estar un poco horrorizado del resultado de este programa. El espacio asignad para el objeto es inicializado al binario cero antes de que nada mas suceda. 3. 4. sin quejas del compilador (C++ produce un comportamiento mas racional en esta situación). no llame ningún método”. Estos no pueden ser sobrescritos y no pueden producir este tipo de sorpresa. Como resultado. que es que todo es al menos inicializado a cero (o lo que sea que cero signifique para un tipo particular de dato) y no solamente queda como basura. Una mejor estrategia es elegir composición primero. Esto incluye referencias a objetos que son incrustadas dentro de una clase mediante composición. que es usualmente un valor revelador cuando observamos en la salida. y aun el comportamiento es misteriosamente equivocado. que son automáticamente finales). el cual comienza en null. y si es posible evitarlo. se puede ver que todo debe ser heredado porque polimorfismo es como una herramienta ingeniosa. La composición no fuerza un diseño a una jerarquía de herencia.

s. El siguiente ejemplo ilustra esto: //: c07:Transmogrify. que es inicializado como un objeto HappyActor. Una línea general es “Use herencia para expresar diferencias en el comportamiento. y campos para expresar variaciones de estado”. En el ejemplo anterior. Pero dado que una referencia puede ser enlazado nuevamente a un objeto diferente en tiempo de ejecución.println("HappyActor").BruceEckel.out.change(). } void go() { a. } } class Stage { Actor a = new HappyActor(). el cual es posible bajar de www.java // Dinámicamente cambia el comportamiento de // un objeto mediante composición.com . En contraste. De esta manera se gana flexibilidad dinámica en tiempo de ejecución (Esto es llamado el Patrón Estado. Vea Pensando en Patrones con Java.act().out.com).println("SadActor"). no se puede decidir heredar de forma distinta en tiempo de ejecución. } class HappyActor extends Actor { public void act() { System. eso debe ser completamente determinado en tiempo de compilación.composición. abstract class Actor { abstract void act(). } } class SadActor extends Actor { public void act() { System. una referencia para el objeto SadActor puede ser sustituida en a y entonces el comportamiento producido por go() cambia. y Stage utiliza composición para permitir que el estado sea cambiado. ambos son utilizados: dos diferentes clases son heredadas para expresar la diferencia en el método act(). } } public class Transmogrify { public static void main(String[] args) { Stage s = new Stage().go(). // Prints "HappyActor" s. 280 Pensando en Java www. En este caso. // Prints "SadActor" } } ///:~ Un objeto Stage contiene una referencia a un Actor.BruceEckel. void change() { a = new SadActor().go(). Esto significa que go() produce un comportamiento particular. mientras que la herencia requiere un tipo exacto que debe ser conocido en tiempo de compilación. ese cambio en el estado se sucede para producir un cambio en el comportamiento. s.

se pudo ver que la forma mas ordenada de crear una jerarquía de herencia es tomar una aproximación “pura”. la clase base puede recibir cualquier mensajes que se pueda enviar a la clase derivada dado que las dos tienen la misma interfase. Esto puede pensado como una substitución pura. Todo lo que 281 . y nunca se necesitará conocer ningún tipo de información extra acerca de las subclases cuando se están utilizando: Esto es. La herencia garantiza que cualquier clase derivada tendrá la interfase de la clase base y nada mas. las clases derivadas también no tendrán mas que la interfase de la clase base. Si se sigue el siguiente diagrama. solo los método se han establecido en la clase base o interfase serán sobrescritos en la clase derivada. como se ve en el diagrama: Esto puede ser llamado una relación pura “es un” dado que la interfase de la clase establece que es.Herencia pura contra extensión Cuando se estudió herencia. dado que los objetos de la clase derivada pueden ser perfectamente substituidos por la clase base. Esto es.

com . desafortunadamente. Esto puede ser nombrado como una relación ”es como una” dado que la clase derivada es como la clase base -tiene la misma interfase fundamental. se dará un giro y se descubrirá que extender la interfase (lo cual. Todo es manejado mediante polimorfismo. y cualquier otro diseño confunde y es por definición inservible. La parte extendida de la interfase en la clase derivada no esta disponible desde la clase base. así es que una vez que se realiza una conversión ascendente no se puede llamar a los nuevos métodos: 282 Pensando en Java www.BruceEckel.pero tiene otras herramientas que requieren métodos adicionales para implementar: A pesar que esto es también una estrategia razonable (dependiendo de la situación) es inconveniente. se verá como que una relación pura “es una” es la única forma acertada de hacer las cosas.necesita es realizar una conversión ascendente de la clase derivada y nunca mirar atrás para fijarse cual es el tipo exacto del objeto que está manejando. Esto es una trampa también. Cuando se vea de esta manera. Tan pronto como se comience a pensar de esta forma. la palabra clave extends parece animar) es la solución perfecta para un problema particular.

se sabe que una conversión ascendente es siempre seguro. mover hacia abajo en la jerarquía de herencia. Conversión descendente e identificación de tipo en tiempo de ejecución Dado que se pierde la información del tipo específico con una conversión ascendente (moviéndose hacia arriba en la jerarquía de herencias).se utiliza una conversión descendente.Si no se esta realizando una conversión ascendente en este caso. 283 . sin embargo cada mensaje que se envía a través de la interfase de la clase base es garantido que será aceptada. no molesta. pero a menudo se tendrá una situación en donde necesitará redescubrir el tipo exacto del objeto para acceder a los métodos extendidos de este tipo. La siguiente sección muestra como se hace esto. Sin embargo. Pero con una conversión descendente. En lugar de eso podría ser un triangulo o un cuadrado o algún otro tipo. la clase base no puede tener una interfase mas grande que la clase derivada. tiene sentido recuperar el tipo de información -esto es. no se sabe realmente que una curva (por ejemplo) es actualmente un círculo.

//: c07:RTTI.BruceEckel. Este acto de verificar tipos en tiempo de ejecución es llamado identificación de tipo en tiempo de ejecución (RTTI).*. en tiempo de ejecución esta conversión es verificada para asegurar que es de hecho el tipo que se piensa que es. se obtiene una ClassCastException. pero en Java cada conversión es verificada! Así es que aún cuando se parezca que se esta realizando una conversión mediante paréntesis común y corriente.Para solucionar este problema debe haber una forma de garantizar que un conversión descendente es correcto. class Util { public void f() {} public void g() {} } class MasUtil extends Util { public void f() {} public void g() {} public void u() {} 284 Pensando en Java www. import java.util. El siguiente ejemplo demuestra el comportamiento de RTTI. En algunos lenguajes (como C++) se debe realizar una operación especial para obtener de una conversión descendente un tipo seguro.java // Conversión descendente & e identificación de // tipo en tiempo de ejecución (RTTI). Esto sería bastante peligroso.com . dado que no queremos accidentalmente convertir a el tipo equivocado y enviar un mensaje que el objeto no pueda aceptar. Si no lo es.

será exitoso. Si es el tipo correcto.f().u(). 285 .g(). // Conversión descendente/RTTI ((MasUtil)x[0]). De otra forma se obtendrá una ClassCastException. No necesita escribir código especial para esta excepción. y si se intenta llamar a u() (el cual solamente existe en MasUtil) se obtendrá un error en tiempo de compilación. x[1]. Hay mas de RTTI que la simple conversión. MasUtil extiende la interfase de Util. en lugar de eso se trabaja solamente en armonía como parte de una “gran pintura” de relaciones de clases. se podrá realizar una conversión descendente. new MasUtil() }.u(). hay una forma de ver con que tipo se esta tratando antes de que se intente realizar un una conversión descendente. o aún crear un ejemplo de polimorfismo sin utilizar abstracción de datos y herencia. x[0]. Todo el capítulo 12 es devoto del estudio de los diferentes aspectos de la identificación de tipo en tiempo de ejecución. Dado que ambos objetos en el arreglo son de la clase Util. Si se desea acceder a la interfase extendida de un objeto MasUtil.u(). se puede enviar el método f() y g() a ambos. dado que indica un error de programación que puede suceder en cualquier parte de un programa. Por ejemplo. Resumen Polimorfismo significa “formas diferentes”. Las personas a menudo se confunden con otras. Se ha visto en este capítulo que es imposible entender. se tiene la misma cara (la interfase común en la clase base) y diferentes formas de utilizar esa cara: las diferentes versiones de los métodos comprometidos dinámicamente. se puede también realizar una conversión ascendente a Util. ((MasUtil)x[1]).public void v() {} public void w() {} } public class RTTI { public static void main(String[] args) { Util[] x = { new Util(). // En tiempo de compilación: método no encontrado en Util: //! x[1]. En la programación orientada a objetos. // Lanza una excepción } } ///:~ Como en el diagrama. por ejemplo). Pero dado que es heredado. Polimorfismo es una característica que no puede ser vista aisladamente (como se puede hacer con una instrucción switch. Se puede ver que esto sucede en la inicialización del arreglo x en main().

Ejercicios La solución de los ejercicios seleccionados pueden encontrarse en el documento electrónico The Thinking in Java Annotated Solution Guide . etc. Para utilizar polimorfismo -y de esta manera técnicas de programación orientada a objetos. proporcione métodos que son comunes a todos los Roedores y proporcione estos en las clases derivadas para que realicen distintos comportamientos dependiendo del tipo específico de Roedor. disponible por una pequeña propina en www. que es a veces presentada como orientada a objetos. Finalmente. Explique que sucede. y vea que sucede. 3. 4.efectivamente en los programas se debe expandir su visión de programación para incluir no solamente miembros y mensajes de una clase individual. No hay que dejarse embaucar: si no es enlazado a última hora. Agregue un nuevo método a la clase base de Shapes. también se debe incluir el populacho de clases que están en medio y sus relaciones con cada una de las otras. Hamster. Cree una jerarquía de herencia de Roedores: Raton.com . Agregue un nuevo tipo de Instrumento a Music3. 1.java de tal forma que cree de forma aleatoria objetos Instrumento de la forma en que Shapes.com .println() (sin ninguna conversión).java de tal forma que que() se convierta en el método toString() de Object. 5. como sobrecarga de métodos.BruceEckel. porque los resultados son un desarrollo de programas mas rápido y un código mas fácil de mantener. 6.BruceEckel.java lo hace. Cambie Music3. Agregue un nuevo tipo de Shape a Shapes. es una lucha digna.características no orientadas a objetos de Java. llénelo con diferentes tipos específicos de Roedores y llame a los métodos de la clase base para ver que sucede. 286 Pensando en Java www. En la clase base. pero no lo sobrescriba en la clase derivada. Cree un arreglos de Roedores. Ahora sobrescríbala en una de las clases derivadas pero no en las otras. 2. no es polimorfismo.java y verifique en el main() que el polimorfismo trabaja para su nuevo tipo como lo hace para los viejos tipos. Trate de imprimir el objeto Instrumento utilizando System. A pesar que esto requiere un esfuerzo significante. Gerbo. Modifique Music3.java y verifique que el polimorfismo trabaja para su nuevo tipo. sobrescríbala en todas las clases derivadas.out.java que imprime un mensaje.

Cree un método estático que tome una referencia a la clase base. 10. realice una conversión ascendente a el tipo base. 15. Modifique el ejercicio 6 para demostrar el orden de inicialización de la clase base y las clases derivadas. Cree una clase con dos métodos. Cree una clase base con un método abstract print() que sobrescribe en la clase derivada. Agregue una clase Picke a Sandwich. llámese el segundo método. 9. Herede una clase y sobrescriba el segundo método. Explique que sucede. Hágase los método de Roedor abstracto cuando sea posible. demuestre que esto funciona en la clase base. la clase base y las clases derivadas. 287 .java. 14. Cree un objeto en la clase derivada.7. y llame el primer método. En el main(). Derive la clase y agregue un método. y debe llamar de forma adecuada la versión de finalize() de la clase base. 11. Demuestre que su jerarquía trabaja propiamente. y luego llame a el método print(). Cree una clase como abstracta sin incluir ningún método abstracto.java. Ahora agréguese objetos miembros a ambas. cree una clase Starship que contenga una referencia AlertStatus que pueda indicar tres diferentes estados. realice una conversión descendente a la clase derivada. Siguiendo el ejemplo en Transmogrify. En el primer método. y llame el método. llame este método. y verifique que no se pueda crear una instancia de esa clase. La versión sobrescrita del método imprime el valor de una variable int definida en la clase derivada. Cree una clase abstracta sin método. En el punto de la definición de esta variable. En el constructor de la clase base. 8. Cada clase en la jerarquía debe tener un método finalize(). y muéstrese el orden en que sus inicializaciones ocurren durante la construcción. En el main() cree un objeto del tipo derivado. 12. Incluya los métodos para cambiar esos estados. Cree una jerarquía de herencia de tres niveles. 13. Modifique el ejercicio 6 para que Roedor sea una clase abstracta. de esta manera elimine la necesidad del realizar una conversión descendente. Explique los resultados. asígnele un valor que no sea cero.

pero estos son 288 Pensando en Java www. Interfases La palabra clave interface toma el concepto de abstracción un paso mas adelante. C++. por ejemplo. sin embargo. Se aprenderá que la interfase es mas que simplemente una clase abstracta tomada al extremo. creando una clase en la que se pueda realizar una conversión ascendente a mas de un tipo base. En el capítulo 7.com . una que no proporciona ningún tipo de implementación. Puede pensar que es como una clase abstracta ”pura”. dado que esta permite realizar una variación sobre “herencias múltiples” de C++. En un principio. a pesar de que es un nuevo concepto para la mayoría.y entonces el tipo de código que se puede escribir con clase interiores es mas elegante y claro. Esto permite al creador establecer la forma de una clase: nombres de métodos. que las clases internas hacen mas que eso -conocen y pueden comunicarse con las clases circundantes. El echo de que existan en Java indica que lo han considerado suficientemente importante para proporcionar soporte directo a través de palabras claves del lenguaje. las clases internas de ven mas como una mecanismo simple de ocultar código: se colocan clases dentro de otras clases. a pesar que el programador listo puede simularlas.8: Interfases y clases internas Las interfases y las clases internas proporcionan los métodos mas sofisticados de organización y control de los objetos en su sistema. La palabra clave interface produce una clase completamente abstracta. no contiene este tipo de mecanismos. que es creada por herederos. listas de argumentos. y tipo de retornos.BruceEckel. pero no el cuerpo de los métodos. Toma un tiempo sentirse confortable con el diseño utilizando clases internas. una interfase puede almacenar también campos. Se aprenderá. se aprendió acerca de la palabra clave abstracta . que permite crear uno o mas métodos en una clase que no tenga definiciones -se proporciona una parte de la interfase sin proporcionar la implementación correspondiente.

pero no la implementación. Para crear una interfase. El diagrama para el ejemplo de los instrumentos se muestra aquí: 289 . cualquier código que utilice una interfase particular sabrá que métodos deben ser llamados para esa interfase. Se estará diciendo “La interfase es como se ve pero ahora especificaremos como ella trabaja”. Así es que la interfase es utilizada para establecer un “protocolo” entre clases (Algunos lenguajes de programación orientada a objetos tienen una palabra clave llamada protocol para hacer la misma cosa). De esta forma. y esto es todo.implícitamente estáticos y finales. se utiliza la palabra clave interface en lugar de la palabra clave cla ss. Una interfase dice: “Esto es como todas las clases que implementen esta interfase particular se verán”. se puede agregar la palabra clave public antes de la palabra clave interface (pero solo si la interfase es definida en un fichero con el mismo nombre) o no colocarla para darle la el estado de “amigable” así solo puede ser utilizable dentro del mismo paquete. Para hacer que una clase se ajuste a una interfase en particular (o a un grupo de interfases) se debe utilizar la palabra clave implements. Una interfase proporciona solo una forma. Como una clase. se ve como una herencia. Aparte de eso.

lo que no es permitido por el compilador Java. } public String que() { return "Cuerdas".out. // static & final // No se puede tener definiciones de métodos: void ejecutar().println("Viento. } public void ajustar() {} } class Cuerdas implements Instrumento { public void ejecutar() { System.util. son públicos aún si no se indica.BruceEckel. } public void ajustar() {} } class Percusion implements Instrumento { public void ejecutar() { System. los métodos de la interfase deben ser definidos como públicos.ejecutar()"). interface Instrumento { // Constante en tiempo de compilación: int i = 5. import java.println("Metal.java // Interfaces. ninguno de los métodos en Instrumento es declarada como público. Esto se puede ver en esta versión modificada del ejemplo de Instrumento. Así es que cuando se implemente una interfase. y se reducirá la accesibilidad de un método durante la herencia.ejecutar()"). que es la única cosa que el compilador permite. esa implementación se convierte en una clase común que puede ser extendida de una forma regular.com .*. } class Viento implements Instrumento { public void ejecutar() { System. Además. pero son automáticamente públicos de todas formas: //: c08:music5:Music5. // Automáticamente público String que(). Pero estos. Se puede elegir explícitamente hacer las declaraciones de métodos en una interfase como públicos. Se puede ver que cada método en la interfase es estrictamente una declaración. De otra forma serán por defecto “amigables”.ejecutar()").println("Percusion.println("Cuerdas. } public String que() { return "Viento".out.ejecutar()"). } 290 Pensando en Java www. } public String que() { return "Percusión".Una vez que se ha implementado una interfase.out. } public void ajuste() {} } class Metal extends Viento { public void ejecutar() { System. void ajustar().out.

orquesta[i++] = new Percusion(). De hecho.out. no tiene espacio asociado con la interfase.. i < e. una clase abstracta o una interfase.public void ajustar() { System.no hay nada que impida que muchas interfases sean combinadas.ejecutar()"). no importa si se esta realizando un upcast a una clase “común” llamada Instrumento. “Herencias múltiples” en Java La interfase no es simplemente una forma “mas pura” de una clase abstracta. } public String que() { return "Madera".length. a una clase abstracta llamada Instrument o a una interfase llamada Instrument. Tiene un propósito mas elevado que ese. i. En C++.. Este el objetivo: cada estrategia le da a el programador un control diferente sobre la forma en que los objetos son creados y utilizados. i++) afinar(e[i]). } } public class Music5 { // No importa el tipo. Esto es de mucha importancia dado que hay veces en donde se necesita decir “Una x es una a y una b y una c”.ajustar()"). orquesta[i++] = new Madera(). Puesto que una interfase no tiene ningún tipo de implementación -esto es.println("Metal. así es que los nuevos // tipos agregados al sistema funcionaran igual: static void afinar(Instrumento i) { // . // Upcasting durante la asignación a el arreglo: orquesta[i++] = new Viento(). int i = 0. } public static void main(String[] args) { Instrumento[]orquesta = new Instrumento[5]. El comportamiento es el mismo.ejecutar(). orquesta[i++] = new Cuerdas().println("MAdera. este acto de 291 . afinatTodo(orquesta). orquesta[i++] = new Metal().out. } } ///:~ El resto del código trabaja de la misma forma. } } class Madera extends Viento { public void ejecutar() { System. se puede ver en el método afinar() que no hay ninguna evidencia acerca de si Instrumento es una clase “común”. } static void afinarTodo(Instrument[] e) { for(int i = 0.

fight(). } 292 Pensando en Java www. y mas bien acarrea algún tipo de equipaje incómodo dado que cada clase puede tener una implementación. interface CanFight { void fight(). así los problemas que se ven en C++ no se suceden con Java cuando se combinan múltiples interfases: En una clase derivada. Si se realiza una herencia de una clase que no es una interfase.combinar múltiples interfases de clases es llamado herencia múltiple. En Java. Se puede tener tantas interfases como se desee -cada una se convierte en un tipo independiente al que se pueda realizar un upcast. CanFly { public void swim() {} public void fly() {} } public class Adventure { static void t(CanFight x) { x.com . pero solo una de las clases puede tener implementación. Todo el resto de los elementos base deben ser interfases. CanSwim. El siguiente ejemplo muestra una clase concreta combinada con muchas interfases para producir una nueva clase: //: c08:Adventure. } interface CanSwim { void swim(). se puede realizar el mismo acto.swim(). no se esta forzado a tener una clase base que sea abstracta o “concreta” (una sin métodos abstractos).*. solo se puede heredar una.BruceEckel.java // Multiple interfaces. import java. } class ActionCharacter { public void fight() {} } class Hero extends ActionCharacter implements CanFight. } static void u(CanSwim x) { x. } interface CanFly { void fly().util. Se coloca todos los nombres de interfase luego de la palabra clave implements y separados con comas.

debe ser una clase con todas las definiciones proporcionadas. la primer elección es hacer una interfase y solo si se esta forzado a tener definiciones de métodos o variables miembro se debe cambiar a una clase abstracta. Si se quiere crear un objeto con el nuevo tipo. luego las interfases (El compilador da un error de otra forma). En la clase Adv enture.fly(). Dado que la forma en que las interfases son planeadas en Java. Esta regla para una interfase es que se debe heredar de ella (como se verá en breve). 293 . De hecho. si se sabe que algo va a ser una clase base. } public static void main(String[] args) { Hero h = new Hero(). // Treat it as an ActionCharacter } } ///:~ Se puede ver que Hero combina la clase concreta ActionCharacter con las interfases CanFight. una segunda razón para utilizar interfases es la misma que utilizar una clase abstracta: prevenir que el cliente programador de crear un objeto de esta clase y establecer que solo es una interfase. lo que significa que se realiza un upcast para cada interfase a su vez.fight(). // Treat it as a CanSwim v(h). la clase concreta debe ir primera. // Treat it as a CanFight u(h). Esto trae una pregunta: ¿Se debe utilizar una interfase o una clase abstracta? Una interfase nos da los beneficios de una clase abstracta y los beneficios de una interfase. Aún cuando Hero no proporcione explícitamente una definición para fight(). esto trabaja sin dificultades y sin un esfuerzo particular por parte del programados. así es que es posible crear una clase base sin ninguna definición de método o variables miembro así es que es preferible siempre utilizar una interfase que una clase abstracta. Se debe tener presente que la razón principal de las interfaces es mostrada en el ejemplo mas arriba: ser capaz de realizar un upcast a mas de un tipo base. este puede ser pasado por cualquiera de estos métodos. la definición viene con ActionCharacter así es que es automáticamente proporcionada y es posible crear un objeto de Hero. } static void w(ActionCharacter x) { x. t(h). Sin embargo. y CanFly . y que fight() no es proporcionada con una definición en Hero.static void v(CanFly x) { x. Se puede ver que la firma para fight() es la misma en la interfase CanFight y la clase ActionCharacter. // Treat it as a CanFly w(h). Cuando un objeto Hero es creado. pero entonces no tiene otra interfase. Donde se combina una clase concreta con interfases de esta forma. o si es necesario a una clase concreta. se puede ver que hay cuatro métodos que toma como argumentos las diversas interfases y la clase concreta. CanSwim .

y las funciones sobrecargadas no pueden diferenciarse solo por el tipo de retorno. } interface I3 { int f(). } } class C2 implements I1. } // overloaded } class C4 extends C implements I3 { // Identical. } interface I2 { int f(int i). ambas definidas en f ().com .BruceEckel. implementación y sobrecarga están desagradablemente mezcladas juntas.java:23: f() en C no puede implementar f() en I1. el mensaje de error lo dice todo: InterfaceCollision. CanFight y ActionCharacter tienen un método exactamente igual llamado void fight(). I3 {} ///:~ La dificultad se sucede a causa de que la sobreescritura. Esto no es un problema porque el método es idéntico en ambos casos. } class C { public int f() { return 1. I2 { public void f() {} public int f(int i) { return 1.Colisión de nombres cuando se combinan interfases Se puede encontrar una pequeña dificultad cuando se implementan múltiples interfases. } // overloaded } class C3 extends C implements I2 { public int f(int i) { return 1. intento de utilizar tipos de retorno incompatibles encontrado : int requerido: void InterfaceCollision.java interface I1 { void f(). ¿Pero que sucede si no lo es? He aquí un ejemplo: //: c08:InterfaceCollision.java:24: interfases I3 y I1 son incompatibles. En el ejemplo anterior. no problem: public int f() { return 1. Cuando se les quita el comentario a las dos últimas líneas. Hay que esforzarse por evitarlos. pero con diferentes tipos de retorno Utilizando los mismos nombres de métodos en interfases diferentes las cuales se intenta combinar generalmente causan confusión en la legibilidad del código. 294 Pensando en Java www. } } // Methods differ only by return type: //! class C5 extends C implements I1 {} //! interface I4 extends I1.

v(if2). 295 . La sintaxis utilizada en Vampire trabaja solo cuando las interfases son heredadas. } static void v(DangerousMonster d) { d. extends se puede referir a muchas interfases base cuando se esta creando una nueva interfase. interface Monster { void menace(). d. } public static void main(String[] args) { DragonZilla if2 = new DragonZilla().Extendiendo una interfase con herencia Se puede fácilmente agregar declaraciones de métodos a una interfase utilizando herencia. } } ///:~ DangerousMonster es una simple extensión de Monster que produce una nueva interfase. Lethal { void drinkBlood().java // Extending an interface with inheritance.menace(). En ambos casos se obtiene una nueva interfase como se puede ver en este ejemplo: //: c08:HorrorShow. pero dado que una interfase puede ser creada con muchas otras interfases.destroy(). } interface Lethal { void kill(). se puede utilizar extends con una sola clase. Esta es implementada en DragonZilla . } class HorrorShow { static void u(Monster b) { b. y se puede también combinar muchas interfases en una sola con herencia. } interface DangerousMonster extends Monster { void destroy().menace(). los nombres de las interfases son simplemente separados con comas. } class DragonZilla implements DangerousMonster { public void menace() {} public void destroy() {} } interface Vampire extends DangerousMonster. u(if2). Normalmente. Como se puede ver.

com . MAY = new Month2("May").* o co8. MAR = new Month2("March"). MARCH = 3. } public String toString() { return name. public interface Months { int JANUARY = 1. Ahora se puede utilizar las constantes fuera del paquete importando co8. JUNE = 6. OCTOBER = 10. package c08.JANUARY. private Month2(String nm) { name = nm. } public final static Month2 JAN = new Month2("January"). AUGUST = 8. El campo en una interfase es público automáticamente.BruceEckel. APR = new Month2("April"). se puede crear una clase como esta 1 : //: c08:Month2. pero esta (comúnmente utilizada) técnica es ciertamente una mejora sobre los números muy codificados en los programas (cuya estrategia es referida a menudo como utilizar “números mágicos” y produce código muy difícil de mantener). y referenciar los valores con expresiones como Months. así es que no hay la seguridad de tipo que las enumeraciones de C++ tiene. public final class Month2 { private String name.Months simplemente de la misma forma en que se haría con otro paquete. } ///:~ Se debe advertir el estilo de Java de utilizar todos las letras en mayúsculas (con infragiones para separar múltiples palabras en un mismo identificador) para estáticas finales que tienen inicializadores constantes. Si se quiere seguridad de tipo extra. APRIL = 4.java // A more robust enumeration system. MAY = 5. SEPTEMBER = 9. tanto como se haría con un tipo enum en C o en C++. la interfase es una herramienta conveniente para crear grupos de valores constantes. FEB = new Month2("February"). package c08. así es que no es necesario especificarlo. DECEMBER = 12. Por ejemplo: //: c08:Months.Agrupación de constantes Dado que los campos que se colocan dentro de una interfase son automáticamente estáticos y finales. lo se obtendrá simplemente será un entero.java // Using interfaces to create groups of constants. 1 Esta estrategia fue inspirada en un correo electrónico de Rich Hoffarth 296 Pensando en Java www. FEBRUARY = 2. Claro. JULY = 7. NOVEMBER = 11.

APR. Estos no pueden ser “finales en blanco”. System. System. MAR .JUN = new Month2("June").println(m. FEB. AUG. que deja elegir meses por números en lugar de nombrándolos (Se debe ver que el JAN extra en el arreglo para proporcionar un desplazamiento en uno. m = Month2. Las únicas instancias son la estática final creada en la clase misma: JAN . public final static Month2[] month = { JAN.DEC)). etc. AUG = new Month2("August"). JUL = new Month2("July"). Inicializando cambos en interfases Los campos definidos en interfases son automáticamente estáticos y finales. como es mostrado en el final del main(). OCT = new Month2("October"). NOV.out. } } ///:~ La clase es llamada Month2. DEC }. public static void main(String[] args) { Month2 m = Month2.java // Initializing interface fields with // non-constant initializers.month[12].DEC).util.out. double rdouble = Math.random() * 10).random() * 10.equals(Month2. float rfloat = (float)(Math. En el main() se puede ver la seguridad del tipo: m es un objeto Month2 así es que este puede ser asignado solo a Month2.out. Es una clase final con un constructor privada así es que no se puede heredar o crear alguna instancia de ella.random() * 10).println(m). DEC = new Month2("December"). import java.*. MAR. System. System.out.println(m).JAN. Por ejemplo: //: c08:RandVals. OCT. así el mes de diciembre es el número 12). así es que a una variable entera que pretende representar a un mes se le puede actualmente dar cualquier valor entero. Estos objetos son también utilizados en el arreglo month. SEP. long rlong = (long)(Math.random() * 10). NOV = new Month2("November"). pero pueden ser inicializados con expresiones no constantes. JUN. En el ejemplo anterior Months2. dado que ya hay una clase Month en la librería estándar de Java. 297 . JAN. public interface RandVals { int rint = (int)(Math. MAY.java proporciona solo números enteros. FEB. JUL. SEP = new Month2("September"). Esta estrategia también permite utilizar == o equals() de forma intercambiable. lo cual no es muy seguro.println(m == Month2.

lo que sucede cuando alguno de los campos es accedido la primera vez. System.java public class TestRandVals { public static void main(String[] args) { System. clase.rfloat). } class CImp implements C { public void f() {} } private class CImp2 implements C { public void f() {} } private interface D { void f(). son inicializados cuando la clase es cargada por primera vez. Ha aquí una simple prueba: //: c08:TestRandVals. } } ///:~ El campo.BruceEckel.rlong).out.out.} ///:~ Dado que los campos son estáticos. System. } public class BImp implements B { public void f() {} } private class BImp2 implements B { public void f() {} } public interface C { void f().rint).println(RandVals.println(RandVals.out. no es parte de la interfase pero en lugar de eso es almacenado en un área estática para la interfase. System. } private class DImp implements D { public void f() {} } public class DImp2 implements D { public void f() {} } public D getD() { return new DImp2(). Esto deja al descubierto una gran cantidad de características muy interesantes: 2 class A { interface B { void f().rdouble). Interfases anidadas Las interfases pueden ser anidadas con clases y con otras interfases.println(RandVals.println(RandVals.com .out. } 2 Gracias a Martin Danner por hacer esta pregunta durante un seminario 298 Pensando en Java www.

receiveD(a. // Only another A can do anything with getD(): A a2 = new A().getD().getD(). // Can't access A. } void g(). a2.C { public void f() {} } // Cannot implement a private interface except // within that interface's defining class: //! class DImp implements A.getD(). // Cannot access a member of the interface: //! a.G { public void f() {} } } public static void main(String[] args) { A a = new A().B { public void f() {} } class CImp implements A.D: //! A.D: //! A. // Cannot be private within an interface: //! private interface I {} } public class NestingInterfaces { public class BImp implements A. // Doesn't return anything but A.getD()).f().D ad = a. public void receiveD(D d) { dRef = d. dRef. } // Redundant "public": public interface H { void f().DImp2 di2 = a.D { //! public void f() {} //! } class EImp implements E { public void g() {} } class EGImp implements E.private D dRef.f(). } } interface E { interface G { void f(). } } ///:~ 299 .G { public void f() {} } class EImp2 implements E { public void g() {} class EG implements E.

Se puede también ver que las interfases anidadas públicas y “amigables” pueden ser implementadas como clases anidadas públicas. otro A. todos los cuales fallan. mediante el método received(). se descubre frecuentemente lugares donde es útil. pero generalmente encuentro que una vez que se conoce acerca de la característica. se puede ver muchos intentos de utilizar este valor de retorno. no se requiere implementar ninguna interfase anidada con ella. ¿Que tan bueno es una interfase anidada privada? Se puede adivinar que solo puede ser implementada como clase anidada privada como en D I m p. ¿Que se puede hacer con el valor de retorno de este método? En el main(). También.DImp2 muestra que también puede ser implementada como una clase pública. Sin embargo. A. sin permitir el realizar una conversión ascendente). Sin embargo. así es que una interfase anidada con otra interfase es automáticamente pública y no puede ser hecha privada. Inicialmente. 300 Pensando en Java www. En particular. estas características pueden verse como que son agregadas estrictamente por un tema de consistencia sintáctica. que todos los elementos de las interfases deben ser públicos. Como un nuevo giro imprevisto. las reglas acerca de interfases -en particular.son estrictamente implementadas aquí. NestingUnterfaces muestra las distintas formas en que se pueden implementar las interfases anidadas. así es que implementar una interfase privada es la forma de forzar la definición de los métodos en esa interfase sin agregar ningún tipo de información (esto es. pero A. La única cosas que funciona es si el valor de retorno es manejado por un objeto que tenga permiso de utilizarlo -en este caso.BruceEckel.com . El método getD() produce un futuro dilema concerniente a la interfase privada: es un método público que retorna una referencia a una interfase privada. las interfases privadas no pueden ser implementadas fuera de su definición de clases. No se está permitido mencionar el hecho de que se implementa la interfase privada. se debe notar que cuando se implementa una interfase.La sintaxis para anidar una interfase sin una clase es razonablemente obvia y de la misma forma que con las interfases no anidadas estas pueden tener visibilidad pública o “amigable”. “amigables” y privadas.D (el mismo requisito de sintaxis es utilizado por las interfases anidadas por lo que se refiere para clases anidadas). las interfases pueden también ser privadas como se ha visto en A. La interfase E muestra que las interfases pueden ser anidadas con cada una de las otras.DImp2 puede solo ser utilizada como esta.

Se podrá ver en un momento que esto no es únicamente la diferencia. public int value() { return i.println(d.java // Creating inner classes.out. En el final de esta sección. Sin embargo. cuando son utilizadas dentro de ship(). } } class Destination { private String label. public class Parcel1 { class Contents { private int i = 11. La clase interna es una característica valiosa porque permite agrupar clases que lógicamente permanecen juntas y para controlar la visibilidad de una dentro de otra. } public static void main(String[] args) { Parcel1 p = new Parcel1(). 301 . System. es importante entender que las clases internas son claramente diferentes de la composición. } } ///:~ Las clases internas. Aquí. la necesidad de clases interna es inmediatamente obvia.readLabel()).ship("Tanzania"). p. cuando ese esta aprendiendo acerca de ellas. } String readLabel() { return label. within Parcel1: public void ship(String dest) { Contents c = new Contents(). después de toda la sintaxis y la semántica de las clases internas sea descrita. se ven igual que cuando utilizamos cualquiera de las otras clases. } } // Using inner classes looks just like // using any other class.Clases internas Es posible colocar la definición de una clase dentro de otra definición de clase. esto es llamado una clase interna. se podrán encontrar ejemplos que hacen claro los beneficios de las clases internas. A menudo. Se crean las clases internas exactamente como se puede imaginar colocando la definición de clase dentro de una clase que la rodee: //: c08:Parcel1. Destination d = new Destination(dest). la única diferencia práctica es que los nombres son anidados dentro de Parcel1 . Destination(String whereTo) { label = whereTo.

las clases internas no parecen tan dramáticas. } } public Destination to(String s) { return new Destination(s). // Defining references to inner classes: Parcel2. si esta escondido.readLabel()). } public void ship(String dest) { Contents c = cont().cont(). public class Parcel2 { class Contents { private int i = 11.NombreDeLaClaseInternacomo se ve en el . como esta: //: c08:Parcel2. } public static void main(String[] args) { Parcel2 p = new Parcel2(). } } ///:~ Si se quiere hacer un objeto de la clase interna en cualquier lado excepto desde adentro de un método no estático de la clase externa.Contents c = q. } public Contents cont() { return new Contents(). public int value() { return i.com . 302 Pensando en Java www. Destination d = to(dest). } String readLabel() { return label. se debe especificar que el tipo de ese objeto es NombreDeLaClaseExterna. System.println(d. Destination(String whereTo) { label = whereTo. Java ya tiene un mecanismo para ocultar perfectamente bueno -simplemente permite a la clase ser “amigable” (visible solo dentro del paquete) en lugar de crearla como una clase interna. una clase externa puede tener un método que retorne una referencia a una clase interna. main(). } } class Destination { private String label. se esta detrás.to("Borneo").java // Returning a reference to an inner class.ship("Tanzania").Destination d = q.BruceEckel. Clases internas y conversión ascendente Hasta el momento. Después de todo. Parcel2.Mas típicamente. p. Parcel2 q = new Parcel2().out.

cont().puede ser completamente desconocida y no disponible para cualquiera. Todo lo que se obtiene es la referencia a la clase base de la interfase. Cuando se obtiene una referencia a la clase base o la interfase.java // Returning a reference to an inner class. } ///:~ //: c08:Contents. Contents c = p. lo que es conveniente para ocultar la implementación. como se muestra aquí: //: c08:Parcel3. que la interfase automáticamente hace todos sus miembros públicos).java public interface Destination { String readLabel(). } public String readLabel() { return label. } ///:~ Ahora Contents y Destination representan interfases disponibles para el cliente programador (Se debe recordar. las clases internas realmente se heredan en si mismas cuando se comienza a realizar un upcast a una clase base. public class Parcel3 { private class PContents implements Contents { private int i = 11. es posible que no se pueda incluso encontrar el tipo exacto. } } public Destination dest(String s) { return new PDestination(s). 303 . } public Contents cont() { return new PContents(). private PDestination(String whereTo) { label = whereTo. y en particular a una interfase (El efecto de producir una referencia a una interfase desde un objeto que la implemente es esencialmente lo mismo que realizar un upcast a la clase base). Esto es porque la clase interna -la implementación de la interfase.Sin embargo.java public interface Contents { int value(). } } class Test { public static void main(String[] args) { Parcel3 p = new Parcel3(). } } protected class PDestination implements Destination { private String label. las interfaces comunes serán definidas en sus propias ficheros así es que ellas pueden ser utilizadas en todos los ejemplos: //: c08:Destination. Primer. public int value() { return i.

extender una interfase es inútil desde la perspectiva del cliente programador dado que este no puede acceder a ningún método adicional que no sean parte de la clase de la interfase pública. En Parcel3. } } ///:~ Se debe ver que dado que main() esta en Test. mas confusas formas en las que se pueden utilizar si se desea: las clases internas pueden ser creadas sin un método o incluso con un alcance arbitrario.PContents pc = p. // Illegal -. main() debe estar en una clase separada para demostrar lo privado de la clase interna PContents. Como se ha mostrado previamente. Además. De hecho. 304 Pensando en Java www. incluso no se puede realizar un conversión descendente a una clase privada interna (o a una clase interna protegida si es una heredera).Destination d = p. En general. Sin embargo. cuando se quiera ejecutar este programa no se ejecutará Parcel3. Clases internas en métodos y alcances Lo que se ha visto hasta ahora engloba el uso típico de las clases internas.new PContents(). el código que se escribirá y leerá acerca de clases internas será de clases internas “planas” que son simples y fáciles de entender. Esto también proporciona una oportunidad para el compilador Java de generar código mas eficiente. De esta forma.can't access private class: //! Parcel3. algo nuevo ha sido agregado: la clase interior PContents es privada así es que ninguna excepto Parcel3 puede ser accedida. así es que ninguna excepto Parcel3. como se puede ver en class Test. protegido es también “amigable”). porque no se puede acceder al nombre.dest("Tanzania"). se esta implementando una interfase de algún tipo así es que se esta creando y retornando una referencia. en lugar de eso: java Test En el ejemplo.BruceEckel.com . el diseño para las clases internas es bastante completo y hay una gran cantidad de otras. las clases en el paquete Parcel3 (dado que son protegidos también conceden acceso al paquete -esto es. la clase privada interna le proporciona a el diseñador de la clase una forma para prevenir completamente dependencias de escritura de código y una forma de ocultar completamente los detalles de la implementación. Clases normales (no internas) no pueden ser hechas privadas o públicas solo públicas o “amigables”. Esto significa que el cliente programador tiene conocimiento y acceso restringido a estos miembros. PDestination es protegida. y los herederos de Parcel3 pueden acceder a PDestination. Aquí hay dos razones para hacer esto: 1.

Destination d = p. PDestination 305 . public Wrapping(int x) { i = x. Una definición de clase sin un método. } } ///:~ La clase PDestination es parte de dest() en lugar que sea parte de Parcel4 (También se advierte que se puede utilizar el identificador de clase PDestination para una clase interna adentro de cada clase en el mismo subdirectorio sin una colisión de nombres). } } return new PDestination(s). } public int value() { return i. Una clase anónima que realiza una construcción utilizando inicialización de instancias (las clases anónimas internas no pueden tener constructores) A pesar de que es una clase común con una implementación. } public String readLabel() { return label. Una clase definida dentro del alcance en un método. 6. Wrapping es también utilizado como una “interfase” común para su clase derivada: //: c08:Wrapping. public class Parcel4 { public Destination dest(String s) { class PDestination implements Destination { private String label. } public static void main(String[] args) { Parcel4 p = new Parcel4(). Por consiguiente.java public class Wrapping { private int i. private PDestination(String whereTo) { label = whereTo. Una clase anónima que realiza inicialización de campos. En los siguientes ejemplos.dest("Tanzania"). 5. el código anterior será modificado para utilizar: 1. 3. 2. Se esta resolviendo un problema complicado y se quiere crear una clase que asista en su solución. pero no quiere que este públicamente disponible. } } ///:~ Se puede advertir acerca de que Wrapping tiene un constructor que requiere un argumento. El primer ejemplo muestra la creación de una clase entera sin el alcance de un método (en lugar del alcance de otra clase): // Nesting a class within a method. para hacer las cosas un poco mas interesantes.2. Una clase anónima extendiendo una clase que tiene un constructor que no es el constructor por defecto.

no esta disponible fuera del alcance en el cual esta definida.java // Nesting a class within a scope.no puede ser accedido desde afuera de dest(). String s = ts. } // No se puede utilizar aquí! Fuera del alcance: //! TrackingSlip ts = new TrackingSlip("x"). De advierte que la conversión ascendente que ocurre en la instrucción de retorno -nada se perfila de dest() excepto una referencia a Destination.BruceEckel.getSlip(). // Punto y coma requerido en este caso } 306 Pensando en Java www. } } ///:~ La clase TrackingSlip está anidada dentro del alcance de una instrucción if. Clases internas anónimas El siguiente ejemplo se ve un poco extraño: //: c08:Parcel6. } public void track() { internalTracking(true).java // Un método que retorna una clase interna anónima. Sin embargo. public class Parcel6 { public Contents cont() { return new Contents() { private int i = 11. el hecho que el nombre de la clase PDestination sea colocado dentro de dest() no significa que PDestinationno sea un objeto válido una vez que retorne dest(). p. } public static void main(String[] args) { Parcel5 p = new Parcel5(). } }. } } TrackingSlip ts = new TrackingSlip("slip"). public int value() { return i. la clase base.track().com . TrackingSlip(String s) { id = s. public class Parcel5 { private void internalTracking(boolean b) { if(b) { class TrackingSlip { private String id. } String getSlip() { return id. Claro. El siguiente ejemplo muestra como se puede anidar una clase interna con cualquier alcance arbitrario: //: c08:Parcel5. se ve exactamente como una clase común. Aparte de esto. Esto no significa que la clase es condicionalmente creada -es compilada junto con todo lo demás.

} } ///:~ Esto es. creo que me deslizaré en una definición de clase”: return new Contents() { private int i = 11. public class Parcel7 { public Wrapping wrap(int x) { // Base constructor call: return new Wrapping(x) { public int value() { return super. “Pero espere. Una clase anónima no puede tener un constructor el cual se llamaría normalmente super(). En la clase interna anónima. 307 . } } return new MyContents(). // Semicolon required } public static void main(String[] args) { Parcel7 p = new Parcel7().wrap(10). //: c08:Parcel7. “Cree un objeto de una clase anónima que es heredada de Contents”. Wrapping w = p. se ve como si se estuviera comenzando a crear un objeto Contents: return new Contents() Pero entonces. Lo que esta extraña sintaxis es.public static void main(String[] args) { Parcel6 p = new Parcel6(). El siguiente código muestra que hacer si su clase base necesita un constructor con un argumento. } }. la clase es anónima -no tiene nombre. simplemente se pasa el argumento a el constructor de la clase base.cont().value() * 47. visto aquí como la x pasada en new Wrapping(x). se dice. Contents es creado utilizando un constructor por defecto. Para hacer las cosas un poco peor. public int value() { return i. public int value() { return i. La clase interna anónima es un atajo para: class MyContents implements Contents { private int i = 11. } }. } } ///:~ ¡El método cont() combina la creación del valor de retorno con la definición de la clase que representa ese valor de retorno! Además. Contents c = p. A la referencia retornada por la expresión new se le realiza un upcast automáticamente a una referencia Contents.java // An anonymous inner class that calls // the base-class constructor. antes de llegar al punto y coma.

es idéntico a el uso de un punto y coma en cualquier lado. el siguiente enfoque esta correcto. En lugar de eso. en . Se puede. indica el final de la expresión que sucede contiene la clase anónima. } public static void main(String[] args) { Parcel8 p = new Parcel8(). public class Parcel9 { public Destination dest(final String dest. se obtendrá un mensaje de error en tiempo de compilación. public String readLabel() { return label. // Inicializador de instancia para cada objeto: { cost = Math. 308 Pensando en Java www. el punto y coma no marca el final del cuerpo de la clase (como se hace en C++).round(price).com . no tiene nombre para darle al constructor -así es que no puede tener un constructor. ¿Que sucede si necesita ejecutar algún tipo de inicialización para un objeto de una clase interna anónima? Dado que es anónima. public class Parcel8 { // Argument must be final to use inside // anonymous inner class: public Destination dest(final String dest) { return new Destination() { private String label = dest. Destination d = p. final float price) { return new Destination() { private int cost. realizar una inicialización en el punto de la definición de sus campos: //: c08:Parcel8. Esto.dest("Tanzania"). Si se olvida. } }. Por esto es que el argumento de dest() es final.BruceEckel. A briefer version // of Parcel5. } } ///:~ Si se esta definiendo una clase interna anónima y se quiere utilizar un objeto que esta definido fuera de la clase anónima interna.java.out. el compilador requiere que el objeto de afuera sea final. efecto.java // An anonymous inner class that performs // initialization. Siempre y cuando se este asignando simplemente un campo.java // Utilización de "inicialización de instancia " para realizar // la construcción de una clase interna anónima. crear un constructor para una clase interna anónima: //: c08:Parcel9. sin embargo.En los dos ejemplos anteriores. ¿Pero que si se necesita realizar alguna actividad propia de un constructor? Con la inicialización de instancia se puede.println("Over budget!"). if(cost > 100) System.

309 . hay otra peculiaridad. El siguiente ejemplo demuestra esto: //: c08:Sequence. Object current().java // Holds a sequence of Objects. Cuando se crea una clase interna. un objeto de esa clase tiene un enlace con el objeto encerrado que lo crea. Así es que en efecto. } public class Sequence { private Object[] obs. que es simplemente un mecanismo para ocultar nombres. Destination d = p. void next(). } public static void main(String[] args) { Parcel9 p = new Parcel9(). } }. no se puede sobrecargar inicializadores de instancia así es que puede tener solo uno de estos constructores. } 3 Esto es muy diferente a el diseño de clases anidadas en C++.dest("Tanzania".} private String label = dest. Claro. next++. las clases internas tienen derechos de acceso a todos los elementos en la clase que lo encierra 3. La unión a la clase externa Hasta el momento. parece que una clase interna simplemente es una forma de ocultar nombres y un esquema de organización de código. public Sequence(int size) { obs = new Object[size]. Además. es limitada. Sin embargo. y se esta manera este puede acceder a los miembros de ese objeto que lo encierra -sin ningún requisito especial. public String readLabel() { return label. la instrucción if). lo que es útil pero no totalmente convincente. No hay unión a un objeto que lo encierra y no hay permisos implicados en C++. una instancia de inicialización es el constructor para una clase anónima interna. } } ///:~ Dentro de la inicialización de instancia se puede ver código que puede no ser ejecutado como parte de un inicializador de campo (esto es. } public void add(Object x) { if(next < obs. 101. private int next = 0. interface Selector { boolean end().length) { obs[next] = x.395F).

Selector sl = s.println(sl. } public Object current() { return obs[i]. Se puede llamar a add() para agregar un nuevo Object al final de la secuencia (si hay espacio sobrante). } } } ///:~ La Sequence es simplemente un arreglo de tamaño fijo de Object con una clase que lo envuelve.getSelector(). pero en lugar de eso es un campo private en la clase que lo encierra.end()) { System.com . while(!sl. la creación de SSelector se ve simplemente como otra clase interna. y next() se refieren a obs. la clase interna puede acceder a métodos y campos de la clase circundante como si fuera su dueña. public boolean end() { return i == obs.out.current()). el SSelector es una clase privada que proporciona funcionalidad a Selector. Al comienzo. que es una referencia que no es parte de SSelector.length.length) i++. sl.BruceEckel. Pero si la examinamos mas detalladamente. Para traer cada uno de los objetos en una secuencia. Esto vuelve a ser muy conveniente.} private class SSelector implements Selector { int i = 0. que permite ver si está al final mediante end(). En el main(). y luego el agregado de un cierto número de objetos String. un Selector es producido por una llamada a getSelector()y esto es utilizado para moverse a través de Sequence y seleccionar cada ítem. Sin embargo.add(Integer. Entonces. Dado que Selector es una interfase. i < 10. i++) s. muchas otras clases pueden implementar la interfase en sus propias formas. Se puede ver que cada uno de los métodos end(). for(int i = 0.next(). } } public Selector getSelector() { return new SSelector(). para ver el Object actual con current(). } public static void main(String[] args) { Sequence s = new Sequence(10). como se pudo ver en el ejemplo mas arriba. current(). Aquí. para crear código genérico. y moverse al siguiente Object mediante next() en Sequence. y muchos métodos pueden tomar la interfase como argumentos. } public void next() { if(i < obs. se puede ver la creación de una Sequence.toString(i)). hay una interfase llamada Selector. 310 Pensando en Java www.

campos estáticos. private PDestination(String whereTo) { 311 . La mayoría de las veces esto sucede sin ninguna intervención por parte del programador. Esto no es verdad. Las clases internas estáticas son diferentes que las clases internas no estáticas de otra forma. Sin embargo.Así es que una clase interna tiene acceso automático a los miembros de la clase que la encierra.java // Static inner classes. Una clase estática interna significa: 1. Que no se puede acceder a un objeto de una clase externa desde un objeto de una clase interna estática. } } protected static class PDestination implements Destination { private String label. así es que las clases no estáticas no pueden tener datos estáticos. Los campos y métodos en una clase interna no estática solo pueden estar en el nivel exterior de la clase. esta referencia (oculta) es utilizada para seleccionar ese miembro. public int value() { return i. cuando se dice que una clase interna es estática. y el compilador se quejará si no se tiene acceso a esa referencia. Entonces cuando se refiere a un miembro de la clase circundante. Para entender el significada de static cuando se aplica a una clase interna. pero se puede entender ahora que un objeto de una clase interna puede ser creada solo en asociación con un objeto de la clase circundante. entonces se puede hacer la clase interna del tipo static . el compilador tiene cuidado de todos esos detalles. sin embargo. Que no se necesita un objeto de una clase externa para crear un objeto de una clase interna estática. igualmente. public class Parcel10 { private static class PContents implements Contents { private int i = 11. Afortunadamente. o clases internas estática. ¿Como puede esto suceder? La clase interior debe mantener una referencia a un objeto particular de la clase circundante que es responsable por crearlo. La construcción del objeto de la clase interna requiere la referencia a el objeto de la clase circundante. Clases internas estáticas Si no se necesita una conexión entre el objeto de la clase interna y el objeto externo. las clases estáticas internas pueden tener todos estos: //: c08:Parcel10. 2. se debe recordar que el objeto de una clase interna común implícitamente mantiene una referencia a el objeto de la clase circundante que lo creó.

k. Dado que la clase es estático no viola las reglas para interfases -la clase interna estática es solo colocada dentro de los espacios de nombres de la interfase: //: c08:IInterface. sugerí colocar un main() en cada clase para que actúe como dispositivo de pruebas para esa clase. ningún objeto Parcel10 es necesario. Como se verá en breve. } // Static inner classes can contain // other static elements: public static void f() {} static int x = 10. Normalmente se puede colocar cualquier código dentro de una interfase. interface IInterface { static class Inner { int i. en una clase interna (no estática) común. static class AnotherLevel { public static void f() {} static int x = 10. Si esto es un problema. la que la hace análoga a un método estático. el enlace a el objeto de la clase externa es alcanzado con una referencia especial this. Una clase interna estática no tiene esta referencia especial this. } public String readLabel() { return label.BruceEckel. se puede utilizar una clase interna estática para almacenar el código de prueba.com . j. Un inconveniente para esto es la cantidad de código extra compilado que debe cargar por esto. } public static Contents cont() { return new PContents(). } } public static Destination dest(String s) { return new PDestination(s).java // Static inner classes inside interfaces. en lugar de eso se utiliza la sintaxis normal para seleccionar miembros estáticos para llamara a esos métodos que retornan referencias a Contents y Destination. } } ///:~ En el main(). pero una clase estática interna puede ser parte de una interfase. Destination d = dest("Tanzania").java 312 Pensando en Java www. } public static void main(String[] args) { Contents c = cont(). public Inner() {} void f() {} } } ///:~ Al comienzo de este libro. //: c08:TestBed.label = whereTo.

this. así es que no hay sobrecarga en tiempo de ejecución). Para hacer esto se debe proporcionar una referencia a el otro objeto de la clase externa en la expresión new . pero no necesita incluirla en el producto terminado. // Must use instance of outer class // to create an instances of the inner class: 313 . } } class Destination { private String label. El resultado de la referencia es automáticamente el tipo correcto (Esto es conocido y verificada en tiempo de compilación. public class Parcel11 { class Contents { private int i = 11. como esta: //: c08:Parcel11. } public static class Tester { public static void main(String[] args) { TestBed t = new TestBed(). public int value() { return i. se nombra el objeto de clase externo seguido por un punto y this. } String readLabel() { return label. Se puede utilizar esta clase para pruebas. } } } ///:~ Esto genera una clase separada llamada TestBedsTester (para ejecutar el programa. class TestBed { TestBed() {} void f() { System. Para hacer esto se debe proporcionar una referencia a el otro objeto de la clase interna.out. A veces se quiere indicar algún otro objeto para crear un objeto de uno de su clase interna.f().SSelector. } } public static void main(String[] args) { Parcel11 p = new Parcel11(). Por ejemplo. Destination(String whereTo) { label = whereTo.println("f()").// Putting test code in a static inner class.java // Creating instances of inner classes. se dice java TestBed$Tester). Haciendo referencia al objeto clase externo Si necesite producir la referencia para el objeto de la case externo. alguno de estos métodos pueden producir la referencia almacenada para la clase externa Sequence indicándola como Sequence. en la clase Sequence. t.

new A().new B().h(). Esto.A. si se crea una clase interna estática. } } ///:~ Para crear un objeto de una clase interna directamente. Sin embargo.java // Las clases anidadas pueden acceder a todos los miembros de todos // los niveles de las clases con las que son anidadse.Contents c = p.new Contents().A mnaa = mna. } } ///:~ 4 Gracias de nuevo a Martin Danner 314 Pensando en Java www. class MNA { private void f() {} class A { private void g() {} public class B { void h() { g(). mnaab.Contents c = p. Alcanzando el exterior desde una clase anidada de forma múltiple No importa cuan profunda esté anidada una clase interna -puede acceder de forma transparente a todos los miembros de todas las clases con las cuales está anidada. f(). no se debe seguir la misma forma y referenciar el nombre de la clase externa Parcel11 como se espera. pero en lugar de eso se debe utilizar un objeto de la clase externa para hacer un objeto de la clase interna: Parcel11. entonces no se necesita una referencia a un objeto de la clase externa.new Destination("Tanzania"). no es posible para crear un objeto de una clase interna a no ser que ya tenga un objeto de la clase externa. MNA.Parcel11.Destination d = p.com . MNA.new Contents().B mnaab = mnaa. Parcel11. } } } } public class MultiNestingAccess { public static void main(String[] args) { MNA mna = new MNA(). como se ve aquí: 4 //: c08:MultiNestingAccess. Esto es porque el objeto de la clase interna es silenciosamente conectado a el objeto de la clase externa que es de donde se ha creado.BruceEckel.

El problema es que la referencia “secreta” a el objeto de la clase que lo encierra debe ser inicializado. La sintaxis “. y aún en la clase derivada ya no hay un objeto por defecto a donde engancharse.A. La respuesta es utilizar una sintaxis proporcionada para hacer la asociación explícita: //: c08:InheritInner. Pero cuando es tiempo de crear un constructor.Se puede ver que en MNA.java // Inheriting an inner class. } } ///:~ Se puede ver que InheritInner se extiende solo en la clase interna.super(). class WithInner { class Inner {} } public class InheritInner extends WithInner. ¿Es posible 315 . el constructor por defecto no es bueno y no se puede simplemente pasar una referencia al el objeto que lo envuelve.new ” produce el alcance así es que no se tiene que calificar el nombre de la clase en la llamada del constructor. se debe utilizar la sintaxis referenciaALaClaseQueEnvuelve.B. Heredando de una clase interna Dado que el constructor de la clase interna debe enganchar a una referencia del objeto de la clase que la encierra. Este ejemplo también demuestra que la sintaxis necesaria para crear objetos de clases internas anidadas de forma múltiple cuando se crean los objetos en una clase diferente. no a la externa. las cosas son ligeramente complicadas cuando se hereda de una clase interna.Inner { //! InheritInner() {} // Won't compile InheritInner(WithInner wi) { wi. InheritInner ii = new InheritInner(wi). Esto proporciona la referencia necesaria y el programa compilará. Además. } public static void main(String[] args) { WithInner wi = new WithInner(). los métodos g() y f() se pueden llamar sin ninguna calificación (a pesar del hecho de que ellos son privados). ¿Pueden ser las clases internas ser sobrescritas? ¿Que sucede cuando se crea una clase interna. luego se hereda de la clase que la envuelve y se define nuevamente la clase interna? Esto es. dentro del constructor.super().

sigue siendo posible explícitamente heredar de la clase interna: //: c08:BigEgg2. pero “sobrescribir” una clase interna como si fuera otro método de la clase externa no hace nada realmente: //: c08:BigEgg. cada una en su propio espacio de nombres.Yolk() Este ejemplo simplemente muestra que no hay ninguna clase interna mágica extra continuando cuando se hereda de una clase exterior.println("New Egg()").com .Yolk()"). class Egg2 { protected class Yolk { public Yolk() { System.f()").Yolk()"). y = new Yolk(). class Egg { protected class Yolk { public Yolk() { System. } } public class BigEgg extends Egg { public class Yolk { public Yolk() { System.out.println("BigEgg.println("Egg2. } } 316 Pensando en Java www. public Egg() { System. Las dos clases internas son entidades completamente separadas. } } private Yolk y.Yolk. La salida es: New Egg() Egg.BruceEckel. la versión “sobrescrita” de Tolk será utilizada.out. y estas llamadas a el constructor por defecto de la clase base. pero este no es el caso.java // An inner class cannot be overriden // like a method.out.Yolk()"). Se puede pensar que puesto que BigEgg se esta creando. } } ///:~ Los constructores por defecto son sintetizados automáticamente por el compilador. } } public static void main(String[] args) { new BigEgg().println("Egg2. Sin embargo. } public void f() { System.java // Proper inheritance of an inner class.sobrescribir una clase interna? Esto parece como que sería un concepto poderoso.println("Egg.out.out.

e2. seguido por el nombre de la clase interna. así cundo g() llama y.java incluyen: InheritInner.f()").println("New Egg2()").Yolk. Identificadores de clases internas Dado que cada clase produce un fichero .println("BigEgg2.out.Yolk. Se puede ver que la versión sobrescrita de f()es utilizada cuando g()es llamada.println("BigEgg2.out. seguido por un ‘$’. } } public BigEgg2() { insertYolk(new Yolk()).Yolk() New Egg2() Egg2.Yolk.f().Yolk() BigEgg2.Yolk explícitamente extiendo Egg2. } } ///:~ Ahora BifEgg2. public Egg2() { System.Yolk() BigEgg2. La salida es: Egg2.f() La segunda llamada a Egg2. Los nombres de esos ficheros/clases tienen una fórmula estricta: el nombre de la clase que lo contiene.Yolk y sobrescribe sus métodos.private Yolk y = new Yolk(). } public void g() { y. se puede adivinar que es clase interna debe producir un fichero .class que contiene toda la información acerca de como crear objetos de este tipo (esta información produce una “meta clase” llamada el objeto Class). } public void insertYolk(Yolk yy) { y = yy.Yolk()"). los ficheros .out.Yolk { public Yolk() { System.class creados por InheritUnner.class para almacenar la información de sus objetos Class.g(). } } public class BigEgg2 extends Egg2 { public class Yolk extends Egg2.f() la versión sobrescrita de f() es utilizada. Por ejemplo. } public static void main(String[] args) { Egg2 e2 = new BigEgg2().class WithInner. } public void f() { System.Yolk() es la llamada a el constructor de la clase base del constructor de BigEgg2. El método insertYolk() permite que BifEgg2 realice un upcast de sus propios objetos Yolk dentro de su referencia y en Egg2.class 317 .class WithInner$Inner.

y el código en la clase interna manipula el objeto de la clase externa en cuyo interior fue creada. Si la clase interna es anidada con clases internas. las clases internas heredan de una clase o implementan una interfase. ‘$’ es un meta carácter para el shell de Unix y por esto a veces se tienen problemas cuando se listan ficheros . Así es que la razón mas convincente para las clases internas es: Cada clase interna puede independientemente heredar de una implementación. las clases internas efectivamente permiten heredar de mas de una no interfase. el compilador simplemente genera números como identificadores de la clase interna.class. pero en lugar de eso el programador se había naturalmente enfocado en los ficheros de los fuentes. los ficheros generados son automáticamente independientes de la plataforma (Véase que el compilador Java cambia sus clases internas en todo tipo de otras formas para hacer su trabajo). Dado que es el esquema de nombres estándar de Java. pero esto no responde la pregunta de por que existen. ¿Por que Sun se tomo tanto trabajo para agregar este rasgo fundamental del lenguaje? Típicamente. una companía basada en Unix. Las interfases solucionan parte del problema. Así es que se puede decir que una clase interna proporciona un tipo de ventana en la clase externa. Así es que una forma de ver la clase interna es como la solución total a el problema de la herencia múltiple. pero las clases internas efectivamente permiten “herencia de múltiple implementación”. algunos diseños y problemas de programación pueden ser intratables. ¿Por que clases internas? En este punto se ha visto un montón de sintaxis y semántica que describen la forma en que las clases internas trabajan. Esto es un poco extraño por parte de Sun.de mas de una clase concreta o abstracta. 318 Pensando en Java www. A pesar que este esquema de generar nombres internos es simple y directo. De esta manera. Una pregunta que directa al corazón de las clases interna es esta: ¿Si simplemente necesito una referencia a una interfase. sus nombres son simplemente anexados luego de un ‘$’ y el(los) identificador(es) de la clase externa. Esto es. Adivino que no consideraron este tema. es también robusto y maneja la mayoría de las situaciones5. 5 Por el otro lado.BruceEckel. la clase interna no esta limitada por el hecho de que la clase externa ya este heredada de una implementación.com .Si las clases internas son anónimas. Sin la habilidad que esa clase interna proporciona al heredar -en efecto. por que no simplemente hago que la clase externa implemente esa interfase? La respuesta es que no se puede siempre tener la conveniencia de las interfases -a veces se está trabajando con implementaciones.

Y y = new Y().makeB()).Para ver esto en mas detalle. Sin embargo. esto asume que para la estructura del código las dos formas tienen sentido. en el siguiente ejemplo la estrategia que se toma no hace mucha diferencia realmente desde un punto de vista de la implementación. takesB(x). considere una situación donde se tiene dos interfases que deben de alguna manera ser implementadas sin una clase. Pero sin otras limitaciones. normalmente se tendrá algún tipo de orientación por parte de la naturaleza del problema acerca de si utilizar una sola clase o una clase interna.java // Dos formas en la que una clase puede // implementar multiples interfases." class C {} abstract class D {} class Z extends C { D makeD() { return new D() {}. takesA(x). } } ///:~ Claro que. tiene dos alternativas: una clase simple o una clase interna: //: c08:MultiInterfaces.java // Con clases concretas o abstractas. interface A {} interface B {} class X implements A. takesA(y). Dado la flexibilidad de las interfases. takesB(y. si se tiene una clase abstracta o concreta en lugar de interfases. se esta repentinamente limitado a utilizar clases internas si su clase debe de alguna forma implementar ambas otras: //: c08:MultiImplementation. } } public class MultiImplementation { static void takesC(C c) {} static void takesD(D d) {} public static void main(String[] args) { 319 . Ambos trabajan. Sin embargo. B {} class Y implements A { B makeB() { // Anonymous inner class: return new B() {}. las clases // internas son solo la forma para producir el // efecto de "herencia de múltiple implementación. } } public class MultiInterfaces { static void takesA(A a) {} static void takesB(B b) {} public static void main(String[] args) { X x = new X().

Z z = new Z(). cada una con su propio estado de información que es independiente de la información en el objeto de la otra clase. 4. El punto de creación del objeto de la clase interna no esta ligado a la creación del objeto de la clase externa. Partiendo de esta definición. inclusive los privados. algún otro objeto entrega un pedazo de información que le permite realizar en algún punto mas adelante llamar al objeto que lo originó. se puede ver que una clase interna es un objeto orientado a cierre. takesC(z). es una entidad separada.com . En una clase externa simple. Pero con las clases internas se tiene estas características adicionales: 1. La clase interna puede tener múltiples instancias. se puede concebir código acerca de todo sin la necesidad de una clase interna. getRSelector(). Un ejemplo de esto será mostrado en breve. sin embargo. Este tipo de flexibilidad es solo capaz con las clases internas. No hay una relación posiblemente confusa de “es un” con la clase interna. Cierres y callbacks Un cierre es un objeto al que se puede llamar que retiene información acerca del alcance en el cual fue creado. Como un ejemplo. esto produce un Selector que mueve para atrás la secuencia. si Sequence. takesD(z. se puede tener un segundo método. no se tendría que decir “una Sequence es un Selector”. Con una callback. 3. si no que automáticamente mantiene una referencia de retorno a la totalidad del objeto de la clase externa. porque no solo contiene cada parte de la información del objeto de la clase externa (“el alcance en el cual fue creado”). como se podrá ver en el capítulo 13 y 16. y no solo se sería capaz de tener un Selector en existencia para una particular Sequence. Esto es un concepto muy poderoso. 320 Pensando en Java www.makeD()). cada una que implemente la misma interfase o herede de la misma clase en una forma diferente. se debe confiar en que el programador se comporte y no desaproveche el puntero. Si una callback es implementada utilizando un puntero. } } ///:~ Si no se necesita implementar un problema de “herencia de múltiple implementación”. También. se puede tener muchas clases internas. Uno de los argumentos mas convincentes hechos para incluir algún tipo de mecanismo de puntero en Java fue permitir callbacks.BruceEckel. donde tiene permiso de manipular todos los miembros.java no utilizara clases internas. 2.

mas flexible y segura que un puntero.increment(). El cierre proporcionado por la clase interna es una solución perfecta. } // Muy simple para implementar solo la interfase:: class Callee1 implements Incrementable { private int i = 0. Callee2 c2 = new Callee2(). private void incr() { i++. } } Incrementable getCallbackReference() { return new Closure(). se debe utilizar una clase interna: class Callee2 extends MyIncrement { private int i = 0. así es que los punteros no son incluidos en el lenguaje. 321 .println(i). } } // La clase debe impelementar increment() de // alguna otra forma. } private class Closure implements Incrementable { public void increment() { incr(). He aquí un ejemplo simple: //: c08:Callbacks. } void go() { callbackReference.out. } } class Caller { private Incrementable callbackReference.out.out. } } class MyIncrement { public void increment() { System.increment(). } public static void f(MyIncrement mi) { mi. System. Caller(Incrementable cbh) { callbackReference = cbh. Java tiene la tendencia de ser mas cuidadoso que eso.Como se ha visto por ahora. } } public class Callbacks { public static void main(String[] args) { Callee1 c1 = new Callee1(). System.java // Utilizando clases internas para callbacks interface Incrementable { void increment(). public void increment() { i++.println("Other operation").println(i).

así es que se esta forzado a proporcionar una implementación por separado utilizando una clase interna. caller1.BruceEckel. caller1. en algún momento mas adelante.go(). Debe notarse que todo exceptuando getCallbackRecerence() en Callee2 es privado. solo llamar a increment() y no tiene otras habilidades (a diferencia de un puntero. Cuando MyIncrement es heredado dentro de Callee2. } } ///:~ Este ejemplo proporciona también una distinción adicional entre implementar una interfase mediante una clase externa o hacerlo con una clase interna. se utiliza la referencia para llamar dentro de la clase Callee. Caller caller1 = new Caller(c1).go(). El valor de la callback esta en su flexibilidad -se puede dinámicamente decidir que funciones serán llamadas en tiempo de ejecución. 322 Pensando en Java www. Para permitir cualquier conexión al mundo externo. Caller caller2 = new Caller(c2.MyIncrement.go(). Callee2 hereda de MyIncrement que ya tiene un métodoincrement() diferente que hace algo no relacionado con eso que se espera de la interfase Incrementable. donde las callbacks son utilizadas en todos lados para implementar funcionalidades en interfases gráficas de usuario (GUI).go(). que permitirá correr descontroladamente).f(c2). Aquí se puede ver como las interfases permiten una completa separación de la interfase con la implementación.getCallbackReference()). El beneficio de esto se hace mas evidente en el capítulo 13. Debe verse también que cuando se crea una clase interna no se agrega o modifica la interfase de la clase externa. caller2. Quienquiera que tenga la referencia Incrementable puede. claro. la interfase Incrementable es esencial. La clase interna Closure simplemente implementa Incrementable para proporcionar un gancho de retroceso dentro de Callee2 -pero un gancho seguro. caller2.com . increment() no puede ser sobrescrito para utilizar por Incrementable. Callee1 es claramente la solución mas simple en términos del código. Caller toma una referencia Incrementable en su constructor (a pesar que la captura de la referencia callback puede suceder en cualquier momento) y entonces.

la librería Swing de Java es un framework de control que de forma elegante soluciona el problema de la GUI y que utiliza mucho las clases internas. } ///:~ 323 . } abstract public void action().currentTimeMillis() >= evtTime. así es que algo de la implementación puede estar incluida aquí: //: c08:controller:Event. Para poner en práctica un framework de aplicación.controller. Es una clase abstracta en lugar de la interfase actual porque el comportamiento por defecto es realizar el control fundamentado en tiempo. en este caso el incumplimiento esta fundamentado en tiempo de reloj. } public boolean ready() { return System. aquí está la interfase que describe cualquier evento de control. abstract public class Event { private long evtTime. considere un framework de control cuyo trabajo es ejecutar eventos cuando quiera que esos eventos estén “listos”. debe heredar de una o mas clases y sobrescribir algunos de los métodos.java // The common methods for any control event. El framework de control es un tipo de aplicación particular de framework de aplicación dominado por la necesidad de responder a eventos. que es poco menos que enteramente accionado por eventos. El código que se escribe en los métodos sobrescritos construyen a la medida la solución general proporcionadas por un framework de aplicación. A pesar que “listo” puede significar cualquier cosa. Lo que sigue es un framework de control que contiene información que no es específica acerca de que es lo que esta controlando.Clases internas y frameworks de control Un ejemplo mas complejo del uso de las clases internas pueden ser encontradas en algo a lo que llamará aquí como framework de control . Uno de los problemas mas importantes en la programación de aplicaciones es la interfase gráfica de usuario (GUI). abstract public String description(). package c08. Un framework de aplicaciónes una clase o un grupo de clases que están diseñadas para solucionar un tipo particular de problema. public Event(long eventTime) { evtTime = eventTime. Para ver como las clases internas permiten la simple creación y uso de los frameworks de control. Como se verá en el capítulo 13. para solucionar su problema específico. un sistema que responde primariamente a eventos es llamado un sistema accionado por eventos. Primero.

class EventSet { private Event[] events = new Event[100]. int start = next.ready()) { 324 Pensando en Java www. lanza una excepción) events[index++] = e. public void add(Event e) { if(index >= events. do { next = (next + 1) % events. // Esto es solo una forma de mantener objetos Event.java // Along with Event.length. the generic // framework for all control systems: package c08. action() es el método que es llamado cuando el Event esta ready(). return events[next]. } } public class Controller { private EventSet es = new EventSet(). Se puede remplazar con un container apropiado. la lista // es vacía: if((next == (start + 1) % events.length) return. y en el capítulo 9 de descubrirá otros contenedores que harán el truco sin que se requiera la escritura de este código extra: //: c08:controller:Controller. private int index = 0.El constructor simplemente captura el tiempo en que se quiere que Event ejecute. } while(events[next] == null). mientras que ready() indica cuando es tiempo de ejecutarlo. } public void run() { Event e.controller. public void addEvent(Event c) { es. Claro. } public void removeCurrent() { events[next] = null. y description() da una información textual acerca de Event.com . while((e = es.add(c).BruceEckel. // (En la vida real. // Verifica que el bucle se realice desde el comienzo: if(start == next) looped = true. private int next = 0.getNext()) != null) { if(e. El siguiente fichero contiene el framework de control actual que maneja y dispara los eventos. // Si se pasa del comienzo.length) && looped) return null. ready() puede ser sobrescrito en una clase derivada para basar Event en alguna otra cosa que tiempo. La primer clase es realmente solo una clase de “ayuda” cuyo trabajo es guardar los objetos Event. } public Event getNext() { boolean looped = false.

Debe verse que hasta el momento en este diseño no se sabe nada acerca de que exactamente hace un Event.action(). index es utilizado para mantener la pista del siguiente espacio disponible. es. System. Ellas permiten dos cosas: 1. Pero el método mas importante es run(). entonces es una buena idea colocarlas a null para darle permiso al recolector de basura de limpiarlas.description()). Para crear la implementación completa de un framework de control de aplicación en una sola clase. buscando un objeto Event que este listo para ejecutarse.println(e. Debe verse que removeCurrent() no configura simplemente alguna bandera indicando que el objeto no esta mas en uso. el “vector de cambio” son las diferentes acciones de los distintos tipos de objetos Event. y se expresa las diferentes acciones creando diferentes subclases Event.out. en consecuencia encapsular todo es único en esta implementación. Las clases internas son utilizadas para expresar las muchas diferentes tipos de action() necesaria para solucionar el problema. Y este es el punto crucial del diseño. y luego se quita el objeto Event de la lista. así es que getN ext() encontrará agujeros en la lista y se moverá a través de ellos.removeCurrent(). Esto es importante durante la llamada a getNext(). y addEvent() permite agregar nuevos eventos a esta lista. O. En lugar de eso. Controller es donde se sucede el trabajo actual. el siguiente ejemplo usa clases 325 . Utiliza un EventSet para mantener sus objetos Event. para ver si se encuentra dentro del bucle. Si se piensa que las referencias pueden colgarse (como ellas podrían aquí).e. Si mediante el método ready() se encuentra listo. se llama al método action(). Además. dado que cambiará el tamaño solo). coloca la referencia en null. como “separa las cosas que cambian de las cosas que siguen siendo las mismas”. Este método realiza un bucle a través de EventSet. porque los objetos Event son quitados de la lista (utilizando removeCurrent()) una vez que se ejecute. imprime la descripción mediante description(). y next es utilizado cuando se esta observando por el siguiente Event en la lista.. Esto es donde las clases internas comienzan a jugar. Esto es importante porque si el recolector de basura ve que la referencia sigue en uso no limpiará el objeto. para utilizar mis términos. } } } } ///:~ EventSet arbitrariamente mantiene 100 objetos E v ent (Si un contenedor “real” del capítulo 8 es utilizado aquí no se necesita preocuparse del tamaño máximo.

Sin esta habilidad el código puede convertirse en suficientemente desagradable a el punto terminar buscando una alternativa. Cada acción es completamente diferente: encender y apagar luces. private String thermostat = "Day". sonar campanas. agua y termostatos.com . Las clases internas controlan esta implementación de volverse torpes.java // Esto produce una aplicación específica de // sistema de control. dentro de una única clase. } } private class LightOff extends Event { public LightOff(long eventTime) { super(eventTime). la clase GreenhouseControls es heredada de Controller: //: c08:GreenhouseControls. Las clases internas permiten tener muchas versiones derivadas de la clase base. private boolean water = false. y reiniciar el sistema. Event.controller.*. viene de mi temprano libro de C++ Inside & Out. 326 Pensando en Java www. dado que se es capas de acceder fácilmente a cualquiera de los miembros en la clase externa.internas privadas así es que la implementación esta oculta completamente y puede ser cambiada con impunidad. y escribir el código de control dentro de accion(). 2.BruceEckel. } public String description() { return "Light is on". Pero el marco de trabajo de control es diseñado para fácilmente aislar estos códigos diferentes. private class LightOn extends Event { public LightOn(long eventTime) { super(eventTime). Considerando una implementación particular del marco de trabajo de control diseñado para controlar las funciones de un invernadero6. import c08. } public void action() { // Coloque el código de control de hardware // aquí para físicamente prender la luz. Las // clases internas permiten encapsular diferentes // funcionalidades para cada tipo de evento. public class GreenhouseControls extends Controller { private boolean light = false. pero Java permite una solución mucho mas elegante. 6 Por alguna razón esto es siempre un problema que me satisface solucionar. Como es típico en un marco de trabajo de aplicación. light = true. todo en una sola clase. Por cada tipo de acción se hereda una nueva clase interna Event.

} public String description() { return "Thermostat on night setting". } public String description() { return "Greenhouse water is off". } } private class WaterOn extends Event { public WaterOn(long eventTime) { super(eventTime). } public String description() { return "Greenhouse water is on". } public String description() { 327 . } public String description() { return "Light is off".} public void action() { // Coloque el código de control de hardware // aquí para físicamente apagar la luz. } } private class ThermostatNight extends Event { public ThermostatNight(long eventTime) { super(eventTime). } public void action() { // Coloque el control de hardware aquí water = false. } } private class ThermostatDay extends Event { public ThermostatDay(long eventTime) { super(eventTime). } public void action() { // Coloque el control de hardware aquí thermostat = "Day". } } private class WaterOff extends Event { public WaterOff(long eventTime) { super(eventTime). } public void action() { // Coloque el control de hardware aquí water = true. light = false. } public void action() { // Coloque el control de hardware aquí thermostat = "Night".

} } public static void main(String[] args) { GreenhouseControls gc = new GreenhouseControls(). addEvent(new WaterOn(tm + 3000)). addEvent(new ThermostatDay(tm + 10000)).currentTimeMillis(). } } ///:~ 328 Pensando en Java www. addEvent(new LightOff(tm + 2000)). private class Bell extends Event { public Bell(long eventTime) { super(eventTime). addEvent(new Bell(tm + 9000)).println("Bing!"). } } // Un ejemplo de una action() que inserta una // nueva de si misma en la lista de eventos: private int rings.out. } public void action() { // Ring every 2 seconds.new Restart(tm)). } public String description() { return "Restarting system". // Puede inclusive agregar un objeto Restart! addEvent(new Restart(tm + 20000)). 'rings' times: System.BruceEckel. // En lugar de codificación. addEvent(new WaterOff(tm + 8000)).return "Thermostat on day setting".run(). } } private class Restart extends Event { public Restart(long eventTime) { super(eventTime). long tm = System. se puede // colocar información de configuración // en un fichero de texto aquí: rings = 5. if(--rings > 0) addEvent(new Bell( System. gc.addEvent(gc.currentTimeMillis() + 2000)).currentTimeMillis(). } public void action() { long tm = System. addEvent(new LightOn(tm + 1000)).com . } public String description() { return "Ring bell". gc. addEvent(new ThermostatNight(tm)).

Por ejemplo. Bell suena. así es que agrega todos los métodos apropiados. Restart es responsable por la inicialización del sistema. Claro que una forma mas sencilla de lograr esto es evitar el codificar los eventos y en lugar de eso leerlos de un fichero (Un ejercicio en el capítulo 11 pregunta como modificar este ejemplo para hacer exactamente eso). y si todavía no ha sonado suficiente veces agrega un nuevo objeto Bell a la lista de eventos. Resumen Las interfases y las clases internas son los conceptos mas sofisticadas que se encontrarán en muchos leguajes de POO. water. A pesar que estas características por si solas son directas. Dado que Restart() es simplemente otro objeto Event.action() así es que el sistema regularmente reinicia solo. Todo el tiempo. donde las interfases y clases internas de Java son. o ambas. Para el momento en que terminemos el capítulo se deberá estar totalmente convencido. que puede. Este ejemplo nos guía por un largo camino para apreciar el valor de las clases internas. thermostat. en comparación mas accesibles. en gran medida igual que el polimorfismo. pero Bell y Restart son especiales. 329 . el uso de estas características es un tema de diseño. en el capítulo 13 se verá de que forma elegante las clases internas son utilizadas para describir las acciones de una interfaces grafica de usuario. y aún las clases internas pueden acceder a esos campos sin ningún requisito o permiso especial. Sin embargo. o una clase interna. no hay nada como ellos en C++. También. especialmente cuando son utilizadas con un marco de trabajo de control. así es que sonara nuevamente mas tarde. la mayoría de los métodos action() involucra algún tipo de control de hardware. HM en C++ se vuelve complicado de utilizar. Juntos. en el mejor de los casos involucrar código que no es Java. Sin embargo. Debe notarse como las clases internas al menos se ven como herencia múltiple: Bell tiene todos los método de Event y estos también parece tener todos los métodos de la clase externa GreenhouseControls. y rings pertenecen a la clase externa GreenhouseControls. Y todo lo que necesita para hacer el main() es crear un objeto GreenhouseControls y agregar un objeto Restart para ponerlo en marcha. Cuando se vean estas características del lenguaje en uso eventualmente se interiorizarán. Muchas de las clases Event se ven similares. se transformará en reconocer situaciones donde se deberá utilizar una interfase.Debe notarse que light. resuelven el mismo problema que C++ intente resolver con herencia múltiple (HM). se puede también agregar un objeto sin Restart. Pero en este punto en este libro se deberá al menos sentir confortable con la sintaxis y la semántica.

8. Pruebe que todos los métodos en una interfase son automáticamente públicos. En co7:Sanswich. 2. 12. cree un objeto de su clase y páselos a cada uno de los métodos. conversión ascendente a la interfase durante el retorno. cada uno de los cuales toma uno de las cuatro interfases como argumento. En un tercer paquete. Cree una clase en un paquete separado.BruceEckel. disponible por una pequeña propina en www. 6. 5.com .java. Cree tres interfases. 1.java. Elimine la declaración play() de Instrument. Pruebe que los campos en una interfase son implícitamente estáticas y finales. Siguiendo el ejemplo dado en Month2. Cree una clase implementando la nueva interfase y también heredando de una clase bien establecida. 330 Pensando en Java www. en su propio paquete. Implemente la interfase un paquete diferente. cree una enumeración de días de la semana. 3. en su propio paquete. Cree una interfase que tenga tres métodos. Cambie el ejercicio 6 en el capítulo 7 para que Rodent sea una interfase. Agregue una clase interna protegida que implemente la interfase. herede de su clase y . agregue un nuevo método. dentro de un método. Escriba un programa que importe y utilice Month2. En Adventure. En el main().com . Ahora escriba cuatro métodos. 10. Herede una nueva interfase de las tres. cada una con dos métodos. 9. Cree una interfase con al menos un método. agregue una interfase llamada CanClimb. 4. Modifique Music5. 11. Cambe tune() para que tome un Playable en lugar de un Instrument. cree una interfase llamada FastFood (con los métodos apropiados) y cambie Sandwich de tal forma que también incremente FastFood.java agregando una interfase Playable. siguiendo la forma de las otras interfases. 7.BruceEckel. Agregue Playable a la clase derivada incluyéndola en la lista de implementaciones.java.Ejercicios La solución de los ejercicios seleccionados pueden encontrarse en el documento electrónico The T h i n k i n g i n J a v a Annotated Solution Guide. Modifique el ejercicio 5 creando una clase abstracta y herédela dentro de la clase derivada. retorne un objeto de la clase interna protegida.java.

luego muestre el efecto en el objeto de la clase externa. 25. Cree una clase conteniendo una clase interna que contenga en si misma una clase interna. Cree una segunda clase que tenga un método que retorne una referencia a la primer clase. En un segundo método de clase externa. 24. En el main(). Cree una clase interna privada que implemente una interfase publica. Repita el ejercicio 18 utilizando una clase interna anónima. Deben verse con cuidado los nombres de los ficheros . 14. realice una conversión ascendente a la interfase. cree un objeto de la clase interna y llame su método. Repita el ejercicio 13 utilizando una clase interna anónima. Cree una interfase conteniendo una clase interna estática.class producidos por el compilador. Cree una segunda clase con una clase interna que herede de la primera clase interna. Cree el objeto para que retorne creando una clase interna anónima que herede de la primer clase. 16. cree una instancia de la clase interna. 15. 23. cree una instancia de la clase interna. Repita esto utilizando una clase interna estática. Repita el ejercicio 13 pero defina la clase interna sin un alcance dentro del método. 19. Escriba un método que retorne una referencia a una instancia de la clase interna privada. Cree una clase con una clase interna que tenga un constructor que no sea por defecto. Cree una clase con un campo privado y un método privado. 20. Cree una segunda clase que tenga un método que retorne una referencia a la primera clase. que retorne una referencia a su interfase. 21. Cree una interfase con al menos un método. Implemente esta interfase y cree una instancia de la clase interna. Cree una clase con un constructor que no sea por defecto y sin constructor por defecto.java. En una clase separada. implemente esa interfase definiendo una clase interna dentro de un método.13. Cree una clase interna con un método que modifique el campo de la clase externa y llame el método de la clase externa. Cree una clase conteniendo una clase interna estática. Cree una clase con una clase interna. Muestre que la clase interna esta completamente oculta intentando realizar una conversión descendente a ella. 22. 17. Repare el problema en WindError. 331 . 18.

Modifique Sequence. 27. Quite algunas de las referencias U de B.Determine si la inversa es verdadera. B debe tener un método que acepte y almacene una referencia a U en el arreglo. un segundo método que configure una referencia en el arreglo (especificado por el argumento del método) a null y un tercer método que mueva a través del arreglo y llame los métodos en U .java agregando un método getRSelector() que produzca una implementación diferente de la interfase Selector que mueve un paso atrás a través de la secuencia desde el final al comienzo. 29. 28. Muestre que una clase interna tiene accedo a los elementos privados de su clase externa. Utilice el B para realizar llamadas para atrás dentro de todos los objetos A. cree un grupo de objetos A y un solo objeto B. Llene el B con referencias U producidas por los objetos A. Cree una clase A con un método que produzca una referencia a un U creando una clase interna anónima. En GreenhouseControls. Cree una interfase U con tres métodos. 332 Pensando en Java www.BruceEckel. Cree una segunda clase B que contenga un arreglo de U .com . En el main().java.26. agregue una clase interna Event que prenda y apague ventiladores..

referencias a objetos) El arreglo es una simple secuencia 333 . donde es mostrado como se define e inicializa un arreglo. la librería de utilitarios de Java tiene un razonablemente completo grupo de clases contenedoras (también conocidas como compendio de clases o collection classes. que ha sido discutido anteriormente. referencias a objetos. Java tiene muchas formas de almacenar objetos (o mejor. Así es que no se puede confiar en la creación de un nombre de referencia para almacenar cada uno de los objetos: MyObject myReference. Retener los objetos es el tema de este capítulo. y un arreglo es simplemente una forma de almacenar objetos. en cualquier lugar. ¿Entonces. Los contenedores proporcionan formas sofisticadas de almacenar e incluso manipular los objetos. pero dado que las librerías de Java 2 usan el nombre Collection para referirse a un subgrupo especial de la librería. Para resolver este problema esencial de la mejor forma. El tipo incorporado es el arreglo. usaré el término mas inclusivo término "contenedor").9: Almacenando objetos Es un programa bastante simple si solo tiene una cantidad fija de objetos con tiempos de vida conocidos. Para solucionar el problema general de programación. que es lo que hace un arreglo especial? Hay dos cosas que distinguen los arreglos de otros tipos de contenedores: eficiencia y tipo. Arreglos La mayoría de la introducción necesaria a los arreglos está en la última sección del capítulo 4. No se conocerá hasta el momento de la ejecución la cantidad o incluso el tipo exacto del objeto que se necesita. También. El arreglo es la forma mas eficiente que proporciona Java para almacenar y acceder de forma aleatoria a una secuencias de objetos (actualmente. los programas estarán creando nuevos objetos basados en algún criterio que será conocido solo en el momento que el programa se esta ejecutando. se necesita ser capaz de crear cualquier número de objetos. En general. en cualquier momento.

ellos son tratados como objetos del tipo Object. Esto significa que el tipo se obtiene en tiempo de compilación chequeando para prevenir que no se coloque el tipo equivocado. La otra clase contenedora genérica que será estudiada en este capítulo. así es que se puede pasar el final1 . Aparte. todas tratan con objetos que no tienen un tipo específico. 334 Pensando en Java www. Esto es el segundo lugar donde un arreglo es superior a un contenedor genérico: cuando se crea un arreglo. List. crear uno nuevo y mover todas las referencias del viejo al nuevo. Esto funciona bien desde un punto de vista necesario para crear solo un contenedor. o como valores permutables envolviéndolos en sus propias clases). Así es que no es mucho riesgo de una forma u otra. cuando se trate de solucionar un problema mas general los arreglos pueden ser muy 1 Es posible sin embargo. y cualquier objeto Java podrá ir en el contenedor (excepto primitivas-estas pueden ser colocadas en contenedores como constantes utilizando envoltorios para primitivas de Java. La clase contenedor vector en C++ conoce el tipo de los objetos que contiene. o equivocarse cuando se esta extrayendo.BruceEckel. se realiza una verificación de los límites independientemente de si se esta utilizando un arreglo o un contenedor -se obtendrá una RuntimeException si se excede los límites. pero tiene un inconveniente diferente cuando se compara con arreglos en Java: el vector operator[] de C++ no chequea los límites. es simplemente amable si el compilador lo indica. preguntar que tan grande es el vector. y en el método at() realizar la verificación de límites. si se agota el espacio. mas rápido en tiempo de ejecución. dado el gasto de esta flexibilidad en el tamaño. Esto es el comportamiento de la clase ArrayList. la razón para que C++ no verifique los límites con cada acceso es la velocidad -en Java se tiene una se tiene un gasto de tiempo de computación constante por chequeos de limites todo el tiempo para arreglos y contenedores.com . y de esta manera no se necesita verificarlo en el código. Sin embargo. y hay menos probabilidades de que el usuario final tenga una sorpresa con una excepción. Esto es. Sin embargo.lineal. una ArrayList es sensiblemente menos eficiente que un arreglo. su tamaño es fijo y no puede ser cambiado durante la vida del objeto. pero se paga por esta velocidad: cuando se crea un arreglo. Java previene de enviar un mensaje inapropiado a un objeto. En Java. se crea para almacenar un tipo específico. que hace el acceso a los objetos rápidos. Se puede sugerir la creación de un arreglo de un tamaño particular y entonces. Por un tema de eficiencia y verificación de tipo vale la pena siempre tratar de utilizar un arreglo si es posible. este tipo de excepción indica un error de programación. que será estudiada mas adelante en este capítulo. y Map. la clase raíz de todas las clases en Java. Como se aprenderá en el capítulo 10. Claro. Set. en tiempo de compilación o en tiempo de ejecución.

println("c. Parte del objeto arreglo (de echo.length. new Weeble() }. Este es el objeto que contiene las referencias a los otros objetos y puede ser creado implícitamente como parte de la sintaxis de inicialización del arreglo o explícitamente con una expresión new . //: c09:ArraySize. i < c.length). el único campo o método al cual se puede acceder) es el miembro de solo lectura length que indica cuantos elementos pueden ser almacenados en el objeto arreglo. // Dynamic aggregate initialization: a = new Weeble[] { new Weeble(). a = d. // Null reference Weeble[] b = new Weeble[5].println("b[" + i + "]=" + b[i]). i++) System. System. Luego de analizar la utilización de arreglos este capítulo será totalmente consagrado a las clases contenedoras proporcionadas por Java.out.length).out.length).length). new Weeble() }. Los arregos son objetos de primera calidad Independientemente del tipo de arreglo con el que se este trabajando. i++) c[i] = new Weeble(). mientras que los arreglos de primitivas contienen los valores primitivos directamente.length = " + c. System. 335 .length=" + a. for(int i = 0. System.out. y como las referencias a arreglos pueden ser asignadas a los diferentes objetos arreglo. // Aggregate initialization: Weeble[] d = { new Weeble(). System.out.length = " + d. // The references inside the array are // automatically initialized to null: for(int i = 0. El siguiente ejemplo muestra las distintas formas en las que un arreglo puede ser inicializado. class Weeble {} // A small mythical creature public class ArraySize { public static void main(String[] args) { // Arrays of objects: Weeble[] a.java // Initialization & re-assignment of arrays.println("a.println("b.restrictivos. Muestra también los arreglos de objetos y de primitivas son al menos idénticos en su uso.length = " + b. el identificador de arreglo es actualmente una referencia a un objeto verdadero que es creado en el heap. La única diferencia es que ese arreglo de objetos contiene referencias.out.length. new Weeble(). i < b.println("d. // Null references Weeble[] c = new Weeble[4].

length = e. // The primitives inside the array are // automatically initialized to zero: for(int i = 0.length). int[] g = new int[4]. cuando un objeto 336 Pensando en Java www.println("f. } } ///:~ He aquí la salida del programa: b. se puede seguir preguntando cual es el tamaño del arreglo. // Compile error: variable e not initialized: //!System. // Null reference int[] f = new int[5].out.length = 5 4 3 3 2 5 4 3 3 2 El arreglo a es inicialmente una referencia a null.System. e = new int[] { 1.println("h.length).length.BruceEckel.length = b[0]=null b[1]=null b[2]=null b[3]=null b[4]=null c.out. esto es el tamaño del objeto arreglo. i++) g[i] = i*i.println("e. System.println("f[" + i + "]=" + f[i]).length = " + e.length = d.length = a. sin embargo.length).length=" + e.length = h. dado que length solo dice cuantos elementos pueden ser colocados en el arreglo. 93 }. System. // Arrays of primitives: int[] e. 47.out. int[] h = { 11.out.println("e.out.length = e.length = f[0]=0 f[1]=0 f[2]=0 f[3]=0 f[4]=0 g.com . i++) System. i < g.length. i < f. System. 2 }. System.length = f. El arreglo b es inicializado para apuntar a un arreglo de referencias Weeble. for(int i = 0.length = " + h.println("a.length).out.length).length). y el compilador previene de hacer algo con esta referencia hasta que haya sido inicializada propiamente. System. no el número de elementos que actualmente contiene.length = " + e.length = " + a.length).length = a.out.println("e.length = " + f.println("g.length = " + g.out. Sin embargo. pero actualmente ningún objeto Weeble son colocados en el arreglo. e = h.

Por ejemplo. exactamente como para el arreglo c) y inicializado con objetos Weeble. (char)0 para char. de la misma forma que referencias a objetos. supóngase que hide() es un método que toma un arreglo de objetos Weeble. Se puede llamar a este diciendo: hide(d).java muestra que los arreglo de primitivas trabajan exactamente como arreglos exceptuando que los arreglos de primitivas contienen los valores primitivos directamente. De forma similar. un arreglo de primitivas es automáticamente inicializado a cero para tipos numéricos. pero las clases envoltura para primitivas pueden ser complicadas de utilizar. Al arreglo c muestra la creación de un objeto arreglo seguido por la asignación de objetos Weeble para todas las ubicaciones en el arreglo. Además. La inicialización en conjunto utilizada por d debe ser utilizada en el punto de la definición de d. La expresión a = d. todo en una sola instrucción. El arreglo d muestra la sintaxis de la "inicialización en conjunto" que produce que un objeto del tipo arreglo sea creado (implícitamente con new en el heap. new Weeble() }). para colocar valores primitivos dentro de un contenedor. pero también se puede crear también dinámicamente el arreglo pasándolo como argumento: hide(new Weeble[] { new Weeble(). etc. Es posible utilizar las clases "envoltura" como Integer. puede ser creado para almacenar primitivas directamente. Un arreglo. muestra como se puede tomar una referencia que esta enganchada a un objeto arreglo y asignarla a otro objeto arreglo. La segunda parte de ArraySize.arreglo es creado sus referencias son automáticamente inicializadas a null. Ahora a y b apuntan a el mismo objeto arreglo en el heap. sin embargo. exactamente como se podría hacer con otro tipo de referencia a objeto. La siguiente inicialización de arreglo puede ser pensado como "inicialización dinámica en conjunto". pero con la segunda sintaxis se puede crear y inicializar un arreglo de objetos en cualquier lugar. Double. y false para boolean. En algunas situaciones esta nueva sintaxis proporciona una forma mas conveniente de escribir código. así es que se puede ver cuando en una ubicación en particular del arreglo hay un objeto verificando si es o no null. Contenedores de primitivas Las clases contendores pueden almacenar solo referencias a objetos. es mucho mas 337 .

while (picked[t]).random() * flav. for (int i = 0.eficiente crear y acceder a un arreglo de primitivas que a un contenedor de primitivas envueltas. "Mud Pie" }. "Vanilla Fudge Swirl". Retornando un arreglo Supongamos que se esta escribiendo un método y no se quiere retornar solamente una cosa. "Praline Cream". "Strawberry". i < n.com . considere retornar un arreglo de Cadenas: //: c09:IceCream.java // Returning arrays from methods. Lenguajes como C y C++ hacen esto dificultoso porque no se puede simplemente retornar un arreglo. do t = (int)(Math. Java tiene una estrategia similar. pero con Java nunca se preocupa acerca de la responsabilidad de ese arreglo -este estará en el momento en que se lo necesite y el recolector de basura lo limpiará cuando se haya terminado. Claro.BruceEckel. 338 Pensando en Java www. se quiere retornar un montón de cosas. Como ejemplo. Actualmente. pero Java no proporciona esto. solo un puntero a un arreglo. 2 Este es uno de los lugares donde C++ es claramente superior a Java.length]. String[] results = new String[n]. "Mocha Almond Fudge". "Mint Chip". se esta retornando una referencia a un arreglo. pero solo se "retorna un arreglo".abs(n) % (flav. el arreglo no funciona y se esta forzado a utilizar un contenedor para envolver primitivas. el cual fácilmente conduce a agujeros en la memoria.length + 1). i++) { int t. Esto introduce problemas porque hace desordenado el control del tiempo de vida del arreglo. claro.length). public class IceCream { static String[] flav = { "Chocolate". "Rum Raisin". dado que C++ soporta tipos parametrizados con la palabra clave template. static String[] flavorSet(int n) { // Force it to be positive & within bounds: n = Math. Algún tipo de mecanismo mediante plantillas puede algún día soportar una mejor forma para que Java maneje este problema2. boolean[] picked = new boolean[flav. si se esta utilizando un tipo primitivo y necesita la flexibilidad de un contenedor que puede ser expandido automáticamente cuando se necesita mas espacio. Se puede pensar que debería haber un tipo especializado de ArrayList para cada uno de los datos primitivos.

Si es exitoso. } return results. que solo se quería el helado. o que el arreglo fuera creado en cualquier lado.length.results[i] = flav[t]. Esto es realizado con un bucle do que realiza elecciones hasta que encuentra uno que no este en el arreglo picked (claro que. así es que se puede ver que flavorSet() selecciona los sabores en orden aleatorio cada vez. que contiene un grupo de métodos estáticos que realizan funciones utilitarias para arreglos.println( "flavorSet(" + i + ") = "). hay un método individual llamado asList() que toma un arreglo y 339 . Estas son cuatro funciones básicas: equals(). pero las comparaciones de String son ineficientes). Todos estos métodos son sobrecargados para todos los tipos primitivos y Objects. } } } ///:~ El método flavorSet() crea un arreglo de cadenas llamado result. Entonces este comienza a elegir sabores de forma aleatoria del arreglo fla v y a colocarlos en result. determinado por el argumento que se pasa al método. se debe recordar. sort(). i++) { System.out. Además.println("\t" + fl[j]).out. j++) System. El recolector de basura tiene cuidado de limpiar el arreglo cuando se haya terminado con el. para llenar un arreglo con un valor. String[] fl = flavorSet(flav. se encontrara la clase Array. no se necesitaba. fill().util. Es fácil ver esto si se realiza una redirección de la salida a un fichero.length). para encontrar un elemento en un arreglo ordenado. se asegura que esa elección no ha sido realizada. No es importante que el arreglo fuera creado con flavorSet(). agrega la entrada y encuentra el siguiente (i se incrementa). debe notarse que cuando flavorSet() elige de forma aleatoria. Aparte. picked[t] = true. el cual finalmente se retorna. main() imprime 20 grupos de sabores. j < fl. y el arreglo persistirá mientras se necesite. para comparar la igualdad de dos arreglos. una comparación de cadenas puede también realizarse para ver si la elección aleatoria ya esta entre el arreglo con los resultados. para ordenar el arreglo. Y cada vez que se vea el contenido del fichero. for(int j = 0. y binarySearch(). para este problema. } public static void main(String[] args) { for(int i = 0. i < 20. Retornar un arreglo es simplemente como retornar cualquier otro objeto -es una referencia. La clase Array En java. El tamaño de este arreglo es n.

java package com.util.util. public interface Generator { Object next(). el método fill() solo toma un solo valor y lo coloca en el arreglo. } ///:~ //: com:bruceeckel:util:ByteGenerator.util.BruceEckel. Por ejemplo.bruceeckel.util por conveniencia. 3 El programador C++ notará cuanto código puede derrumbarse con el uso de argumentos por defecto y plantillas. public interface ShortGenerator { short next(). } ///:~ //: com:bruceeckel:util:CharGenerator.lo coloca dentro de un contenedor List -del cual se aprenderá mas tarde en este capítulo. } ///:~ //: com:bruceeckel:util:IntGenerator.java package com.bruceeckel. así es que si se quiere -por ejemplo. 340 Pensando en Java www. hay aquí una gran cantidad de código duplicado 3.bruceeckel.com .java package com.java package com. } ///:~ //: com:bruceeckel:util:ShortGenerator.llenar el arreglo con un grupo de números generados de forma aleatoria. public interface CharGenerator { char next(). public interface ByteGenerator { byte next(). Y como se habrá visto. una interfase “generadora” se requiere para cada tipo dado que el tipo de retorno de next() debe ser diferente en cada caso: //: com:bruceeckel:util:Generator. Por ejemplo. Así es que tiene sentido suplementar la clase Array con algunas utilidades adicionales. y llenaran un arreglo con valores de objetos que son creados por un objeto llamado generador que se puede definir. El programador Python verá que la totalidad de la librería será en su mayoría innecesaria en este lenguaje.util. fill() no ayudará.bruceeckel.bruceeckel.util. se estarán ubicadas en el paquete com.util. Dado que el código necesita ser creado para cada tipo primitivo de la misma forma que para Object.java package com. public interface BooleanGenerator { boolean next().bruceeckel.bruceeckel. Estos imprimirán un arreglo de cualquier tipo. la clase Array no llega a ser totalmente funcional. A pesar de que es útil. } ///:~ //: com:bruceeckel:util:BooleanGenerator. sería bueno ser capas de imprimir los elementos de un arreglo sin tener que codificar un bucle for a mano cada vez.java package com.

length).util.length). Allows any array to be printed. se puede agregar un mensaje antes que el arreglo sea impreso. El código de print() se explica solo: //: com:bruceeckel:util:Arrays2. for(int i = from. Object[] a) { System. to provide // additional useful functionality when working // with arrays.bruceeckel. 341 . } public static void print(String msg.util. } ///:~ //: com:bruceeckel:util:LongGenerator.out. 0.print("("). Se puede simplemente imprimir un arreglo.util. import java. // and to be filled via a user-defined // "generator" object. } ///:~ //: com:bruceeckel:util:DoubleGenerator.java package com. System. a.print("["+ from +":"+ to +"] "). sobrecargadas para cada tipo. public interface DoubleGenerator { double next(). 0. int to.out.java // A supplement to java. package com.out.java package com.print(msg + " ").out. to.println(")"). public interface FloatGenerator { float next(). } ///:~ Arrays2 contiene una variedad de funciones print(). int length) { if(from != 0 || to != length) System. int to){ start(from.length). public class Arrays2 { private static void start(int from.out. } ///:~ //: com:bruceeckel:util:FloatGenerator. print(a. i < to. o se puede imprimir un rango de elementos dentro del arreglo.java package com.*.bruceeckel.bruceeckel. a. } public static void print(Object[] a.print(a[i]). int from. i++) { System. a.Arrays.bruceeckel. public interface LongGenerator { long next(). } private static void end() { System.util.util. } public static void print(Object[] a) { print(a.util.public interface IntGenerator { int next().

print(". print(a. to. i < to. } public static void print(char[] a) { print(a. } public static void print(char[] a.length).out. a. "). i < to. i++) { System. 0. "). 0. } public static void print(String msg. i++) { System.BruceEckel. } public static void print(String msg. char[] a) { System. if(i < to -1) 342 Pensando en Java www. a. 0. int from. a.print(".out. to. i < to. 0.length). a.out.print(msg + " ").length). } end(). } end(). a. if(i < to -1) System. 0. for(int i = from. 0. print(a. i++) { System. a. int to) { start(from. } public static void print(String msg. boolean[] a) { System. int from.out. for(int i = from. for(int i = from. if(i < to -1) System.length).print(".out.out.print(a[i]). a.length).print(a[i]).out.com . byte[] a) { System. int to) { start(from.if(i < to -1) System.length).length).length). int from.print(msg + " "). print(a.out. a. to. } public static void print(boolean[] a.length).print(msg + " "). int to) { start(from.print(a[i]). } public static void print(byte[] a) { print(a. } public static void print(boolean[] a) { print(a. a. } end(). } public static void print(byte[] a.out. ").

print(msg + " "). a. i++) { System.print(". to. "). ").print(".out. i < to. int from. int to) { start(from. if(i < to .length).length). int[] a) { System. long[] a) { System. } end(). } public static void print(short[] a. } public static void print(String msg. a.out.length). 0.out.print(a[i]). 0. i < to.out.System.length). for(int i = from. 343 . a. print(a.print(". to. a. } public static void print(String msg. } end().length). to.print(msg + " "). int to) { start(from.print(msg + " "). for(int i = from. 0. int from. a.out. 0. a. a. } public static void print(long[] a. 0.length). 0.out.length).length).out. if(i < to . int from. print(a.length).out. a. for(int i = from.1) System.print(a[i]). int to) { start(from. "). a. } public static void print(short[] a) { print(a.1) System. } public static void print(int[] a) { print(a.1) System. } public static void print(String msg. } end(). if(i < to .print(a[i]).out. } public static void print(long[] a) { print(a. short[] a) { System.out. "). i++) { System. print(a. } public static void print(int[] a.print(". i < to. i++) { System.

print(a.out.length).com . BooleanGenerator gen) { fill(a. 0.length). } public static void print(float[] a.length. to.out. } end(). } public static void print(double[] a. for(int i = from.next().print(msg + " "). } public static void fill(Object[] a. double[] a) { System.1) System.length). int from. a. a. ").1) System. int to){ start(from.print(a[i]).out. print(a. int from. i++) { System.print(msg + " ").length). if(i < to . } public static void 344 Pensando en Java www. 0. float[] a) { System. i < to. a. } public static void print(String msg. 0.print(".length). for(int i = from. Generator gen){ for(int i = from. a. int from.print(a[i]). i++) a[i] = gen. i++) { System. to. i < to. a. a. gen). } public static void print(double[] a) { print(a. } public static void print(String msg. } public static void print(float[] a) { print(a. int to) { start(from. a. } public static void fill(boolean[] a. i < to. } // Fill an array using a generator: public static void fill(Object[] a. 0.out.print(".} end(). if(i < to .BruceEckel. } end(). gen).length).length. int to. "). a.out. Generator gen) { fill(a.out. 0. 0.

next(). } public static void fill(short[] a. i++) a[i] = gen.length. a. int from. i++) a[i] = gen.length. gen).length. int from. int to. ByteGenerator gen) { fill(a. 0. 0. i++) a[i] = gen.next(). 0.length. } 345 . } public static void fill(short[] a.next(). i++) a[i] = gen. LongGenerator gen) { fill(a. i < to. i < to. 0. BooleanGenerator gen) { for(int i = from. i < to. i++) a[i] = gen. } public static void fill(int[] a.next(). gen). gen). int to. int to. LongGenerator gen) { for(int i = from.length. ShortGenerator gen) { for(int i = from. i < to.fill(boolean[] a. gen). } public static void fill(long[] a. gen). } public static void fill(char[] a. } public static void fill(char[] a. i++) a[i] = gen. } public static void fill(long[] a. a.next(). i < to. } public static void fill(byte[] a. int to.next(). int from. ShortGenerator gen) { fill(a. int from. ByteGenerator gen) { for(int i = from. IntGenerator gen) { fill(a. int to. a. } public static void fill(int[] a. a. int to. a. CharGenerator gen) { fill(a. CharGenerator gen) { for(int i = from. int from. IntGenerator gen) { for(int i = from. i < to. } public static void fill(byte[] a. 0. int from.

} public static void fill(double[] a. for(int i = 0.nextBoolean(). public static class RandBooleanGenerator implements BooleanGenerator { public boolean next() { return r. i < len. return src[pos % src. 0. } private static Random r = new Random(). public RandStringGenerator(int length) { len = length. a. 0. int to. gen). private RandCharGenerator cg = new RandCharGenerator(). static char[] src = ssource. i++) a[i] = gen. DoubleGenerator gen) { fill(a. public static class RandCharGenerator implements CharGenerator { public char next() { int pos = Math.BruceEckel. FloatGenerator gen) { for(int i = from. gen). FloatGenerator gen) { fill(a. int to.next(). } } public static class RandStringGenerator implements Generator { private int len. i++) a[i] = gen.nextInt()). } public Object next() { char[] buf = new char[len]. i < to.com . a. int from. } } public static class RandByteGenerator implements ByteGenerator { public byte next() { return (byte)r.abs(r.public static void fill(float[] a. } public static void fill(float[] a. DoubleGenerator gen){ for(int i = from. i < to.length.next(). } public static void fill(double[] a.length.length].toCharArray(). i++) 346 Pensando en Java www.nextInt(). int from. } } static String ssource = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz".

} } public static class RandIntGenerator implements IntGenerator { private int mod = 10000.next(). return new String(buf). } public int next() { return r. El tamaño del arreglo esta determinado por el argumento del constructor.nextFloat(). } } public static class RandDoubleGenerator implements DoubleGenerator { public double next() {return r. Los generadores de datos aleatorios son útiles para pruebas. Ahora se puede crear cualquier generador implementando la interfase apropiada.nextDouble(). } } public static class RandLongGenerator implements LongGenerator { public long next() { return r. 347 .nextInt(). Se puede ver que RandStringGenerator utiliza RandCharGenerator para llenar un arreglo de caracteres.} } } ///:~ Para llenar un arreglo de elementos utilizando un generador. que tiene un método next() que de alguna manera producirá un objeto del tipo adecuado (dependiendo de como la interfase esté implementada). } } public static class RandShortGenerator implements ShortGenerator { public short next() { return (short)r.buf[i] = cg. así es que un grupo de clases internas es creada para implementar todas las interfases generadoras primitivas. de la misma forma que un generador String para representar Object. public RandIntGenerator() {} public RandIntGenerator(int modulo) { mod = modulo.nextLong(). y utilizar su generador con fill(). El método fill() simplemente llama al método next() hasta que el rango deseado ha sido llenado. que es colocado dentro de un String. el método fill() toma una referencia a una interfase generadora apropiada.nextInt() % mod. } } public static class RandFloatGenerator implements FloatGenerator { public float next() { return r.

bruceeckel.print(a5. new Arrays2.print(a6.print(a5). a3).print(a4. String[] a9 = new String[size]. size/3. pero el constructor sobrecargado permite elegir un valor mas pequeño.RandByteGenerator()). a5). new Arrays2. new Arrays2.print("a1 = ". size/3 + size/3). byte[] a2 = new byte[size].print(a1.com .parseInt(args[0]). size/3 + size/3). Arrays2.fill(a4. Arrays2.java // Test and demonstrate Arrays2 utilities import com. size/3. new Arrays2. Arrays2.print(a2). public class TestArrays2 { public static void main(String[] args) { int size = 6. Arrays2.RandCharGenerator()).print("a6 = ".fill(a5. Arrays2. Arrays2. new Arrays2.000.Para generar números que no sean muy grandes.length != 0) size = Integer. size/3 + size/3). size/3 + size/3).RandBooleanGenerator()).RandLongGenerator()).print(a4). Arrays2. Arrays2.fill(a2.print("a5 = ".util. Arrays2. He aquí un programa para verificar la librería. Arrays2. long[] a6 = new long[size]. char[] a3 = new char[size].print(a3. size/3. Arrays2. float[] a7 = new float[size]. Arrays2.print(a2. size/3 + size/3).print("a3 = ".BruceEckel. a1). Arrays2.print("a4 = ". size/3. 348 Pensando en Java www. Arrays2. double[] a8 = new double[size]. size/3. Arrays2. size/3.fill(a1. a6). Arrays2. boolean[] a1 = new boolean[size]. Arrays2. short[] a4 = new short[size]. Arrays2. y para demostrar como es utilizada: //: c09:TestArrays2. Arrays2. size/3 + size/3).fill(a3.print(a3). new Arrays2.print("a2 = ". Arrays2. Arrays2. a2). // Or get the size from the command line: if(args.fill(a6. por defecto RandIntGenerator a un módulo de 10. Arrays2. Arrays2.RandShortGenerator()). int[] a5 = new int[size].print(a6).*.print(a1).RandIntGenerator()). Arrays2. a4).

fill(a1.print("a3 = ".RandDoubleGenerator()).print(a8. Llenando un arreglo La librería estándar Arrays también tiene un método fill(). a3).print(a9).fill(a7. short[] a4 = new short[size]. Arrays2. pero se puede también colocar en la línea de comandos. new Arrays2. Arrays2.print("a1 = ". Arrays2. size/3 + size/3). size/3.fill(a3. Arrays2. Arrays2. o en el caso de los objetos. Arrays2. size/3 + size/3). float[] a7 = new float[size]. Arrays.dill() puede ser fácilmente demostrado: //: c09:FillingArrays.parseInt(args[0]). Arrays. true). 349 . long[] a6 = new long[size]. Arrays2.print("a9 = ". size/3 + size/3). } } ///:~ El parámetro size tiene un valor por defecto. a2). el método Arrays. new Arrays2.bruceeckel. (byte)11). copia la misma referencia dentro de cada ubicación. Arrays2. byte[] a2 = new byte[size]. int[] a5 = new int[size].print("a2 = ". size/3. Arrays.print(a7). Arrays2. Arrays2.print("a7 = ". String[] a9 = new String[size].fill() import com. size/3.*.fill(a2.print(a9. public class FillingArrays { public static void main(String[] args) { int size = 6. a7). new Arrays2. // Or get the size from the command line: if(args. Arrays2.fill(a9. a1). double[] a8 = new double[size]. Utilizando Array2. 'x').print(a8). Arrays2. import java. char[] a3 = new char[size].length != 0) size = Integer. pero este es mas bien trivial -solo duplica un único valor dentro de cada ubicación.RandStringGenerator(7)).print(a7.fill(a8.print("a8 = ".util.RandFloatGenerator()). a9).java // Using Arrays.Arrays2.util. Arrays2. Arrays2. boolean[] a1 = new boolean[size]. a8).print().*.

o -como se muestra en las dos últimas instrucciones. Arrays. Arrays. 99).fill(a7.fill(a5.length). System. Arrays. j). a5). Pero dado que se puede proporcionar un solo valor a utilizar para llenar utilizando Arrays.fill(a6. int[] j = new int[25]. Aquí hay un ejemplo para manipular arreglos de enteros.un rango de elementos. 103).print("a8 = ". el método Arrays2. Arrays. Arrays2.util. Arrays.arraycopy(i. system. 19).print("a5 = ".java // Using System. k.Arrays. 0.*.fill(j. Arrays2.print("k = ".fill(a9.fill(k. "World").com . k.arraycopy(k. a6). 5.BruceEckel.arraycopy(i. Arrays2. Arrays2.fill(k.fill(a4.fill(). i.fill(a9. import java. 0. System. } } ///:~ Se puede también llenar el arreglo entero.print("a9 = ". Arrays2. Copiando un arreglo La librería estándar de Java proporciona un método estático. a4). 0. System. a9). Arrays2. Arrays. Arrays2.print("a4 = ". System. j. a8). Arrays2. Arrays. k).arraycopy() se sobrecarga para manejar todos los tipos.fill() produce resultados mucho mas interesantes. int[] k = new int[10]. 47). //: c09:CopyingArrays. (short)17).fill(a8. 47). k. 0.*.util. public class CopyingArrays { public static void main(String[] args) { int[] i = new int[25].print("i = ". que puede hacer mucho mas rápido la copia de un arreglo que si se utiliza un bucle for para realizar la copia a mano.fill(i. 29). Arrays.bruceeckel. Arrays2. a7). a9). Arrays2. 3.print("i = ". Arrays. i).print("a6 = ".print("a7 = ". // Objects: 350 Pensando en Java www.print("j = ".arraycopy().print("a9 = ". Arrays2. i. 0. "Hello"). i).length). 103). j). 0.arraycopy() import com. 23).length). Arrays2.print("j = ". // Manipulating ranges: Arrays.

a2)). los arreglos deben tener el mismo número de elementos y cada elemento debe ser equivalente a cada elemento correspondiente en el otro arreglo. } } ///:~ 351 . y el número de elementos a copiar.length/2. "Hi". int[] a2 = new int[10]. Nuevamente.util. He aquí un ejemplo: //: c09:ComparingArrays.print("u = ".print("v = ".out. String[] s1 = new String[5].println(Arrays. "Hi". v. Sin embargo.arraycopy(v. new Integer(47)). "Hi"}. Arrays2. esta es sobrecargada para todas las primitivas. new Integer(99)). Arrays2. u). s2)).fill(v. Arrays. Esto es llamado una copia superficial(vea el Apéndice A). v). Integer[] v = new Integer[5].out. y para Object. el equal() de la clase envolvente de la primitiva es utilizada. si se copia arreglos de objetos solo las referencias son copiadas -no hay duplicación de los objetos. 47). Arrays. el desplazamiento dentro del arreglo fuente de donde se comenzará a copiar. Para ser igual. System.Integer[] u = new Integer[10].length). u). "Hi"). Arrays. Integer. Arrays2. Arrays.fill(s1. 0. el desplazamiento dentro del arreglo destino donde la copia comienza.print("u = ". System. System. a2[3] = 11.equals(s1. u. Arrays. } } ///:~ Los argumentos de arraycopy() son el arreglo fuente.fill(a1. el arreglo destino. public class ComparingArrays { public static void main(String[] args) { int[] a1 = new int[10].equals(a1.equals() para int). System.fill(u.out. cualquier violación de los límites del arreglo producirá una excepción.equals(a1.println(Arrays. "Hi".java // Using Arrays.println(Arrays. String[] s2 = {"Hi". por ejemplo.*.equals() import java. 47). a2)). Comparando arreglos Los arreglos proporcionan la sobrecarga del método equals() para comparar la igualdad de arreglos enteros. u. El ejemplo muestra que los arreglos de primitivas y los arreglos de objetos pueden ser copiados. utilizando equals() para cada elemento (Para primitivas.fill(a2. Naturalmente.

pero entonces uno de los elementos es cambiado así es que la segunda línea para la salida es “falso”. y un valor positivo si el argumento es mayor que el objeto actual. import java. Con una llamada de retorno. 352 Pensando en Java www. y la parte del código que siempre es la misma siempre realizara una llamada de retorno al código que cambia.Comparable. Una meta primaria en el diseño de programación es “separar cosas que cambian de cosas que se quedan iguales”. Esta es una interfase muy simple con un solo método. todos los elementos de s1 apuntan a el mismo objeto.sort(): //: c09:CompType. import com. Comparaciones de elementos de un arreglo Una de las características ausentes en las librerías Java 1. cero si el argumento es igual. la igualdad esta basada en el contenido (mediante Object. En Java 2.*. así es que la salida es “verdadera”.lang. pero s2 tiene cinco objetos que son uno mismo. He aquí una clase que implementa Comparable y demuestra la forma de comparar que utiliza el método de librería estándar de Java Array. Esto fue una situación confuso para alguien que esperaba una librería estándar adecuada. La primera es con el método de comparación natural que es concedido a una clases implementando la interfase java. pero se debe ser capaz de reconocer que esto no produce un código que sea fácil de reutilizar para los nuevos tipos. compareTo(). Sin embargo.util.*.util.Originalmente. y las cosas que cambian de un uso al otro es la forma en que los objetos son comparados.1 son las operaciones algorítmicas -incluso la simple ordenación. una estrategia es escribir un método diferente de ordenamiento para cada tipo diferente. hay dos formas de proporcionar funcionalidad de comparación.java // Implementing Comparable in a class. Un problema con escribir código genérico para ordenar es que se deben realizar comparaciones basadas en el tipo actual de objeto. En el último caso. el código que se queda igual es el algoritmo de ordenación general. a1 y a 2 son exactamente iguales. Afortunadamente. Claro.com . y aquí.0 y 1. Así es que en lugar de escribir todo el código de comparación en muchas rutinas de formas diferentes. Este método toma otro objeto Object como argumento.bruceeckel. la técnica de llamada de retorno (callback)es utilizada. Java 2 soluciona la situación. la parte del código que varía de caso a caso es encapsulada dentro de su propia clase. y produce un valor negativo si el argumento es menor que el objeto actual.BruceEckel. al menos para el problema del ordenamiento.equals()) así es que el resultado es “verdadero”.

} private static Random r = new Random(). j = " + j + "]".randInt()). se obtiene un mensaje de error en tiempo de compilación cuando se intenta llamar a sort().public class CompType implements Comparable { int i. compare() y equals(). y los valores j son ignorados. creando una clase separada que implemente una interfase llamada Comparator. return (i < rvi ? -1 : (i == rvi ? 0 : 1)). El método randInt() estático produce valores positivos entre cero y 100. Esto tiene dos métodos.abs(r. no se tiene que implementar 353 .fill(a. Aquí. y el método generator() produce un objeto que implementa la interfase Generator. } } ///:~ Cuando se define la función de comparación.sort(a). Esto crea objetos CompType inicializándolos con valores aleatorios. } }. Si Comparable no fue implementado. } public static Generator generator() { return new Generator() { public Object next() { return new CompType(randInt(). se es responsable por decidir que significa comparar uno de sus objetos con otros. generator()). int n2) { i = n1. solo los valores i son utilizados len la comparación. Ahora supongamos que alguien entrega una clase que no implementa Comparable.print("after sorting. } public String toString() { return "[i = " + i + ". el generador es utilizado para llenar un arreglo de CompType. a). Arrays. a = ". que luego es ordenado. Sin embargo. se debe utilizar una segunda estrategia para comparar objetos.i. public CompType(int n1. a). a = ". Arrays2. Arrays2. En el main(). pero se decide que no gusta la forma en que trabaja y preferiblemente se tenga una función de comparación diferente para ese tipo. Para hacer esto. } public int compareTo(Object rv) { int rvi = ((CompType)rv). int j. j = n2. creando una clase interna anónima (vea el capítulo 8). o se entrega esta clase que implementa Comparable.nextInt()) % 100. Arrays2.print("before sorting. } public static void main(String[] args) { CompType[] a = new CompType[10]. private static int randInt() { return Math.

a = ". Arrays2.bruceeckel. import java. dado que en cualquier momento se puede crear una clase que herede implícitamente de Object. a = ". Arrays2. Así es que se puede simplemente utilizar el equals() por defecto de Objects y satisfacer el contrato impuesto por la interfase.fill(a. cero o un entero positivo si el primer argumento es menor. a). import java. Arrays2. Arrays.BruceEckel.print("after sorting. a = ".reverseOrder() produce la referencia a el Comparator.j.util. Como segundo ejemplo. igual o mayor que el segundo respectivamente.print("before sorting.sort(a. 354 Pensando en Java www.print("before sorting. import com.generator()).util. public class Reverse { public static void main(String[] args) { CompType[] a = new CompType[10]. Collections.java // Implementing a Comparator for a class. } } public class ComparatorTest { public static void main(String[] args) { CompType[] a = new CompType[10]. } } ///:~ La llamada a Collections. Arrays2.j. a = ".equals() a no ser por necesidades especiales de rendimiento. CompType. el siguiente Comparator compara objetos CompType basados en sus valores j en lugar de sus valores i: //: c09:ComparatorTest. a). que tiene un equals().util.fill(a. } } ///:~ El método compare() debe retornar un entero negativo.reverseOrder() Comparator. a). a). Arrays2.sort(a. Arrays.print("after sorting. Esto puede ser fácilmente aplicado a el CompType: //: c09:Reverse. CompType. return (j1 < j2 ? -1 : (j1 == j2 ? 0 : 1)).*. class CompTypeComparator implements Comparator { public int compare(Object o1.*. int j2 = ((CompType)o2). Object o2) { int j1 = ((CompType)o1).*.*.java // The Collecions.com . import com.generator()).reverseOrder()).util. new CompTypeComparator()). La clase Collections (que veremos mas adelante) contiene un solo Comparator que invierte el ordenamiento natural. Arrays2.bruceeckel.

import java. public class AlphabeticComparator implements Comparator{ public int compare(Object o1. esto debe ser agregado al paquete "util": //: com:bruceeckel:util:AlphabeticComparator. Arrays2.compareTo( s2.java 355 .toLowerCase(). } } ///:~ Algo que se notará acerca de la salida en el algoritmo de ordenamiento de cadenas es la lexicografía.bruceeckel.*. Esto cubre un gran agujero en las librerías de Java lo crea o no.RandStringGenerator(5)). new Arrays2.Ordenando un arreglo Con los métodos de ordenamiento incluidos. no hay soporte en Java 1.1 para ordenar cadenas! Aquí hay un ejemplo que genera objetos String de forma aleatoria y luego los ordena: //: c09:StringSorting. seguido por todas las palabras que comienzan con minúscula.bruceeckel. y se puede hacer esto definiendo una clase Comparator.0 o 1.java // Keeping upper and lowercase letters together. Arrays.*. sa). package com. Para reutilizar. He aquí una prueba utilizando AlphabeticComparator(): //: c09:AlphabeticSorting. El método compareTo() original para cadenas proporciona la funcionalidad deseada. import com.util. Arrays2.print("Before sorting: ".print("After sorting: ". se puede ordenar cualquier arreglo de primitivas. String s2 = (String)o2.toLowerCase()). y cualquier arreglo de objetos que implemente Comparable o tenga un Comparator asociativo. sa).*. Las guías telefónicas típicamente son ordenadas de esta forma). public class StringSorting { public static void main(String[] args) { String[] sa = new String[30]. } } ///:~ Cada cadena es convertida a minúsculas antes de la comparación.fill(sa. Arrays2. ya que colocas todas las palabras comenzando con letras mayúsculas primero. Object o2) { String s1 = (String)o1.util.util.util. de esta manera sobrescribir el comportamiento por defecto de Comparable para las cadenas. return s1.sort(sa).java // Sorting an array of Strings. Se puede querer agrupar las letras juntas sin importar si son mayúsculas o minúsculas. import java.

Arrays. se puede realizar una búsqueda rápida de un ítem en particular utilizando Arrays.util. int location = Arrays.RandIntGenerator(1000). Arrays2.*.RandStringGenerator(5)).out.print("Before sorting: ". Así es que no necesita perder tiempo preocupándose por rendimiento a no ser que su herramienta de perfilado indique que el proceso de ordenamiento es un cuello de botella.fill(a. Arrays. los resultados pueden ser impredecibles.com . sa). Sin embargo. gen). Arrays2. import java. luego produce valores para buscar: //: c09:ArraySearching. while(true) { int r = gen.binarySearch().binarySearch(a. Arrays2.print("Sorted array: ". } } ///:~ El algoritmo de ordenamiento que es utilizado en la librería estándar de Java esta diseñado para ser óptimo para un tipo particular que se esté ordenando -un Quicksort para primitivas.util. es muy importante que no intente utilizar binarySearch() en un arreglo sin ordenar. Búsqueda en un arreglo ordenado Una vez que un arreglo es ordenado. import com.fill(sa. if(location >= 0) { System. break.sort(sa. Los siguientes ejemplos utilizan un RandIntGenerator para llenar un arreglo.next(). public class AlphabeticSorting { public static void main(String[] args) { String[] sa = new String[30].RandIntGenerator gen = new Arrays2. import java. sa).util.println("Location of " + r + " is " + location + ".binarySearch(). Arrays2. y un ordenamiento de mezcla estable para objetos.*. a[" + location + "] = " + a[location]).BruceEckel.sort(a).bruceeckel. Arrays2. // Out of while loop } } 356 Pensando en Java www. r). a). import com.util.*.*.print("After sorting: ". new Arrays2. public class ArraySearching { public static void main(String[] args) { int[] a = new int[100].java // Using Arrays.// Keeping upper and lowercase letters together.bruceeckel. new AlphabeticComparator()). Arrays2.

comp).println("Index = " + index).bruceeckel. Si el arreglo contiene elementos duplicados. Arrays. El valor producido es -(punto de inserción) .fill(sa. hasta que uno de ellos es encontrado. comp).java puede ser modificado para realizar una búsqueda: //: c09:AlphabeticSearch.binarySearch(sa.} } ///:~ En el bucle while. 357 . no hay garantía que uno sea encontrado. el programa AlphabeticSorting.1 El punto de inserción es el índice a el primer elemento mayor que la clave. System.binarySearch() produce un valor mayor o igual a cero si el ítem de búsqueda es encontrado. Por ejemplo. De otra forma. int index = Arrays. public class AlphabeticSearch { public static void main(String[] args) { String[] sa = new String[30]. Arrays2.*. AlphabeticComparator comp = new AlphabeticComparator().RandStringGenerator(5)). sa[10]. tanto como tolerarlos. new Arrays2.util.sort(sa. Solo en casos de cuellos de botella de rendimiento debe remplazar el TreeSet con un arreglo mantenido a mano. import com. el éxito es garantido porque el ítem de búsqueda es extraído del arreglo a si mismo.size() si todos los elementos son menores que la clave especificada. Si ordena un arreglo utilizando un Comparator (arreglos de primitivas no permiten ordenar con un Comparator). } } ///:~ El Comparator debe ser pasado a la binarySearch() sobrecargada como el tercer argumento. utilice un TreeSet. El algoritmo no esta diseñado para soportar elementos duplicados.*. produce un valor negativo representando el lugar donde el elemento debe ser insertado si se quiere mantener un arreglo ordenado a mano. En el ejemplo mas arriba.java // Searching with a Comparator. Si necesita una lista ordenada de elementos no duplicados.out.util. Arrays. de debe incluir el mismo Comparator cuando se realice una binarySearch() (utilizando la versión sobrecargada de la función que proporciona). los valores aleatorios son generados como ítems de búsqueda. o a. sin embargo. import java. que será introducido mas tarde en este capítulo. Este cuida los detalles automáticamente.

358 Pensando en Java www.com . "contenedora". Esto fue mejor de lo que estaba disponible antes de las clases contenedoras de C++ (nada).1. he visto librerías de contenedores que consisten en una sola clase. pero no fueron bien trasladadas en Java. colas y deques (colas con doble final. la primera y mas eficiente elección para almacenar un grupo de objetos debería ser un arreglo. El diseño de una librería de contenedores es difícil (cierto en la mayoría de los problemas de diseño en librerías). En C++. que actúa como una secuencia lineal y un arreglo asociativo al mismo tiempo. almacena solo un objeto de cada valor. las clases contenedoras cubren las bases con muchas clases diferentes.Resumen de arreglos Para resumir lo que se ha visto hasta ahora. También completan la funcionalidad de la librería de contenedores. o si se necesita una forma mas sofisticada de almacenar sus objetos. Así es que. y Maps un arreglo asociativo que deja que se asocie cada objeto con otro objeto -Las clases contenedoras de Java automáticamente cambiaran de tamaño a ellas mismas. y si se está forzado a esta elección si se quiere almacenar un grupo de primitivas. Java proporciona una librería de clases contenedoras para solucionar este problema. Introducción a los contenedores Para mi. a diferencia de los arreglos se puede colocar cualquier cantidad de objetos sin preocuparse acerca de que tan grande hacer el contenedor cuando de esta escribiendo el programa. proporcionando el comportamiento de listas enlazadas. En el otro extremo. que se pronuncian "decks"). Los contenedores de Java 2 representan un cuidadoso rediseño4 de mas bien pobres mostrados en Java 1.BruceEckel. donde no se conoce en el momento en que se esta escribiendo en programa cuantos objetos se van a necesitar. las clases contenedoras son una de las mas poderosas herramientas para programar en bruto porque incrementa significativamente el músculo de programación. Set y Map. los tipos básicos son List. En el resto de este capítulo veremos un caso mas genera. Entre sus otras características -Set por ejemplo. La librería de contenedores de Java 2 logra un balance: la total funcionalidad 4 Por Joshua Bloch en Sun.0 y 1. Se puede solucionar un sorprendente número de problemas utilizando estas herramientas. Algunos de los rediseños hacen cosas herméticas y mas acertadas.

"Spot"). m. simplemente se crea un Map cuyos valores sean Maps (y los valores de aquellos Maps pueden ser Maps. es conveniente ver partes de un Map creando una Collection para representar esa parte. } static Map fill(Map m) { m. De esta forma un Map puede retornar un Set de estas claves. luego iremos por los detalles. a menudo con alguna regla aplicada. 2.put("dog". esto puede parecer como que debe ser una Collection de pares. A primera vista.put("cat". o un Set de sus pares. Veremos primero las características de los contenedores. Map: un grupo de pares de objetos del tipo clave valor. los contenedores se despliegan bien sin ninguna ayuda. Una List debe almacenar los elementos en una secuencia particular y un Set no puede tener elementos duplicados (una bolsa. una Collection de sus valores. Desplegando contenedores A diferencia de los arreglos. El resultado puede verse como un poco estrafalario en sus sitios. y como elegirlos. c. pueden ser fácilmente expandidos a múltiples dimensiones sin agregar nuevos conceptos. return c.add("cat"). Aquí hay un ejemplo que también introduce a los tipos básicos de contenedores: public class PrintingContainers { static Collection fill(Collection c) { c.que se espera de una librería de contendores madura y la facilidad para aprender y utilizar de las clases contenedoras de C++ y otras librerías de contenedores similares. etc). "Bosco"). así que es mas claro tener otro concepto. return m. pero cuando se trata de implementar de esta forma el diseño se vuelve complicado. 359 . Colección: un grupo de elementos individuales. y finalmente aprenderemos por que hay versiones diferentes de algunos contenedores. que no esta implementada en la librería de contenedores de Java -dado que la List proporciona suficiente de esa funcionalidadno tiene ese tipo de reglas).add("dog"). La librería de contenedores de Java 2 toma el tema de "almacenar los objetos" y lo divide en dos conceptos distintos: 1.put("dog". como los arreglos. "Rags"). c. Los Map.add("dog"). Por el otro lado. m.

Para agregar ítems a cualquier Collection. se busca -mas o menos como se estuviera indexado dentro de un arreglo (Maps son también llamados arreglos asociativos. hay un método add(). sin embargo. Esto incluye la List. se puede ver que el comportamiento por defecto cuando se despliega en pantalla (proporcionado por diversos métodos toString()) producen resultados bastante legibles. el HashMap.out. con cada elemento separado con una coma. cat] [cat. mas bien como una pequeña base de datos. La Map almacena pares de claves y valores.} public static void main(String[] args) { System. Y el Map solo acepta uno de cada tipo de ítem. y HashSet es un tipo de Set.out.println(fill(new ArrayList())). System.println(fill(new HashSet())).com . no por el orden en el cual aparecen -para eso se debe utilizar una List). Si se mira la salida.println(fill(new HashMap())). respectivamente. [dog. basado en 360 Pensando en Java www. hay dos categorías básicas en las librerías de contenedores. La ArrayList es un tipo de List. Esto será mostrado mas tarde. La List almacena los objetos exactamente como fueron entrados. se esta preocupado solo por si alguno es o no es miembro del Set. así es que no es necesario soporte adicional para el despliegue como con los arreglos. solo acepta uno de cada uno de los objetos y usa su propio método interno para ordenarlo (en general. El método sobrecargado fill() rellena Collections y Maps. Se puede inmediatamente ver el comportamiento básico de los diferentes contenedores. El programa mas arriba utiliza un estilo de Map. valores en la derecha). Un Map esta rodeado por llaves. Para ) agregar elementos a un Map hay un método put() que toma una clave y un valor como argumentos.out. y Set. dog] {cat=Rags. La categoría Collection solo almacena un ítem en cada ubicación (El nombre confunde un poco dado que la librería entera de contenedores es a menudo llamada "colección"). dog. El Set. La distinción esta basada en el número de ítems que son almacenados en cada ubicación del contenedor. El ejemplo anterior solo muestra como se agregan elementos y no muestra los elementos almacenados luego que se agregaron. System.BruceEckel. Si se tiene un Map que asocia estados con sus capitales y se quiere saber la capital de Ohio. con cada clave y valor asociado con un signo de igual (claves en la izquierda. sin ningún ordenamiento o edición. que almacena un grupo de ítems en una secuencia específica. dog=Spot} Una Collection es desplegada rodeada por paréntesis cuadrados. que solo permite la suma de un ítem de cada tipo. } } ///:~ Como se ha mencionado antes.

tenemos la interfase generadora que produce el Pair: //: com:bruceeckel:util:Collections2.la clave. Exactamente como los arreglos.fill() method. } } ///:~ Además.java package com. i < 10.Arrays. public class FillingLists { public static void main(String[] args) { List list = new ArrayList(). pero Map requiere su propia interfase generadora dado que un par de objetos (una clave y un valor) deben ser producidos por cada llamada a next(). Pair(Object k. La interfase generadora definida previamente trabajara para Collections. hay una clase amiga llamada Collections que contiene métodos utilitarios estáticos incluyendo uno llamado fill(). Object v) { key = k. value = v.add("").bruceeckel. import java. Collections. i++) list. "Hello").println(list).java // Para llenar cualquier tipo de contenedor 361 .bruceeckel. for(int i = 0.fill(list. public class Pair { public Object key. System. } } ///:~ Este método es aún menos útil por el hecho de que solo pueden remplazar elementos que ya se encuentran en la List. he aquí una librería complementaria llamada Collections2 (parte de com.util. y no pueden agregar nuevos elementos. Este fill() también duplica una sola referencia a objeto por todo el contenedor. y también solo trabaja para objetos List y no para Sets o Maps: //: c09:FillingLists.util por conveniencia) con un método fill() que utiliza un generador para agregar elementos.util. y permite especificar el número de elementos que se quiere agregar. y también tiene su propio ordenamiento interno y no importa el orden en el cual se entran los ítems. Aquí tenemos la clase Pair: //: com:bruceeckel:util:Pair.java // The Collections. Cargando contenedores A pesar de que el problema de desplegar contenedores ya esta solucionado. el cargar los contenedores padece de la misma deficiencia que java.out. value.*.util. Para ser capaz de crear ejemplos interesantes.

i++) { Pair p = gen. import java.next()).com .put(p. Generator gen. public static class StringPairGenerator implements MapGenerator { private int index = -1. // Produce una secuencia de arreglos en 2D: public static class StringGenerator implements Generator { 362 Pensando en Java www. public class Collections2 { // Llenar un arreglo utilizando un generador: public static void fill(Collection c.pairs).// utilizando un objeto generador.length. } public static void fill(Map m. } public Pair next() { // Fuerza a que el índice se pliegue: index = (index + 1) % d. int count) { for(int i = 0. i < count. package com. public RandStringPairGenerator(int len) { gen = new Arrays2.RandStringGenerator gen. private String[][] d. } } public static class RandStringPairGenerator implements MapGenerator { private Arrays2. m. p. d[index][1]).next(). } } // Utilice un grupo de datos predefinido: public static StringPairGenerator geography = new StringPairGenerator( CountryCapitals. } public StringPairGenerator reset() { index = -1.next().key.util. MapGenerator gen. } } // Objeto por defecto así es que no se tiene // que crear el propio: public static RandStringPairGenerator rsp = new RandStringPairGenerator(10).next()).add(gen. i++) c.value).RandStringGenerator(len).*. gen.bruceeckel.util. int count) { for(int i = 0. } public Pair next() { return new Pair(gen. i < count. return new Pair(d[index][0].BruceEckel. public StringPairGenerator(String[][] data) { d = data. return this.

public StringGenerator(String[][] data. static StringGenerator capitals = StringGenerator(CountryCapitals. El StringGenerator también toma un arreglo String de dos dimensiones pero genera un solo ítems en lugar del Pairs."Luanda"}. return this."Algiers"}. countries. que produce pares de String dando un arreglo String de dos dimensiones."Bangui"}. Los objetos estáticos rsp. Además.pairs. los duplicados simplemente serán ignorados. 363 . Las dos versiones de fill() toman un argumento que determina en número de ítems para agregar a el contenedor. {"CAPE VERDE"."Porto-Novo"}. {"ANGOLA". que crea cualquier cantidad de pares de cadenas con galimatías cuyo largo esta determinado por el argumento del constructor. Este grupo esta en fuente pequeña para prevenir que ocupe espacio innecesario: //: com:bruceeckel:util:CountryCapitals. {"BENIN". que consisten en nombres de países y sus capitales.0). } public Object next() { // Fuerza a el índice a plegarse: index = (index + 1) % d. y capitals proporcionan generadores previamente creados."Ouagadougou"}. y StringPairGenerator. los últimos tres utilizando todos los países del mundo y sus capitales. Aquí tenemos un grupo de datos predefinidos. position = pos."Yaounde"}. } public StringGenerator reset() { index = -1. y su se colocan los pares dentro del Map.1). } } // Use public new public new } ///:~ un grupo de datos predefinido: static StringGenerator countries = StringGenerator(CountryCapitals.bruceeckel.private String[][] d. {"CENTRAL AFRICAN REPUBLIC"."Gaberone"}. {"CAMEROON". hay dos generadores para el Map: RandStringPairGenerator.util. {"BURKINA FASO". private int index = -1.length. {"BOTSWANA". public class CountryCapitals { public static final String[][] pairs = { // Africa {"ALGERIA". {"BURUNDI"."Praia"}."Bujumbura"}. los generadores comenzarán nuevamente desde el principio. Debe notarse que si se crean mas pares de los que están disponibles.java package com. return d[index][position]. private int position. int pos) { d = data.pairs. geography.

{"JORDAN"."Singapore"}.com ."Manama"}. {"BISSAU". {"THE GAMBIA". {"NAURU"."Conakry"}."Sao Tome"}."Sana'a"}."Kuala Lumpur"}."Ankara"}."Kuwait City"}. {"BANGLADESH". {"VANUATU"."Honaira"}."Dhaka"}. {"QATAR"."Jerusalem"}. {"MOROCCO"."Moroni"}."Doha"}. {"GUINEA". {"THAILAND". {"UNITED ARAB EMIRATES"."Amman"}."Rabat"}. {"CHINA"."Kampala"}.BruceEckel."Antananarivo"}. {"NIGER". {"VIETNAM"."Suva"}. {"MALAYSIA"."Port Moresby"}. {"FIJI". {"SOUTH KOREA". {"SIERRA LEONE"."Tunis"}. {"SAO TOME E PRINCIPE". {"SOLOMON ISLANDS"."Addis Ababa"}. {"TONGA". {"SOUTH AFRICA"."Abuja"}. {"PHILIPPINES". {"MALI"."Phnom Penh"}."Cairo"}. {"DJIBOUTI". {"PAPUA NEW GUINEA". {"BAHRAIN"."Lusaka"}. {"NIGERIA"."Palikir"}."Kabul"}. {"MAURITANIA". {"PAKISTAN"."N'djamena"}."Thimphu"}."Islamabad"}."Bairiki"}. {"LIBYA"."Maputo"}."Nicosia"}. {"GHANA"."Beijing"}."Port Louis"}. {"SOMALIA"."Maseru"}."Manila"}. {"LESOTHO"."Riyadh"}. {"SEYCHELLES"."Dodoma"}."Asmara"}."Bangkok"}."Male"}."Kigali"}. {"UGANDA". {"TUVALU". {"WESTERN SAMOA". {"GUINEA"."Rangoon"}."Freetown"}. {"TOGO". {"MARSHALL ISLANDS". {"RWANDA". {"MONGOLIA". {"THE MALDIVES". // Australia and Oceania {"AUSTRALIA"."Colombo"}. {"TURKEY"."P'yongyang"}."Wellington"}."< Port-Vila"}."Taipei"}."Nairobi"}."New Delhi"}."Accra"}."Mbabane"}. {"CAMBODIA". {"LIBERIA"."Damascus"}."Jakarta"}."Brazzaville"}. {"OMAN"."Abu Dhabi"}."Monrovia"}. {"SENEGAL". {"CONGO"."Dijibouti"}. {"TAIWAN (REPUBLIC OF CHINA)"."Khartoum"}."Hanoi"}. {"MADAGASCAR"."Victoria"}. {"LEBANON". {"NEPAL". {"MOZAMBIQUE"."Dakar"}. {"NAMIBIA"."Baghdad"}. {"SUDAN". {"BRUNEI". 364 Pensando en Java www. {"TANZANIA". {"CETE D'IVOIR (IVORY COAST)". {"TUNISIA". {"YEMEN". {"NEW ZEALAND"."Muscat"}."Tripoli"}. {"MICRONESIA"."Yamoussoukro"}. {"SYRIA". {"JAPAN". {"KIRIBATI"."Libreville"}. {"SRI LANKA"."Apia"}."-"}."Canberra"}."Lilongwe"}."Banjul"}. {"MYANMAR (BURMA)"."Beirut"}."Vientiane"}."Harare"}."Lome"}."Mogadishu"}. {"ERITREA". {"DEMOCRATIC REPUBLIC OF THE CONGO (ZAIRE)"."Bandar Seri Begawan"}. {"PALAU"."Kinshasa"}."Ulan Bator"}. {"BHUTAN"."Tehran"}."Bissau"}. {"COMOROS". {"GABON"."Tokyo"}. {"ETHIOPIA". {"EGYPT"."Nuku'alofa"}. {"LAOS". {"EQUATORIAL GUINEA". {"ISRAEL"."Niamey"}."Windhoek"}. {"NORTH KOREA". {"SAUDI ARABIA". {"ZIMBABWE"."Bamako"}. // Asia {"AFGHANISTAN"."Malabo"}. {"SWAZILAND". {"ZAMBIA".{"CHAD". {"MALAWI"."Nouakchott"}."Seoul"}. {"INDIA"."Pretoria/Cape Town"}."Fongafale"}."Katmandu"}."Koror"}. {"CYPRUS". {"IRAN". {"SINGAPORE". {"INDONESIA"."Dalap-Uliga-Darrit"}. {"IRAQ"."Yaren"}. {"MAURITIUS". {"KUWAIT". {"KENYA".

{"BOLIVIA"."Belgrade"}."Bridgetown"}. {"AUSTRIA"."Washington. {"BELIZE"."Alma-Ata"}. {"HAITI". {"IRELAND"."Tashkent"}."Nassau"}."Kyiv"}. {"NICARAGUA"."Prague"}. {"GRENADA". {"RUSSIA"."Buenos Aires"}. {"MACEDONIA". {"CANADA"."Almaty"}. {"ST."Copenhagen"}. {"MONACO"."Caracas"}."Berne"}."Panama City"}. {"SERBIA"."Luxembourg"}."Valletta"}."Brasilia"}."Santo Domingo"}."Minsk"}. KITTS". {"JAMAICA". D. {"ICELAND". {"SWEDEN". {"KAZAKSTAN". {"UNITED STATES OF AMERICA". {"MOLDOVA"."Kingstown"}. {"TURKMENISTAN"."Sarajevo"}. {"TAJIKISTAN"."Ottawa"}. {"LATVIA". {"GUYANA"."Paramaribo"}."Tbilisi"}."Montevideo"}. {"ECUADOR". {"BAHAMAS". {"GREECE"."Bratislava"}. {"ROMANIA". {"DOMINICAN REPUBLIC"."Ljujiana"}. {"BELGIUM"."Podgorica"}."Vilnius"}. {"DOMINICA". {"PANAMA"."-"}."}."Monaco"}."Rome"}. {"POLAND". LUCIA". {"SPAIN"."San Marino"}."Lisbon"}."Guatemala City"}. {"UNITED KINGDOM". {"SWITZERLAND". {"GERMANY"."Vienna"}."Baku"}. // South America {"ARGENTINA". {"VATICAN CITY"."Skopje"}."Athens"}."Santiago"}. {"HUNGARY". {"CROATIA".// Eastern Europe and former USSR {"ARMENIA"."Vaduz"}."Paris"}. {"SAN MARINO"."Basseterre"}. {"ST."Moscow"}."Stockholm"}."Bogota"}."Castries"}. {"GEORGIA". {"PERU". {"AZERBAIJAN". {"PARAGUAY". VINCENT AND THE GRENADINES". {"THE NETHERLANDS"."Yerevan"}. {"EL SALVADOR"."Port of Spain"}. {"ESTONIA"."Riga"}."Managua"}."London"}. // Europe {"ALBANIA". {"BOSNIA"."Berlin"}."Oslo"}."Tegucigalpa"}. {"MEXICO". {"NORWAY"."Belmopan"}. {"SLOVENIA". {"ANDORRA". {"COSTA RICA"."---"}."Warsaw"}."Madrid"}."San Jose"}."Quito"}. {"NEVIS". {"BARBADOS". {"TRINIDAD AND TOBAGO"."Lima"}. } ///:~ 365 ."Asuncion"}."-"}. {"MALTA"."Dublin"}.C."Saint John's"}. {"ST. {"BELARUS (BYELORUSSIA)"."Andorra la Vella"}. {"LUXEMBOURG"."San Salvador"}. {"CHILE". {"CUBA"."Amsterdam"}."Budapest"}. {"HERZEGOVINA"."Port-au-Prince"}. {"COLOMBIA". {"LIECHTENSTEIN". {"FINLAND"."Kingston"}. {"ITALY". {"PORTUGAL"."Helsinki"}."Roseau"}. {"CZECH REPUBLIC". {"BRAZIL". {"KYRGYZSTAN"."Bucharest"}."Chisinau"}."Mexico City"}. {"UKRAINE"."Zagreb"}. {"FRANCE"."Havana"}."Tallinn"}. {"SLOVAKIA". {"URUGUAY". {"SURINAME". }. {"VENEZUELA". {"GUATEMALA"."Dushanbe"}."Reykjavik"}."Ashkabad"}. {"MONTENEGRO". {"HONDURAS". {"UZBEKISTAN"."Saint George's"}. {"DENMARK"."Tirana"}. {"LITHUANIA"."Brussels"}."Sucre (legal)/La Paz(administrative)"}."Georgetown"}. // North and Central America {"ANTIGUA AND BARBUDA".

out.fill(list2. System. Map m = new HashMap(). } } ///:~ Con estas herramientas se puede fácilmente probar los contenedores llenándolos con datos interesantes.org).println(m2). Collections2.out. Map m2 = new HashMap(). Collections2.fill(m2.out. System. Hay una simple prueba utilizando el método fill() y generadores: //: c09:FillTest. Esto sucede porque el programador de la clase contenedor no tiene idea de que tipo específicamente se va a colocar en el contenedor y haciendo que el contenedor almacene solo un tipo impide que se convierta en una herramienta de propósito general. Collections2. System.bruceeckel.out. public class FillTest { static Generator sg = new Arrays2.util. System.fill(m.java import com.capitals. Collections2.BruceEckel.println(set + "\n").*. Desventaja de los contenedores: tipos desconocidos La “desventajas” de utilizar los contenedores de Java es que se pierde la información de tipo cuando se coloca un objeto en un contenedor. 25).util. 25). System. Collections2. Así es que en lugar de eso.println(list + "\n").fill(set.geography.rsp. Collections2.Python.fill(list. import java.println(list2 + "\n"). luego procesado para crear un programa Python (vea www.RandStringGenerator(7). List list2 = new ArrayList(). 25).out. sg. public static void main(String[] args) { List list = new ArrayList(). Collections2. 25).println(m + "\n").Esto es simplemente un arreglo de dos dimensiones de datos String 5.*. Set set = new HashSet(). 25). sg. 366 Pensando en Java www.com . Collections2. el contenedor 5 Este dato fue encontrado en la Internet.

} void print() { System. se puede pensar un ArrayList como “un arreglo que automáticamente se expande a si mismo”. colocar objetos utilizando add(). excepto porque: 1. dado que no son heredadas de nada). Primero.almacena referencias de cualquier tipo (Claro. gatos.java public class Dog { private int dogNumber. las clases C a t y Dog son creadas: //: c09:Cat. Para las personas que comienzan. Dog(int i) { dogNumber = i.println("Cat #" + catNumber). Aquí hay un ejemplo utilizando el típico caballo de tiro de los contenedores. aún si se piensa almacenar.java public class Cat { private int catNumber. Dado que la información de tipo es desechada cuando se coloca una referencia a un objeto en un contenedor. la única cosa que el contenedor conoce que almacena es una referencia a un objetos.out. Cat(int i) { catNumber = i. El lado bueno de esto es que Java no deja que se desaproveche los objetos que se coloca en un contenedor. no hay restricciones del tipo de objeto que se va a colocar en el contenedor. Utilizando un ArrayList es directo: crear uno. } void print() { System. 367 . 2. Cualquiera podría simplemente colocar perros en el contenedor. ArrayList también tiene un método size() para saber cuantos elementos fueron agregados para de esta forma no salirse del arreglo causando una excepción.println("Dog #" + dogNumber). digamos. se obtendrá una excepción en tiempo de ejecución cuando se intente sacar la referencia del contenedor y convertirla a gato. Esto es una gran solución.out. } 6 Este es el lugar donde la sobrecarga de operadores sería bueno. } } ///:~ //: c09:Dog. Dado que la información de tipo se pierde. Se debe realizar una conversión para corregir el tipo antes de utilizarlo. ArrayList. Si coloca un perro en un contenedor de gatos y luego intenta tratar todo en el contenedor como gatos. esto no incluye primitivas. y mas tarde sacarlos con get() utilizando un índice -exactamente igual que con un arreglo pero sin los paréntesis cuadrados6.

a pesar de la carencia y la dificultad.*.} ///:~ C a ts y Dogs son colocados dentro del contenedor y luego extraídos: //: c09:CatsAndDogs.BruceEckel. for(int i = 0.add(new Dog(7)). for(int i = 0. y se descubre solo en una parte alejada del programa una excepción de que un objeto equivocado fue colocado en el contenedor. public class CatsAndDogs { public static void main(String[] args) { ArrayList cats = new ArrayList(). // Not a problem to add a dog to cats: cats. de otra forma se obtendrá un error de sintaxis. Esto es mas que una molestia. Dado que ArrayList almacena Objects. Un caso es bastante especial: la clase String algo de ayuda extra del compilador para 368 Pensando en Java www.util.add(new Cat(i)). // Dog is detected only at run-time } } ///:~ Las clases C a t y Dog son distintas -no tienen nada en común excepto que son Objects (Si no se indica explícitamente de que clase esta heredando. Por otro lado. Algunas veces funciona de todas formas Dejamos de lado que en algunos casos las cosas parecen trabajar correctamente sin conversiones inversas del tipo original. i < 7. también se pueden agregar objetos Dog sin quejas en tiempo de compilación o en tiempo de ejecución.size(). import java. i++) cats. Cuando extraiga lo que se piensa que es un objeto C a t utilizando el método get(). i++) ((Cat)cats. Luego se necesita encerrar la expresión entera entre paréntesis para forzar la evaluación de esta conversión cuando llamamos el método print() para Cat.com . entonces se debe encontrar donde la inserción sucedió. se heredará automáticamente de Object). no solo se pueden colocar objetos C a t dentro de este contenedor utilizando el método add() de ArrayList. se traerá la referencia a un objeto que se debe convertir a C a t.print(). Entonces.java // Simple container example. Es algo que puede crear dificultades para encontrar errores de programación.get(i)). Si una parte (o varias partes) de un programa insertan objetos en un contenedor. en tiempo de ejecución. cuando se intente convertir el objeto Dog en C a t se obtendrá una excepción. es conveniente comenzar con algunas clases contenedoras estandarizadas para programar. i < cats.

De esta forma. i++) mice.add(new Mouse(i)). El segundo bucle for en el main() se puede encontrar la instrucción: System.println("Mouse: " + mouse.get(i)). // Cast from Object System. que luego es utilizado donde quiera que se quiera. i++) { // No cast necessary. } public int getNumber() { return mouseNumber.toString(): public String toString() { return "This is Mouse #" + mouseNumber. public class Mouse { private int mouseNumber. Mouse(int i) { mouseNumber = i. i < mice. automatic // call to Object.out. class MouseTrap { static void caughtYa(Object m) { Mouse mouse = (Mouse)m.get(i)).caughtYa(mice.println( "Free mouse: " + mice. } } ///:~ //: c09:WorksAnyway.out. como se muestra en el siguiente ejemplo: //: c09:Mouse. MouseTrap. todo lo que se necesita para hacer que se imprima objetos de su clase es sobrecargar el método toString().*.java // In special cases. import java.getNumber()). 369 .size(). } // Override Object. i < 3.toString(): System.java // Overriding toString(). for(int i = 0. Este método produce el objeto String deseado.out.get(i)). for(int i = 0. } } } ///:~ Se puede ver que toString() esta sobrecargada en Mouse. things just // seem to work correctly.util. } } public class WorksAnyway { public static void main(String[] args) { ArrayList mice = new ArrayList(). automáticamente llamara el método toString() que esta definido en Object y puede ser sobrecargado por cualquier clase Java.hacer que trabaje como una seda.println("Free mouse: " + mice. Donde quiera que el compilador espere un objeto del tipo String y no tenga uno.

com . Ninguna conversión es necesaria. } } ///:~ Aquí hay una prueba para el nuevo contenedor: //: c09:MouseListTest. i < 3.get(index).caughtYa(mice.caughtYa(mice. get() produce un Object.java public class MouseListTest { public static void main(String[] args) { MouseList mice = new MouseList(). acepta un Object. El método caughtYa() no acepta un Mouse. que es entonces convertido a Mouse. import java. Desafortunadamente.se tendrá una excepción en tiempo de ejecución.util. public class MouseList { private ArrayList list = new ArrayList(). Una segunda estrategia para ocultar la conversión ha sido colocada dentro de MouseTrap. Una solución mas guerrera es crear una nueva clase utilizando la clase ArrayList. for(int i = 0. Se debe notar en la utilización de este método: MouseTrap. Esto es bastante presuntuoso.size(). claro. } } ///:~ 370 Pensando en Java www. Sin embargo. } public int size() { return list. for(int i = 0. así es que el para obtener el String deseado el compilador llama implícitamente a toString(). la conversión es incorrecta -si se pasa el tipo equivocado.size(). i++) MouseTrap. dado que aceptando un Object todo puede ser pasado a el método. } public Mouse get(int index) { return (Mouse)list.BruceEckel. i < mice. tal que acepte solo un tipo y produzca solo el tipo deseado: //: c09:MouseList.*.Después del signo ‘+’ el compilador espera un objeto String.add(m). public void add(Mouse m) { list.java // A type-conscious ArrayList. Esto no es bueno si hablamos de verificaciones en tiempo de compilación pero sigue siendo robusto. se puede trabajar este tipo de magia con String: no esta disponible para ninguno otro tipo. i++) mice. Haciendo una ArrayList consciente del tipo Puede que uno no quiera rendirse a este tema todavía.add(new Mouse(i)).get(i)).get(i)).

realizando algunas actividades antes de pasar la responsabilidad (vea Thinking in Patterns with Java. si se indica: mice. el cual se puede bajar de www. add() es la forma en la que se insertan objetos.Esto es similar a el ejemplo anterior. solo objetos Mouse. y mas tarde en su programa se descubre que dado que la forma en que se esta 371 . ¿Pero cuando se comienza a utilizar ArrayList. no acepta y produce objetos genéricos del tipo Object. y get() es una forma de sacarlas. el MouseList comienza a reemplazarel ArrayList. esta es la tarea primaria de un contenedor -almacenar cosas. Dado que MouseList aceptará solo un Mouse. Note que no es necesaria la conversión cuando se utiliza get() -siempre es un Mouse. Debe notarse que si MouseList en lugar de haber sido heredado de ArrayList. esto es directamente soportado por el lenguaje . indicara inmediatamente si esta utilizando un tipo inapropiado. De esta forma. Esta estrategia. y métodos exactamente igual a una ArrayList. y seleccionar múltiples elementos a la vez utilizando diferentes índices. que es mas tediosa desde el punto de vista de la codificación.com). y en el cual es útil tener especificación del tipo en tiempo de compilación. mediante plantillas Sería bueno que en una futura versión de Java soporte .BruceEckel. Sin embargo. el método add(Mouse) no podría simplemente sobrecargar el método existente add(Object) y entonces seguiría no habiendo restricción en el tipo de objeto que podría ser agregado. excepto que la nueva clase MouseList tiene un miembro privado del tipo ArrayList. hay un inconveniente: se necesita saber el tipo exacto del contenedor para utilizarlo. Tipos parametrizados Este tipo de problema no es aislado -hay varios caso en los cuales se necesita crear nuevos tipos basados en otros tipos. Esto parece no ser malo en un comienzo. Se obtendrá un mensaje de error en tiempo de compilación.add(new Pigeon()). algunas variaciones de tipos parametrizados. En el ArrayList. Después de todo. Iteratores En una clase contenedora. Este es el concepto de tipo parametrizado En C++. se debe tener una forma de colocar cosas dentro y una forma de sacarlas. el actual candidato a ganar automáticamente crea clases similares a MouseList. Si se quiere comenzar a pensar en un nivel mas alto. ArrayList es flexible -se puede seleccionar cualquier cosa en cualquier momento.

no 372 Pensando en Java www.hasNext()) ((Cat)e. Un iterator es un objeto cuyo trabajo es moverse a través de una secuencia de objetos y seleccionar cada objeto en esa secuencia sin que el cliente programador sepa o le importe acerca de la estructura de las capas mas bajas de esa secuencia. algunos iteratores pueden moverse en una sola dirección. Con el Iterator.iterator().java atrás en este capítulo. for(int i = 0. así es que puede ser utilizado en diferentes tipos de contenedores sin volver a escribir ese código? El concepto de iterator puede ser utilizado para alcanzar esta abstracción. Obtener el siguiente objeto en la secuencia con next().util. while(e. Preguntarle al contenedor para manejar un Iterator utilizando un método llamado iterator(). El Iterator de Java es un ejemplo de iterator con este tipo de restricciones. Además. es una simple implementación de un iterator. Por esta razón.add(new Cat(i)). public class CatsAndDogs2 { public static void main(String[] args) { ArrayList cats = new ArrayList().next()).utilizando el contenedor podría ser mucho mas eficiente utilizar LinkedList en lugar de ArrayList? ¿O supongamos que se quiere escribir un fragmento de código genérico que no se sabe o no importa con que tipo de contenedor se esta trabajando. 4. En la versión original. 3. volvamos a visitar el programa CatsAndDogs. por ejemplo.print().*. a menudo se encuentra aparentemente extrañas restricciones para iteratores. Ver si hay mas elementos en la secuencia con hasN ext().java // Simple container with Iterator. i < 7. pero en la siguiente versión modificada un Iterator es utilizado. pero sigue siendo poderoso (y hay un mas sofisticado ListIterator para Lists). //: c09:CatsAndDogs2. Esto es solo. Quitar el último elemento retornado por el iterator con remove(). Iterator e = cats. 2. } } ///:~ Se puede ver que en las dos últimas líneas ahora se utiliza un Iterator para moverse dentro de la secuencia en lugar de un bucle for. el método get() fue utilizado para seleccionar cada elemento. el iterator es usualmente lo que es llamado una objeto “peso ligero”: uno que es económico de crear. import java.BruceEckel. No hay mucho que se pueda hacer con uno excepto: 1. Para ver como trabajan. Este iterator estará listo para retorna el primer elemento en la secuencia de en su primer llamada al método next().com . i++) cats.

Otro ejemplo.println((String)e. sin embargo. que tiene el efecto de llamar a toString().toString(). Se puede notar que no hay información acerca del tipo de sentencia. } } public class HamsterMaze { public static void main(String[] args) { ArrayList v = new ArrayList().*. se puede ser mas explícito utilizando una conversión. hasNext() y next() tienen cuidado de esto por nosotros.hay que preocuparse por el número de elementos en el contenedor.java // Using an Iterator.out. i < 3.util. Todo lo que tiene es un Iterator. Esta idea de tomar un contenedor de objetos y pasar a través de el para realizar una operación en cada uno es poderosa. en cada caso una cadena es automáticamente producida llamando el método toString() apropiado. y eso es todo lo que se necesita conocer acerca de la secuencia: que se puede obtener el siguiente objeto. 373 . y se han convertido los objetos resultantes en ese tipo (obteniéndose una excepción en tiempo de ejecución si se está equivocado).iterator()). class Hamster { private int hamsterNumber. } public String toString() { return "This is Hamster #" + hamsterNumber. System. import java. Se debe asumir que se ha tenido un Iterator en una secuencia de un tipo particular en la cual se estaba interesado.printAll(v. así es que se incurrirá contra el tema de la conversión de tipo nuevamente. } } class Printer { static void printAll(Iterator e) { while(e.add(new Hamster(i)). A pesar de que es innecesario. El método println() es sobrecargado para todos los tipos primitivos de la misma forma que Object. Hamster(int i) { hamsterNumber = i.println(e. considerando la creación de un método de impresión de propósito general: //: c09:HamsterMaze. se querrá hacer algo mas que llamar a los métodos de Object. dado que implícitamente utiliza el método Object.out.next()). } } ///:~ Miremos de cerca printAll(). i++) v. y será vista a través de este libro.next()). En general. El ejemplo es aún mas genérico.hasNext()) System. for(int i = 0. y que se puede saber cuando se esta en el final. Printer.

out. import java. lo que produce una llamada recursiva. estos contienen un método toString(). for(int i = 0.println(v). que hace exactamente eso. System.*. Cuando se dice: “InfiniteRecursion address: “ + this El compilador ve un String seguido por un ‘+’ y algo que no es una cadena. Dentro de ArrayList por ejemplo. Se hace la conversión llamando a toString().com .toString() (Esto solo trabaja si se esta directamente heredando de Object. se tendrá una interminable secuencia de excepciones. Así es que en lugar de decir this. i < 10. Lo que está sucediendo es la conversión de tipo automático para Strings. se debe decir super. } } ///:~ Si simplemente se crea un objeto InfiniteRecursion y luego se imprime.util.java // Accidental recursion. Parece tener sentido simplemente referirse a this (en particular. Este ha sido sobrescrito así es que pueden producir un String que los represente.BruceEckel. el toString() camina sobre los elementos de ArrayList y llama a toString() para cada uno. la solución es llamar el método toString() de Object. o si ninguna de sus clases padres son sobrecargadas a el método toString()).add(new InfiniteRecursion()). Supongamos que se quiere imprimir la dirección de su clase. 374 Pensando en Java www. así es que trata de convertir this a String. public class InfiniteRecursion { public String toString() { return " InfiniteRecursion address: " + this + "\n".Recursión no intencionada Dado que (como en cada una de las otras clases). i++) v. esto es verdadero si se colocan los objetos InfiniteRecursion en un ArrayList y se imprime el ArrayList como se muestra aquí. } public static void main(String[] args) { ArrayList v = new ArrayList(). los contenedores estándares de Java son heredados de Object. Si realmente se quiere imprimir la dirección del objeto en este caso. los programadores C++ son propensos a esta estrategia): //: c09:InfiniteRecursion. incluyendo los objetos que almacenan.

Los cuadros punteadas representan interfases. y los cuadros sólidos con clases reculares (concretas).Clasificación de contenedores Collections y Maps pueden ser implementados en diferentes formas. los contenedores no son tan desalentadores. pero se verá que solo hay tres componentes contenedores: Map. y solo dos o tres implementaciones de cada uno (con. Cuando se vea esto. parcialmente implementa esa interfase). Las flechas con línea punteadas indican que una clase particular implementa una interfase (o en el caso de una clase abstracta. de acuerdo a las necesidades del programa. los cuadros hechos con rayas representan clases abstractas. List y Set. típicamente. Ayuda mucho ver un diagrama de los contenedores de Java 2: El diagrama puede ser un poco abrumador al principio. una versión preferida). Las flechas sólidas muestran que una clase puede 375 .

Si se esta creando un Set propio. se realizará una conversión ascendente a la interfase correspondiente. y el único lugar donde se especificara el tipo preciso que se está utilizando es en el punto de creación. donde una Lista puede producir un ListIterator (de la misma forma que un Iterator común y corriente. Idealmente. no se necesitará considerar los elementos heredados cuando se escriba el nuevo código.BruceEckel. se puede también decidir hacer x una LinkedList (en lugar de una List genérica) y cargar la información de tipo exacta con x. Las interfases que están interesadas en almacenar objetos son Collection. la librería de contenedores tiene suficiente funcionalidad para satisfaces sus necesidades virtualmente todo el tiempo. se escribirá mas de su código para hablar de estas interfases. El resto de su código puede permanecer sin tocar (alguna de estas generalidades pueden ser también alcanzada con iteratores). Por consiguiente. Por ejemplo. Claro.producir objetos de la clase a la cual apunta la flecha. La belleza (y la ambición) de utilizar la interfase es que si se decide cambiar su implementación. de la siguiente forma: List x = new ArrayList(). cuando se ve el diagrama. en lugar de eso se hereda de AbstractSet y se hace el trabajo mínimo necesario para hacer la nueva clase. En la jerarquía de clases. Además. Set y Map. Así es que para sus propósitos. todo lo que necesita es cambiarla en el punto de creación. Por lo tanto. Así es que se puede crear una List de esta forma: List x = new LinkedList(). lo que realmente debe preocupar es solo aquellas interfases en la parte superior del diagrama y las clases bien establecidas (aquellas que tienen un cuadro sólido rodeándolas). no se comenzaría con la interfase Set y e implementar todos los método. se puede ignorar cualquier clase que comience con “Abstract”. por ejemplo. dado que List es heredada de Collection). y luego se usará la interfase a través del resto del código. Son simples herramientas que implementan parcialmente una interfase particular. Sin embargo.com . el diagrama puede ser enormemente simplificado para verse de esta forma: 376 Pensando en Java www. se puede ver unas clases cuyos nombres comienzan con “Abstract”. cualquier Collection puede producir un Iterator. Típicamente se creará un objeto de una clase bien establecida. y estos pueden ser un poco confusos al principio. List.

for(int i = 0. pero ArrayList es el típico caballo de tiro de Collection.next()).*. import java. El método add().add(Integer.println(it. i < 10. He aquí un ejemplo simple.toString(i)). Iterator it = c.out. dado que este ejemplo utiliza solo los métodos de Collection. que llena una Collection (representada aquí con un ArrayList) con objetos String. y que luego imprime cada elemento de la Collection: //: c09:SimpleCollection. la documentación cuidadosamente declara que add() “se asegura que ese contenedor contiene el elemento específico”. que agrega un elemento solo si ya no 377 . como su nombre lo sugiere.java // A simple example using Java 2 Collections.Ahora solo incluye las interfases y clases que se encontraran en los ejemplos básicos. i++) c. public class SimpleCollection { public static void main(String[] args) { // Upcast because we just want to // work with Collection features Collection c = new ArrayList(). y también los elementos en los cuales nos enfocaremos en este capítulo. while(it.iterator(). Esto es para tolerar el significado de Set. cualquier objeto de la clase heredado de Collection trabajará. Sin embargo. coloca un nuevo elemento en la Collection.util. } } ///:~ La primer línea en el main() crea un objeto ArrayList y luego realiza una conversión ascendente a una Collection.hasNext()) System.

imprimiendo cada elemento. Aquí. true si el contenedor almacena el argumento. todo lo que se puede hacer con una Set o una List (List también tiene funcionalidades adicionales). Funcionalidad de Collection La siguiente tabla muestra todo lo que se puede hacer con una Collection no se incluyen aquellos método que automáticamente vienen con Object). porque en las Lists no importa si hay elementos duplicados.com . true si el contenedor almacena el elemento en el argumento. boolean add(Object) Se asegura que el contenedor almacena el argumento. Retorna true si alguno de los elementos fue agregado (“Opcional”). Todas las Collections pueden producir un Iterator con el método iterator(). o cualquier tipo de List. true si el contenedor no tienen elementos Retorna un Iterator que se puede utilizar para moverse a través de los elementos en el contenedor Si el argumento es un contenedor. y serán tratados separados. un Iterator es creado y utilizado para atravesar la Collection. add() siempre significa “ponlo aquí”.esta allí. Maps no son heredadas de Collection. descrito mas adelante en este capítulo). una instancia de boolean addAll(Collection) void clear() boolean contains(Object) boolean containsAll(Collection) boolean isEmpty() Iterator iterator() boolean remove(Object) 378 Pensando en Java www. Con una ArrayList.BruceEckel. Retorna falso si no se agrega el argumento (Esto es un método “opcional”. y de esta forma. Agrega todos los elementos en el argumento. Quita todos los elementos en el contenedor (“Opcional).

public class Collection1 { public static void main(String[] args) { 379 . Retorna true si alguna eliminación se sucede (“Opcional”).*. si se quiere examinar todos los elementos de una Collection se debe utilizar un iterator. Retorna el número de elementos en el contenedor.bruceeckel. Esto es porque Collection también incluye Set.java // Cosas que se pueden hacer con todas las Collections.contenedor. //: c09:Collection1.*. Retorna un arreglo conteniendo todos los elementos en el contenedor. una instancia de ese elemento es eliminado. cuyo tipo es aquel del arreglo antes que un Object plano (se debe realizar una conversión del arreglo a el tipo correcto). esta es la única forma de ir a buscar las cosas de vuelta. boolean removeAll(Collection) Elimina todos los elementos que están contenidos en el argumento. pero una ArrayList es utilizada como un tipo de “menor común denominador”.util. boolean retainAll(Collection) int size() Object[] toArray() Object toArray(Object[] a) Note que no hay función get() para acceder de forma aleatoria a un elemento. estro trabaja con todo lo que se herede de Collection. Retorna true si la eliminación se sucede (“Opcional”). que mantiene su propio orden interno (y esto hace la búsqueda del acceso aleatorio absurda). Nuevamente. El siguiente ejemplo muestra todos estos métodos. Retorna un arreglo conteniendo todos los elementos en el contenedor. Retiene solo los elementos que están contenidos en el argumentos (una “intersección” teóricamente). import java. import com. De esta manera. Regresa true su algún cambio se sucede (“Opcional”).util.

c = new ArrayList(). // Encuentre los elementos máximo y mínimo. // Se deshace de todos los elementos // en c2 que también aparecen en c3: c2.println("Collections. c.containsAll(c2) = "+ c. System.BruceEckel.out.println(c).pairs[1][0]).countries. System. c. System.println(c). // Cree un arreglo de la lista: Object[] array = c.remove(CountryCapitals. Collection c3 = ((List)c).com .remove(CountryCapitals.out. System.min(c)). c.fill(c.out.retainAll(c3). 10). System.out.add("eleven").println(c).fill(c2.removeAll(c2). Collections2. 5). System.containsAll(c2)). // ¿Hay una Collection en esta Collection? System. System.contains(" + val + ") = " + c.countries. System.println(c).fill(c.max(c)). Collections2.isEmpty()).println(c).Collection c = new ArrayList(). // Mantiene todos los elementos que están // en c2 y c3 (una intersección de grupos): c2.out. Collections2.removeAll(c3). // ¿Hay algún elemento en esta Collection? String val = CountryCapitals.println("Collections. 10).out.out. esto significa // cosas diferentes dependiendo de la forma en que // la interfase Comparable interfase es implementada: System.pairs[0][0]). 10). Collections2.out. c.println("c. System. System.out.println(c).out. 380 Pensando en Java www.subList(3.println(c).countries.addAll(c2).toArray().max(c) = " + Collections. c.out. Collections2.add("ten"). // Elimine todos los componentes que están en la // colección de argumentos: c.out.toArray(new String[1]).min(c) = " + Collections.contains(val)). c. // Cree un arreglo de cadenas de la lista: String[] str = (String[])c.addAll(c2).pairs[3][0].println( "c.isEmpty() = " + c.println( "c. Collections2. // Agregue una Collection a otra Collection Collection c2 = new ArrayList().

println("after c.println(c). Se notará que las clases heredadas Vector. Una List producirá un ListIterator. como se ha visto hasta ahora con ArrayList. pero no para insertar y remover elementos.out. esta promete mantener los elementos en una secuencia particular. La siguiente sección describe las distintas implementaciones de List. así es que es claro que ningún otro mas que la interfase Collection es utilizada. Funcionalidad de List La List básica es bastante simple de utilizar.println(c). List (interfase) El orden es la característica mas importante de una List. de la misma forma que insertar y eliminar elementos en el medio de la List. y Map e indica en cada caso (con un asterisco) cual podría ser la elección por defecto. Una List implementada con un arreglo. pero es lento cuando se inserta o se eliminan elementos en el medio de la lista. get() para obtenerlos uno por vez. main() utiliza simples ejercicios para mostrar todos los métodos en Collection. System. La List agrega una cantidad de métodos a la Collection que permite insertar y eliminar elementos del medio de una List (Esto es recomendable solo para una LinkedList). hay también un grupo de otros método que pueden ser útiles. Permite un rápido acceso aleatorio a los elementos. y se puede utilizar para atravesar la lista en ambas direcciones.clear(). que se distinguen en el acceso aleatorio a los elementos. Set.clear():"). lo que es costoso comparado con una ArrayList* 381 .System. // Remove all elements System. y Hashtable no están incluidas porque en todos los casos son clases preferidas dentro de los contenedores de Java 2. } } ///:~ Las ArrayLists son creadas conteniendo diferentes grupos de datos y se realizan conversiones ascendentes a objetos Collection. Además. c. y la mucho mas poderosa LinkedList (que no esta diseñada para accesos rápidos. ListIterator debe ser utilizado solo para atravesar para adelante y para atrás en una ArrayList. Stack. pero tiene un grupo mas general de métodos).out. un iterator() para obtener un Iterator de la secuencia. hay actualmente dos tipos de List: la básica y la ArrayList. A pesar de la mayor parte del tiempo solo se utilizará add() para insertar objetos.out.

// Obtiene el objeto en la posición 1 i = a. cola o deque. static ListIterator lit. import java. // ¿Algún elemento dentro? it = a. //: c09:List1. y operaciones disponibles solo para LinkedLists.countries. Los métodos en el siguiente ejemplo cubren diferentes grupos de actividades: cosas que cada lista puede hacer (basicTest()). fill(new ArrayList())). (Se debe utilizar ArrayList en su lugar).add(1. getFirst(). "x").*. // ¿Esta ahí? // ¿Esta la colección entera ahí? b = a.isEmpty(). static int i. moviéndose mediante un Iterator (iterMotion()) contra cambiar las cosas con un Iterator (iterManipulation()).containsAll(fill(new ArrayList())).util.util. static Iterator it. // Indica el índice del objeto b = a.contains("1").listIterator(3).addAll(fill(new ArrayList())). // Comienza en la posición 3 382 Pensando en Java www.listIterator(). // Agregar una colección comenzando por la posición 3: a. // Iterator común lit = a. Collections2. lo que es costoso comparado con una LinkedList.iterator(). lo que no // tiene mucho costo para ArrayList. 10). Collections2.java // Cosas que se pueden hacer con listas. b = a. public class List1 { public static List fill(List a) { Collections2. // Agregar al final // Agregar una colección: a.elementos.addAll(3.com . getLast(). Relativamente lento para acceso aleatorio. public static void basicTest(List a) { a. } static boolean b. // Las listas permiten el acceso aleatorio. // Add at location 1 a.countries.reset(). import com. con inserciones de bajo costo y eliminaciones del medio de la List.BruceEckel. static Object o. LinkedList Proporciona un óptimo acceso secuencial. y removeLast() (que no esta definido en ninguna interfase o clase base) para permitir que se utilice como pila. removeFirst(). y es caro // para las LinkedList: o = a. return a.indexOf("1"). viendo los efectos de la manipulación de List (testVisual()).fill(a.get(1). addLast().bruceeckel.add("x").*. // ListIterator lit = a. También tiene addFirst().

listIterator(a.out. } public static void testVisual(List a) { System.hasPrevious().out.add("47").next().println(a). i = it.remove("3").listIterator(a.println(x.println(a). i = it. System. // Elimina este objeto a.out. // Elimina todos los elementos } public static void iterMotion(List a) { ListIterator it = a. // Se debe mover a un elemento luego de remove(): it.next(). x. List b = new ArrayList().out. "y"). System.hasNext().set(1.println(b).hasPrevious()) 383 . System.set("47"). o = it. // Atravesar la lista para atrás: x = a.remove().set("47").add("one").remove(1).next()). System. System. x. // Elimina la posición 1 a. // Especifica la posición 1 para "y" // Mantiene todo lo que esta en el argumento // (la intersección de los dos grupos): a. i = a. fill(b).print("b = "). System.lastIndexOf("1"). // Insertar. // Remueve todo lo que se encuentra en el argumente: a.println(a). // Última coincidencia a.i = a.nextIndex().out.println(x. } public static void iterManipulation(List a) { ListIterator it = a. // Cambie el elemento que simplemente fue producido: it.previous().size().next(). o = it. y remplazar elementos // utilizando un ListIterator: ListIterator x = a. System.remove().previousIndex(). b = it. b = it.addAll(fill(new ArrayList())).size()). a. it. eliminar.listIterator().removeAll(fill(new ArrayList())).out.out. // ¿Que tan grande es? a.listIterator().addAll(b).next()).out.clear(). // De debe mover a un elemento luego de add(): it. x.println(a). a. // Elimina el elemento que simplemente fue generado: it. while(x.size()/2).retainAll(fill(new ArrayList())).

// Como sacar de una pila: System. // Tratarla como una pila.com antes de utilizarlas. testLinkedList(). testVisual(fill(new LinkedList())).println(ll). iterManipulation(fill(new ArrayList())).removeLast()). System.getFirst()).out.out.out.println("testVisual finished").println(ll. System. } public static void main(String[] args) { // Crea y llena una nueva lista cada vez: basicTest(fill(new LinkedList())). primero en salir” (LIFO). System.println(ll. iterMotion(fill(new LinkedList())). // Tratarla como una cola.println(ll).System. cualquier cosa que “empuje” en la pila último es lo primero que se puede “extraer”. Como todos los otros contenedores en Java. } } ///:~ En basicTest() y iterMotion() las llamadas son simplemente hechas para mostrar la sintaxis correcta.removeFirst()). iterMotion(fill(new ArrayList())).previous() + " "). el valor de retorno no es capturado dado que no es típicamente utilizado. // Con las operaciones anteriores es una dequeue! System.println(ll.BruceEckel. y mientras que el valor de retorno es capturado.out. En algunos casos.out.print(x. System. no es utilizado.out.println(ll).out. } // Aquí hay algunas cosas que solo las // LinkedLists pueden hacer: public static void testLinkedList() { LinkedList ll = new LinkedList().com .println(ll.addFirst("one").sun. iterManipulation(fill(new LinkedList())). ll. System. empujando: ll. Creando una pila de una LinkedList Una pila es algo referido como un contenedor “ultimo en entrar. Esto es. basicTest(fill(new ArrayList())).out. fill(ll).addFirst("two").out. así es que se debe realizar una 384 Pensando en Java www.println(). jalando elementos // del final de la cola: System.out.removeFirst()). // Como "fisgonear" en lo mas alto de la pila: System. Se debe encontrar todas las utilizaciones de cada uno de estos métodos en la documentación en línea en java. lo que se empuja y se extrae son Objects.

System.getFirst().bruceeckel. //: c09:Queue. y se empujan hacia el otro. así es que simplemente se puede utilizar una LinkedList en lugar de crear una clases pila.java // Making a stack from a LinkedList. 385 . Creando una cola con una LinkedList Una cola es un contendedor “primero en entrar. //: c09:StackL. primero en salir (FIFO). public class StackL { private LinkedList list = new LinkedList().*.println(stack. } public static void main(String[] args) { StackL stack = new StackL(). public void push(Object v) { list. ” Esto es. i++) stack. La LinkedList tiene métodos para dar soporte a el comportamiento de una cola. } } ///:~ Si se quiere el comportamiento de pila solamente.out.out.*.pop()).push(Collections2.out.next()).pop()).util. import java. Así es que el orden en que se colocan es el mismo orden en el que salen.out.println(stack. heredar es inapropiado aquí porque producirá una clase con todos los métodos de una LinkedList (se verá mas tarde que este gran error fue cometido por los diseñadores de la librería de Java 1. La LinkedList tiene métodos que directamente implementan funcionalidad de pila. System.println(stack.println(stack. se colocan cosas en un final. import java.pop()).removeFirst(). } public Object top() { return list. System.top()). a no ser que solo se utilice el comportamiento del Object.println(stack.countries.util.*. public class Queue { private LinkedList list = new LinkedList().addFirst(v).top()). } public Object pop() { return list. import com.util.conversión lo que se extrae. System.java // Making a queue from a LinkedList. System. así es que se puede utilizar en una clase Cola .0 con Stack). for(int i = 0. i < 10.out.

Los Objects agregados a Set deben definir un equals() para establecer la unicidad del objeto. En lugar de eso. Un Set rechaza almacenar mas de una instancia de cada valor de objeto (lo que constituye el “valor” de un objeto es mas complejo. Funcionalidad de Set Set tiene la misma interfase que Collection.isEmpty(). Set es exactamente una Collection. Esto es como una cola. i++) queue.com . pero se puede agregar y borrar elementos de ambos finales. } } ///:~ De puede también crear una deque (cola con doble final) de una LinkedList.println(queue. dado que la interfase es la misma que Collection.toString(i)). Para Sets donde el tiempo de búsqueda es importante los Objects deben también definir hashCode().put(Integer.BruceEckel.get()).public void put(Object v) { list. for(int i = 0. De esta forma. La interfase de Set no garantiza que mantendrá los elementos en un orden particular. En lugar de eso. esto demuestra el comportamiento que hace un Set único: 386 Pensando en Java www. } public Object get() { return list. HashSet* TreeSet El siguiente ejemplo no muestra todo lo que se puede hacer con un Set. } public boolean isEmpty() { return list.out. como se verá). while(!queue. Set (interfase ) Cada elemento que se agrega a Set debe ser único.addFirst(v). y se practicó en el ejemplo anterior.removeLast(). i < 10. solo tiene diferente comportamiento (Esto es el uso ideal de herencia y polimorfismo: expresar diferente comportamiento). } public static void main(String[] args) { Queue queue = new Queue(). así es que no hay funcionalidad extra como la hay con las dos diferentes Lists.isEmpty()) System. Set tiene exactamente la misma interfase que Collection. de otra forma Set no agregará el elemento duplicado. se puede extraer una secuencia ordenada de un Set. Un Set ordenado respaldado por un árbol.

System.fill(a. // Duplicados no! // Agregue otro set para este: a.util. Collections2.add("one").contains("one")).println("HashSet"). He aquí un ejemplo: //: c09:Set2.out.out. 10).util. se debe tener cuidado porque un Set necesita una forma de mantener un orden de almacenamiento.i).out.add("one"). a.//: c09:Set1.*.reset().*. } public boolean equals(Object o) { return (o instanceof MyType) && (i == ((MyType)o).out. public MyType(int n) { i = n.StringGenerator gen = Collections2.fill(a.*. gen.println(a). import com. 10). } } ///:~ Los valores duplicados son agregados a Set. // Busca algo: System. } public static void main(String[] args) { System.fill(a. testVisual(new HashSet()). dado que cada uno tiene una forma diferente de ordenar elementos para poder ser localizados mas tarde (TreeSet los mantiene ordenados. class MyType implements Comparable { private int i. mientras que HashSet utiliza una función de hash.countries. Cuando se ejecute este programa se verá que el orden mantenido por el HashSet es diferente de TreeSet. pero cuando son impresos se vera que Set ha aceptados solo una instancia de cada valor.util.println(a). public static void testVisual(Set a) { Collections2. a.java // Cosas que se pueden hacer con Sets. 10). import java. lo que significa que se debe implementar la interfase Comparable y definir el método CompareTo().contains(\"one\"): " + a. System.out.addAll(a). gen. public class Set1 { static Collections2. Collections2. System. Cuando se crean sus propios tipos. la que es diseñada para una rápida búsqueda). a.println("TreeSet").add("one").reset().println("a. testVisual(new TreeSet()).java // Putting your own type in a Set. } 387 .reset().bruceeckel. gen. import java.

i++) a.println(a). solo trabajará propiamente si i e i2 son enteros “sin signo” (Si Java tuviera una palabra clave “unsigned”. System. return a. 10). } public String toString() { return i + " ". Se debe definir un equals() en ambos casos.BruceEckel. Si i es un entero positivo grande y j es un entero negativo grande. 10).public int hashCode() { return i. Sin embargo. } public static void test(Set a) { fill(a. i-j produce un desbordamiento y retorna un valor negativo. como estilo de programación se debería siempre sobrecargar hashCode() cuando se sobrecargue equals(). pero el hashCode() es absolutamente necesario solo si la clase será colocada en un HashSet (lo que es bueno. } } public class Set2 { public static Set fill(Set a. dado que generalmente será su primera elección como una implementación Set).addAll(fill(new TreeSet(). por lo que no funcionará. fill(a. } } ///:~ La forma para definir equals() y hashCode() será descrita mas tarde en este capítulo. a. } public int compareTo(Object o) { int i2 = ((MyType)o). Este proceso será detallado totalmente mas tarde en este capítulo. debe notarse que no se utiliza la “simple y obvia” forma return i-i2. Esto rompe el entero con signo de Java que no es suficientemente grande para representar la diferencia entre dos enteros con signo. A pesar de que esto es un error común de programación. SortedSet Si se tiene un SortedSet (de los cuales TreeSet es el único disponible). return (i2 < i ? -1 : (i2 == i ? 0 : 1)).com . 10)).out.add(new MyType(i)). int size) { for(int i = 0. los elementos están garantizados de estar en orden lo que permite funcionalidad adicional para proporcionar estos métodos en la interfase de SortedSet: 388 Pensando en Java www. // Try to add duplicates fill(a. En el compareTo(). i < size. test(new TreeSet()). cosa que no es). 10). } public static void main(String[] args) { test(new HashSet()).i.

exclusivo. pero en lugar de buscar . ¿Pero que si se quiere seleccionar de una secuencia de objetos utilizando algún otro criterio? Una pila es un ejemplo: el criterio de selección es “la última cosa empujada en la pila”.Comparator comparator(): Produce el comparator utilizado por este Set. La librería estándar de Java contiene dos tipos diferentes tipos de Maps: HashMap y TreeMap. ¡Se buscan utilizando otro objeto! Esto es a menudo un proceso clave en un programa. pero difieren en una forma clara: eficiencia. o null para ordenamiento natural. Se puede también probar un Map para ver si contiene la clave o si un valor con containsKey() y containsValue(). Aquí es donde HashMap acelera las cosas. toElement): Produce una vista de este Set con elementos de fromElement. Un HashMap toma el hashCode() de un objeto u lo usa para rápidamente buscar la clave. Es código hash es una forma de tomar cierta información en el objeto en cuestión y convertirlo en “relativamente el único” entero para ese objeto. hasta toElement. En lugar de una búsqueda lenta por una clave. Funcionalidad de Map Una ArrayList permite seleccionar de una secuencia de objetos utilizando un número. un diccionario. Todos los objetos Java pueden producir un código hash. Object first(): Produce el menor elemento. Un poderoso giro de esta idea de “seleccionar de una secuencia” es llamada alternativamente un map. esta utiliza un valor especial llamado código hash. El método put(Object key. get(Object key) produce el valor dando la clave correspondiente. Object value) agrega un valor (lo que se quiera). o un arreglo asociativo Conceptualmente. parece una ArrayList. Si se observa lo que hace get(). objetos utilizando un número. SortedSet tailSet(fromElement) : Produce una vista de este Set con los elementos mayores o iguales a toElement. Object last(): Produce el mayor elemento. y lo asocia con una clave (lo que se va a buscar). Ambos tienen la misma interfase (dado que ambos implementan Map). Este resulta en una dramática mejora en el rendimiento7 . y hashCode() es un método en la clase raíz Object. se puede acelerar escribiendo su propio Map y hacerlo a la medida para sus tipos particulares 389 . El concepto muestra en Java como la interfase Map. así es que en cierto sentido asocia números a objetos. inclusive. 7 Si este aumento de velocidad no es apropiado para sus necesidades de rendimiento. SortedSet subSet(fromElement. este parece mas lento de buscar a través de (por ejemplo) una ArrayList por una clave.

out. así veremos esto un poco mas adelante. ").fill() y la prueba de grupos de datos que fuera previamente definida. public class Map1 { static Collections2. Volume 3: Shorting and Searching. El rendimiento puede ser ajustado mediante constructores que permita configurar la capacidady el factor de carga de la tabla hash.util. El siguiente ejemplo utiliza el método Collection2.bruceeckel.BruceEckel. Para alcanzar niveles mas altos aún de rendimiento. static Collections2.out. para reemplazar las listas con arreglos que tienen dos beneficios adicionales: pueden ser optimizados con características que permitan un optimo almacenamiento en disco y pueden ahorrar la mayoría del tiempo de creación y de recolección de basura de cada registro individual. //: c09:Map1.Map(interfase) Mantiene una asociación clave-valor (pares) . import com.println(m. así es que se puede buscar un valor utilizando una clave. import java. HashMap* TreeMap A veces se necesita conocer los detalles de como el hashing trabaja.print("Size = " + m.geography.RandStringPairGenerator rsp = Collections2. Proporciona rendimiento constante en tiempo para insertar y encontrar pares. Implementación basada en un árbol rojo-negro. El punto de TreeMap es que se obtienen los resultados ordenados.size() +".rsp.StringPairGenerator geo = Collections2. Cuando se ven las claves o los pares. TreeMap es el único Map con el método subMap(). } // Producing a Collection of the values: para evitar retrasos de conversiones de y hacia Objects.out. discutidos mas adelante). System.*. Implementación basada en una tabla hash (Utilice esta en lugar de una Hashtable).com . los entusiastas de la velocidad pueden utilizar la segunda edición de Donald Knuth’s The art of computer programming.keySet()).util. 390 Pensando en Java www. estos serán ordenados (determinado por Comparable o por comparator. que permite retornar una parte del árbol.print("Keys: ").java // Things you can do with Maps.*. // Producing a Set of the keys: public static void printKeys(Map m) { System. System.

System.println("First key in map: "+key).get(key)).fill(m2. Map m2 = new TreeMap(). m.out. El método keySet() produce un Set respaldado por las claves en el Map. System.public static void printValues(Map m) { System. 25).out.out.removeAll(m. System. printKeys(m).println("m. geo.println("m. System.println("m. test(new HashMap()).get(\"" + key + "\"): " + m.out. 391 . 25). que produce una Collection conteniendo todos los valores en el Map (Debe notarse que las claves deben ser únicas.out. String value = CountryCapitals.pairs[4][1]. System. Collections2. // Operations on the Set change the Map: m.out.isEmpty()). System. geo. 25). mientras que los valores pueden contener duplicados).iterator().pairs[4][0]. 25). Collections2. Tratamiento similar es dado a values(). cualquier cambios en una Collection será reflejado en el Map asociado.out.toString().println("Testing TreeMap").clear().containsKey(\"" + key + "\"): " + m. key = m. printValues(m).println(m. geo.out.putAll(m2).fill(m.remove(key).println("m.out.isEmpty(): " + m. printKeys(m). estos demuestran también como producir vistas Collection de un Map. } public static void main(String[] args) { System.keySet(). m.keySet(). rsp. } } ///:~ Los métodos printKeys() y printValues() no solo son utilitarios útiles.reset().isEmpty()). Dado que estas Collections son respaldadas por Map. System.out.containsKey(key)).containsValue(\"" + value + "\"): " + m.print("Values: ").out. printKeys(m).println("m.reset().next(). // Map has 'Set' behavior for keys: Collections2.isEmpty(): " + m. System. test(new TreeMap()).keySet()).containsValue(value)).values()). String key = CountryCapitals.println("Testing HashMap").fill(m. } public static void test(Map m) { Collections2. m.fill(m.println(m). System.

El resto del programa proporciona un ejemplo simple de cada operación Map.random() * 20)).java // Simple demostración de HashMap.toString(i).util.get(r)).*. Si ya se encuentra. } } ///:~ En el main(). } System. public String toString() { return Integer. i++) { // Produce a number between 0 and 20: Integer r = new Integer((int)(Math. el que en este caso es un objeto Counter. i < 10000.println(hm). ¿El número ya ha sido encontrado?). dado que asocia objetos con objetos (en este caso. Como ejemplo de uso de un HashMap. 392 Pensando en Java www. class Counter { int i = 1. este produciría una perfecta distribución de números aleatorios. Si la clave no ha sido encontrada todavía.i++. esta indica la primer ocurrencia de este número aleatorio particular.com . pero para verificar esto se deberá generar un montón de números aleatorios y contarlos para colocarlos en varios rangos. solo una referencia a un objeto). Dado que Counter automáticamente inicia la variable i a uno cuando es creada. el método put() colocara un nuevo par clave-valor dentro del HashMap. el método get() produce el valor asociado a la clave. El método containsKey() verifica para ver si la clave ya se encuentra en el contenedor (Esto es.random().random() junto con el número de veces que el número aparece): //: c09:Statistics. cada vez que un número es generado es envuelto dentro de un objeto Integ er de tal manera de que se pueda utilizar la referencia con el HashMap (no se puede utilizar una primitiva con un contenedor. if(hm.containsKey(r)) ((Counter)hm. new Counter()). El valor i dentro del contador es incrementado para indicar que un número aleatorio mas de este ha sido encontrado. considere un programa que verifique la aleatoriedad del método Math. import java.out. } } class Statistics { public static void main(String[] args) { HashMap hm = new HashMap().BruceEckel. y probar cada tipo de Map. Idealmente. else hm. Un HashMap es perfecto para esto. el objeto valor contiene el número producido por Math. for(int i = 0.put(r.

dado que no se puede colocar ninguno de los tipos primitivos en los contenedores. 3=509. hay una garantía de que las claves están en orden lo que permite funcionalidad adicional proporcionada con estos métodos en la interfase SortedMap: Comparator comparator(): Produce la comparación utilizada para este Map. 4=489. 0=505} Se puede preguntar por la necesidad de la clase Counter. así es que estamos forzados a crear una nueva clase que satisfaga la necesidad. que parece no tener la funcionalidad de la clase envoltura Integer. 14=495. Esto es. 9=514. 13=512. El método de HashMap toString() se mueve a través de todos los pares clave-valor llamando toString() para cada uno. 1=475. 18=533. toKey): Produce una vista de este Map con claves de fromKey . SortedMap Si tiene un SortedMap (de los cuales TreeMap es el único disponible).toString() es predefinido. Object firstKey(): Produce la clave mas baja. este es impreso simplemente. 7=497. 10=487. 5=480. ¿Por que no utilizar int o Integer? Bueno. Object lastKey(): Produce la clave mas alta. Luego de ver contenedores la clases envolturas pueden tener un poco mas de sentido. Esto hace que la envoltura Integer sea inútil inmediatamente para solucionar nuestro problema. la única cosa que se puede hacer con las envolturas de Java es inicializarlas a un valor particular y leer ese valor. o null para un ordenamiento natural. 15=521. Sin embargo. 16=513. 12=483. 17=460. no hay forma de cambiar el valor una vez que se ha un objeto envoltura ha sido creado. porque tiene 393 . 2=503. 8=523. inclusive. no se puede utilizar un int porque todos los contenedores solo pueden almacenar solo referencias a Object. La salida para una corrida (con algunas finales de línea agregados es: {19=526. y se puede ver el toString() para Counter. hasta toKey . 11=488. El Integer. SortedMap subMap(fromKey. SortedMap headMap(toKey): Produce una vista de este Map con claves menores que toKey . una clase de la librería estándar (Integer) fue utilizada como calve para un HashMap. Hashing y códigos hash En el ejemplo previo. Esta trabaja bien como clave.Para mostrar el HashMap. 6=487. SortedMap tailMap(fromKey): Produce una vista de este Map con claves mayores o iguales que fromKey .

containsKey(gh)) System. Groundhog(int n) { ghNumber = n. “Dame la Predic tion asociado con el número de Groundhog 3”. for(int i = 0. Por ejemplo. i < 10.todas las cosas necesarias para hacer que trabaje correctamente como clave.random().5. public String toString() { if(shadow) return "Six more weeks of Winter!".println( "Looking up prediction for Groundhog #3:").out. En main(). System. Pero una dificultad común sucede con HashMaps cuando se crean clases propias para ser utilizadas como claves. Groundhog gh = new Groundhog(3).get(gh)). Entonces un Groundhog con un número idéntico de 3 es utilizado como clave para buscar la predicción para la Groundhog numero 3 (que como se puede ver esta en el Map).out. pero no trabaja. } } class Prediction { boolean shadow = Math. import java. else System. //: c09:SpringDetector. } } public class SpringDetector { public static void main(String[] args) { HashMap hm = new HashMap(). La clase Prediction contiene un boolean que es inicializado utilizando Math. El HashMap es impreso así es que se puede ver que ha sido llenado. un HashMap es llenado con Groundhogs y sus Predictions asociadas. y se utiliza Groundhog como la clave y Prediction como valor. Se ve suficientemente simple. considere un sistema para predecir el tiempo que hace corresponder objetos Groundhog con objetos Prediction. } } ///:~ Cada Groundhog esta dando un valor idéntico.out.*.util.println("Key not found: " + gh). Esto parece bastante directo -se crean las dos clases. i++) hm. if(hm. de esta manera todas las clases son a 394 Pensando en Java www. else return "Early Spring!". new Prediction()). El problema es que Groundhog es heredado de la clase raíz en común Object (que es lo que sucede si no especifica una clase base. así es que se puede encontrar un Predictionen el HashMap diciendo. class Groundhog { int ghNumber.put(new Groundhog(i).out.java // Looks plausible. y un toString() que interpreta el resultado. but doesn't work.random() > 0.println("hm = " + hm + "\n").println((Prediction)hm. System.com .BruceEckel.

} public int hashCode() { return ghNumber.println( "Looking up prediction for groundhog #3:"). } } public class SpringDetector2 { public static void main(String[] args) { HashMap hm = new HashMap(). el Object.out. como se muestra en la siguiente solución a el problema anterior: //: c09:SpringDetector2. así es que SpringDetector.out.out. System. Se puede pensar que todo lo que necesita hacer es volver a escribir una función apropiada para hashCode(). if(hm. } } ///:~ Debe notarse que se utiliza la clase Prediction del ejemplo anterior. i < 10.util.new Prediction()). import java.println((Prediction)hm. la primer instancia de Groundhog(3) no produce un código hash igual a el código hash para la segunda instancia de Groundhog(3) que ha sido utilizada para realizar la búsqueda. System. así es que Groundhog(3) no es igual a otro Groundhog(3) . } public boolean equals(Object o) { return (o instanceof Groundhog2) && (ghNumber == ((Groundhog2)o).java // Una clase que es utilizada como clave en un HashMap // debe sobrescribir hashCode() y equals().get(gh)). Este método es utilizado por HashMap cuando se trata de determinar si su clave es igual a cualquiera de las claves de la tabla.ghNumber).java.put(new Groundhog2(i). De esta forma. Groundhog2 gh = new Groundhog2(3).java debe ser compilado primero o se obtendrá un error en tiempo de compilación cuando se trate de compilar SpringDetector2.*. Pero seguiría sin funcionar hasta que se haya hecho una cosa mas: sobrescribir equals() que es también parte de Object. hashCode() y equals(). for(int i = 0.equals() simplemente compara direcciones de objetos. y por defecto este utiliza la dirección de ese objeto. Groundhog2(int n) { ghNumber = n.println("hm = " + hm + "\n").containsKey(gh)) System. Nuevamente. para utilizar su propia clase como clave en un HashMap. 395 . De esta forma. El método hashCode() de Object es utilizado para generar el código hash para cada objeto. se debe sobrescribir ambas funciones. i++) hm.fin de cuentas heredadas de Object). class Groundhog2 { int ghNumber.

Entry. MPair será definido como el nuevo tipo de Map.Groundhog2.Entry . value. que será explicada totalmente en el capítulo 12). Es posible implementar su propio Map. para tener una buena solución para el problema se necesita entender que sucede dentro de la estructura de datos hash. Primero. Para hacerlo de esta forma.java // A Map implemented with ArrayLists. Aun cuando pareciera ser que el método equals() es solo una prueba para ver cuando el argumento es una instancia Groundhog2 (utilizando la palabra clave instanceof.util. } 8 N. Comparable { Object key. Sin embargo. Groundhog significa marmota. cuando se ejecute el programa. Pero se puede lograr esto con un treeSet o un treeMap también. El autor se esta refiriendo tal vez a la propiedad de predecir el tiempo que se le atribuye a la marmota. Cuando se cree una clase propia para utilizar en HashSet.*. considere la motivación detrás del hashing: se quiere encontrar un objeto utilizando otro objeto. En este ejemplo.com . public class MPair implements Map. el método Map. import java. Esta vez. la estructura de datos hash (HashSet o HashMap) no será capaz de tratar con sus claves propiamente. Asumiendo que es el tipo correcto y no null.T. Este muestra que si no sobrescribe hashCode() y equals() de acuerdo a su clave.BruceEckel. Object v) { key = k.hashCode() retorna el número de marmota 8 como un identificador. se debe prestar atención en los mismo temas que cuando se utiliza una clave en un HashMap. el programador es responsable por asegurarse que no existan dos marmotas con el mismo número ID. instanceof actualmente silenciosamente realiza una segunda verificación de cordura. 396 Pensando en Java www. Para que sea colocado en un TreeSet debe implementarse equals() y ser Comparable: //: c09:MPair. Entendiendo hashCode() El ejemplo mas arriba es solo el inicio de a través de una solución correcta del problema. se podrá ver que produce la salida correcta.Entry . para ver si el objeto en null. El hashCode() no es necesario para retornar un identificador único (algo que se entenderá mejor mas tarde en este capítulo).entrySet() debe ser alimentado para producir un grupo de objetos Map. la comparación esta basada en la actual ghNumbers. MPair(Object k. value = v. pero el método igual debe ser capas de determinar estrictamente cuando dos objetos son equivalentes.

java // Un Map implementado con ArrayLists. Collections2. values. values = new ArrayList(). } public static void main(String[] args) { SlowMap m = new SlowMap(). return result.iterator().util.contains(key)) { keys.compareTo( ((MPair)rv). } public Set entrySet() { Set entries = new HashSet(). } public Object setValue(Object v){ Object result = value. Collections2.*. 397 .hasNext()) entries. value). El siguiente ejemplo implementa un Map utilizando un par de ArrayLists: //: c09:SlowMap. vi. import java.contains(key)) return null.add(key). } public Object get(Object key) { if(!keys.get(keys.indexOf(key)). vi = values. if(!keys. public class SlowMap extends AbstractMap { private ArrayList keys = new ArrayList().add(new MPair(ki. value = v. } public Object getValue() { return value. 25).bruceeckel.iterator().set(keys.equals(((MPair)o).key).util.add(value). return result. Iterator ki = keys.next().indexOf(key).geography. import com. return values. public Object put(Object key. } else values.*.key). así es que los valores duplicados son perfectamente aceptables.public Object getKey() { return key. return entries. } public boolean equals(Object o) { return key. } public int compareTo(Object rv) { return ((Comparable)key). Object value) { Object result = get(key). while(ki.next())).fill(m. } } ///:~ Debe notarse que las comparaciones solo se interesan en las claves.

lo que es la forma mas lenta de buscar algo. las colisiones son manejadas mediante encadenado externo: al arreglo no apunta directamente a un valor. producido por el método hashCode() (en el lenguaje de la ciencia de las computadoras. pero eso es un caso especial. Esto es.com . } } ///:~ El método put() simplemente coloca las claves y los valores en las correspondientes ArrayLists. en lugar de eso apunta a una lista de valores. una vez ubicado.binarySearch() para realizar una búsqueda (un ejercicio en el final de este capítulo trabajará sobre este proceso).println(m). Del objeto clave. El punto mas importante del hashing es la velocidad: el hashing permite que la búsqueda se suceda rápidamente. que no importa que tan grande sea el arreglo porque cada objeto clave aterrizará en alguna parte del arreglo. En todos los otros casos. la estructura mas rápida es aquella en la que se almacena un grupo de elementos en un arreglo. así es que tenemos un problema: queremos ser capaces de almacenar cualquier número de valores en el Map. Dado que el cuello de botella es la velocidad de búsqueda de la clave. no se le puede cambiar el tamaño. puede haber colisiones. Así es que el proceso de buscar un valor comienza computando el código hash y utilizándolo para indexar dentro del arreglo. así es que probablemente no lo use si se tiene una alternativa disponible. Estos valores son explorados 398 Pensando en Java www.out. Pero como el nombre lo sugiere. y no la propia clave).BruceEckel. así es que será utilizada para representar la información de la clave (debe notarse cuidadosamente que he dicho “información de la clave”. una de las soluciones del problema puede ser mantener las claves ordenadas y luego utilizar Collections. un SlowMap es cargado y luego impreso para mostrar que este trabaja. Si se puede garantizar que no habrá colisiones (lo que puede ser posible si se tiene un número fijo de valores) entonces se tiene la función de hashing perfecta. un SlowMap no es muy rápido. pero el número de claves es fijo por el tamaño del arreglo. esta es la función hash definida en Object y presumiblemente sobrescrita por su clase. También se puedo ver en este capítulo el hecho de que un arreglo. Es por esto. Este número es el código hash. mas de una clave puede producir el mismo índice. ¿Como puede ser esto? La respuesta es que el arreglo no almacena las claves. El problema es en la búsqueda de la clave: no hay orden así es que una búsqueda simple es utilizada.System. Hashing va mas allá diciendo que todo lo que quiere hacer es almacenar la clave en algún lugar de tal forma que pueda encontrarla rápidamente. Como se ha visto en este capítulo. Para solucionar el problema del arreglo de tamaño fijo. un número será derivado que indexara dentro del arreglo. Esto muestra que no es duro producir un nuevo tipo de Map. En el main().

getValue(). LinkedList pairs = bucket[index].java // A demonstration hashed Map. lo que hace a el HashMap tan rápido. import com. // Reemplaza el viejo con el nuevo found = true. Conociendo las básicas del hashing. private LinkedList[] bucket= new LinkedList[SZ].hasNext()) { Object iPair = it.bruceeckel. Es posible implementar un simple Map utilizando hash: //: c09:SimpleHashMap.hashCode() % SZ. boolean found = false. pero si la función de hash es buena solo se tendrá unos pocos valores en cada ubicación. LinkedList pairs = bucket[index]. Object value) { Object result = null. MPair match = new MPair(key.hasNext()) { Object iPair = it. if(iPair. public class SimpleHashMap extends AbstractMap { // Elige un número primo para el tamaño de la tabla // hash. } } if(!found) bucket[index].hashCode() % SZ.equals(pair)) { result = ((MPair)iPair). public Object put(Object key. a lo sumo. if(index < 0) index = -index. return result. Esto es mucho mas rápido. Claro.getValue(). it. ListIterator it = pairs. para alcanzar una distribución uniforme: private final static int SZ = 997.*.util. int index = key. while(it.add(pair). se puede rápidamente saltar a una ubicación donde se tiene que comparar unas pocas entradas para encontrar el valor. import java. if(bucket[index] == null) return null. } public Object get(Object key) { int index = key.listIterator(). null). if(iPair.next(). break.en una forma lineal utilizando el método equals(). MPair pair = new MPair(key.set(pair). value). este aspecto de la búsqueda es mucho mas lento. ListIterator it = pairs.equals(match)) return ((MPair)iPair). while(it.listIterator(). } 399 .util. if(bucket[index] == null) bucket[index] = new LinkedList(). Así es que en lugar de buscar a través de la lista entera.*. if(index < 0) index = -index.next().

pero mas simple. Iterator it = bucket[i]. Entonces es forzado a encajar dentro del arreglo de buckets utilizando el operador módulo y el tamaño del arreglo.out. La bandera found mantiene el rastro de donde un viejo par clave-valor fue encontrado y. el Map puede ser probado llenándolo de valores y luego imprimiéndolos.next()). el arreglo que representa la tabla actual es llamada bucket. el viejo valor asociado con esa clave. 400 Pensando en Java www.com . } } ///:~ Dado que las ubicaciones en una tabla hash son a menudo referidas como buckets (cubos). System. así es que una nueva LinkedList es creada para almacenar el objeto.add(it. El valor de retorno de put() es null o. 25).return null. y el resultado es forzado a ser un número positivo.println(m). En get().length. agregándolas a el resultante Set. El valor de retorno es result. for(int i = 0. entrySet() debe encontrar y atravesar todas las listas. Una ves que este método ha sido creado. el viejo valor es colocado como result y el nuevo valor reemplaza el viejo. } return entries. while(it. El índice es calculado dentro del arreglo bucket. Para put() y get().iterator().geography. lo que automáticamente proporciona soporte para las colisiones -cada nuevo ítem es simplemente agregado al final de la lista. el nuevo par es agregado al final de la lista. i++) { if(bucket[i] == null) continue. que es inicializado a null.fill(m.hasNext()) entries. Para promover una distribución pareja. si no. i < bucket. } public static void main(String[] args) { SimpleHashMap m = new SimpleHashMap().BruceEckel. si la clave ya se encuentra en la lista. Collections2. Note que es un arreglo de LinkedList. pero la clave es descubierta en la lista luego result es asignado a esa clave. el número buckets es típicamente un número primo. la primera cosa que sucede es que el hashCode() es llamada para esa clave. se puede ver código similar que el contenido en put(). y si una LinkedList existe es explorada por una coincidencia. Collections2. y si hay. significa que no hay elementos en esa ubicación. el proceso normal es buscar a través de la lista para ver si hay duplicados. Si la ubicación es null. } public Set entrySet() { Set entries = new HashSet(). Sin embargo.

y esa capacidad cambia dependiendo de que tan lleno el contenedor esta. y luego redistribuyendo los objetos existentes en el nuevo grupo de buckets (esto es llamado rehashing).Factores de rendimiento de HashMap Para entender estos temas alguna terminología es necesaria: Capacidad: El número de buckets en la tabla Capacidad inicial: El número de buckets cuando la tabla es creada. Factor de carga: El tamaño sobre la capacidad. Esto depende de la capacidad de un objeto HashMap en particular. y cual es el factor de carga. sin importar cuando hashCode() es llamado. esto produce el mismo valor para un objeto particular cada ves que es llamado.5 es la mitad de la totalidad de la tabla. no se tiene control de la creación o del valor actual que se esta utilizando para indexar dentro del arreglo de buckets. El factor mas importante en la creación de hashCode() es ese. HashMap y HashSet tienen constructores que permiten especificar el factor de carga. etc. Un factor de carga mas alto disminuye el espacio requerido por la tabla pero incrementa el costo de búsqueda. Si se termina con un objeto que produce 401 . lo que es importante porque la búsqueda es lo que se hace la mayor parte del tiempo (incluyendo get() y put()). Una tabla cargada ligeramente tendrá menos colisiones y por lo tanto óptima para inserciones y búsquedas (pero será lenta en el proceso de atravesarla con un iterator). 0. Antes que nada. Un factor de carga de cero es una tabla vacía. Esto pareces ser un buen trueque entre el tiempo y los costos de espacio. Sobrecargando el hashCode() Ahora que se entiende que está involucrado en la función de HashMap. que significa que cuando este factor es alcanzado el contenedor automáticamente aumentará la capacidad (el número de buckets) doblándola groseramente.75 (no realiza un rehashing hasta que la tabla esta 3/4 llena). Si se sabe cuantas entradas se van a almacenar en un HashMap. El valor producido por su hashCode() será procesado para crear el índice de buckets (en SimpleHashMap el calculo es solo un módulo entre el tamaño del arreglo de buckets). El factor de carga utilizado por HashMap es 0. los temas involucrados en la escritura de hashCode() tendrá mas sentido. HashMap y HashSet: tienen constructores que permiten especificar la capacidad inicial Tamaño: El número de entradas actualmente en la tabla. créelo con una capacidad inicial apropiada para prevenir la sobrecarga del rehashing automático.

probablemente no se querrá generar un hashCode() que esté basado en información de un único objeto -en particular.hashCode()). entonces el HashMap o el HashSet será mas pesado de cargar en algunas áreas y no será tan rápido como podría ser en una función hash distribuida de forma pareja.out. Si los valores tienden a agruparse. He aquí un ejemplo que sigue estas líneas: 402 Pensando en Java www. Hay otro facto: un buen hashCode() tiene que resultar en una distribución de valores pareja. Este fue el problema que se sucedió en SpringDetector.com . el valor de this crea un hashCode() malo porque no puede generar una clave idéntica a la utilizada para colocar con put() el par clave-valor original. Se puede ver esto corriendo este programa: //: c09:StringHashCode. Así es que si se querrá utilizar la información en el objeto que identifica a el objeto en una forma significativa. debe ser rápido y debe ser significativo: esto es. y otro durante un get(). Así es que para que un hashCode() sea efectivo. Recordemos que este valor no tiene que ser único -se debe apuntar a la velocidad en lugar de la unicidad.hashCode()). Además.java public class StringHashCode { public static void main(String[] args) { System. Un ejemplo se encuentra en la clase String. solo se necesita generar un entero.println("Hello".println("Hello".BruceEckel. } } ///:~ Para que esto trabaje. Las cadenas tienen la característica especial que si un programa tiene varios objetos String que contienen secuencias idénticas de caracteres. no será capas de recuperar los objetos. Así es que si su hashCode() depende de datos que cambian en el objeto el usuario se debe hacer consiente que cambiar los datos efectivamente producirá una clave diferente que generara un hashCode() diferente.un valor hashCode() cuando esta en put() en un HashMap.java porque la implementación por defecto de hashCode() utiliza la dirección del objeto.y entre hashCode() y equals() la identidad del objeto puede ser completamente resuelta. Dado que se favorece que hashCode() sea procesado antes que el incide de buckets sea producido. System. entonces esos objetos String se acotan en la misma memoria (el mecanismo de esto esta descrito en el Apéndice A). el rango de valores no es importante. debe generar un valor basado en el contenido del objeto. el hashCode() para String debe ser basado en el contenido de String.out. Así es que tiene sentido que el hashCode() producido por dos instancias separadas de new String(“hello”) sean idénticas.

} public String toString() { return "String: " + s + " id: " + id + " hashCode(): " + hashCode() + "\n". El conteo se logra en al constructor por iteración a través del ArrayList estático donde todos los Strings son almacenados. new Integer(i)). m. // Id is the total number of instances // of this string in use by CountedString: while(it.equals(s)) id++. i++) { cs[i] = new CountedString("hi").out. public CountedString(String str) { s = str.put(cs[i]. for(int i = 0.id.get(cs[i])). CountedString[] cs = new CountedString[10]. import java.equals(((CountedString)o). created. si ellos son basados solamente en los String o en solamente el id encontraremos coincidencias duplicadas para distintos valores.iterator().java // Creating a good hashCode().println(m.out. } public static void main(String[] args) { HashMap m = new HashMap(). } public int hashCode() { return s. } public boolean equals(Object o) { return (o instanceof CountedString) && s. System. i < cs.print("Looking up " + cs[i]).next(). private static ArrayList created = new ArrayList(). private int id = 0.s) && id == ((CountedString)o).length.util. hashCode() y equals() produce resultados basados en ambos campos. 403 .out.//: c09:CountedString.add(s). i < cs. i++) { System.length.*. } System.hashCode() * id. } } } ///:~ CountedString incluye una cadena y un id que representa el numero de objetos CountedString que contiene una cadena idéntica. public class CountedString { private String s. for(int i = 0. Iterator it = created.hasNext()) if(it.println(m).

En orden. el recolector de basura no lo liberará porque sigue en uso por el programa. Se logra esto utilizando un objeto Reference como un intermediario entre el programador y la referencia común. pero si la agotamiento de memoria es inminente se permite que el objeto sea liberado. Esto puede significar que se tiene una referencia común en la pila que llega correctamente al objeto.Deba notarse lo simple que hashCode() es: el hashCode() para un String es multiplicado por el id. Hay tres clases heredadas de la clase abstracta Reference: SoftReference.BruceEckel. y PhantomReference. y PhantomReference.lang. WeakReference. Se utilizan objetos Reference cuando se quiere continuar almacenando una referencia a ese objeto -se quiere ser capaz de alcanzar ese objeto. En main(). El HashMap es desplegado así es que se puede ver como esto se almacena internamente (en órdenes que no se pueden discernir) y entonces cada clave es buscada individualmente para demostrar que el mecanismo de búsqueda esta trabajando adecuadamente.com . y no debe haber referencias comunes a el objeto (una que no haya sido envuelta dentro de un objeto Reference). Cada uno de estas proporcionan un nivel diferente de vía indirecta para el recolector de basura. y corresponden a diferentes niveles de accesibilidad. Las referencias weak son implementaciones de 404 Pensando en Java www. si el objeto en cuestión solo se puede alcanzar a través de uno de estos objetos Reference. que los duplicados crean valores únicos a causa del contador id. cada uno es “mas débil” que el anterior. Almacenando referencias La librería java. no liberará ese objeto. un montón de objetos CountedString son creados. Si un objeto es accesible esto significa que en alguna parte del programa el objeto puede encontrarse. SoftReference. pueden ser muchos enlaces intermedios. lo que es especialmente útil cuando se tiene grandes objetos que pueden causar agotamiento de memoria. El menor es generalmente mejor (y rápido) para hashCode(). Si un objeto no es accesible.ref contiene un grupo de clases que permiten una gran flexibilidad en la recolección de basura. utilizando el mismo String para mostrar. Las referencias soft son implementaciones de ocultación en memoria susceptible. Si un objeto es accesible. se tiene una forma de seguir utilizando el objeto. no hay forma de que el programa lo use así es que es seguro recolectar la basura de ese objeto. Si el recolector de basura descubre que un objeto es alcanzado por una referencia común. pero se puede querer también tener una referencia a un objeto que sea referencia al objeto en cuestión. De esta manera.pero también se quiere permitir que el recolector de basura libere ese objeto. WeakReference.

parseInt(args[0]). Las referencias phantom son para fijar la hora de acciones de limpieza premortem en una forma mas flexible de la posible con el mecanismo de finalización de Java. System.length. for(int i = 0.println("Just created: " + (VeryBig)wa[i].get()).get()). i < sa. Aquí hay una demostración simple: //: c09:References. public VeryBig(String id) { ident = id.println("Finalizing " + ident).println("Just created: " + (VeryBig)sa[i].println("In queue: " + (VeryBig)((Reference)inq). } WeakReference[] wa = new WeakReference[size]. SoftReference[] sa = new SoftReference[size]. public static void checkQueue() { Object inq = rq.lang. for(int i = 0.java // Demuestra los objetos Reference import java. checkQueue(). rq).esto no impide que sus claves (o valores) sean reclamados. // O. 405 . Mediante SoftReferences y WeakReferences. System.out. i++) { wa[i] = new WeakReference( new VeryBig("Weak " + i).get()). pero una PhantomReference solo puede ser creada en una ReferenceQueue. i < wa. i++) { sa[i] = new SoftReference( new VeryBig("Soft " + i). rq).mapeos canonicalizados -donde las instancias delos objetos pueden ser simultáneamente utilizados en varios lugares en un programa. } public void finalize() { System. se elige el tamaño mediante la linea de comandos: if(args.length.*.out.out.poll(). class VeryBig { static final int SZ = 10000. } public static void main(String[] args) { int size = 10.ref.out. } } public class References { static ReferenceQueue rq= new ReferenceQueue(). double[] d = new double[SZ]. } public String toString() { return ident. para ahorrar almacenamiento.length > 0) size = Integer. se tiene la elección acerca de colocarlas en una ReferenceQueue (el dispositivo utilizado para acciones de limpieza pre-mortem). String ident. if(inq != null) System.

se esta ahorrando almacenamiento haciendo solo una instancia del valor en particular.com . rq). } SoftReference s = new SoftReference( new VeryBig("Soft")). System.checkQueue(). es muy conveniente que el WeakHashMap permita que el recolector de basura automáticamente limpie las claves y los valores. Dado que esto es una técnica de ahorro de almacenamiento. El gatillo que permite la limpieza es si la clave no esta mas en uso. } } } ///:~ Cuando se ejecute este programa (se va a querer canalizar la salida a través de “mas” programas utilitarios así se puede ver la salida en paginas). se puede heredar de una clase Reference particular lo que se este interesad y agregar mas métodos útiles a el nuevo tipo de Reference.BruceEckel. checkQueue(). el WeakHashMap. Cuando el programa necesita ese valor. se utiliza get()). El WeakHashMap La librería de contenedores tiene un Map especial para almacenar referencias weak. Se puede ver también que la ReferenceQueue siempre produce una Reference conteniendo un objeto null. for(int i = 0. busca el objeto existente en el mapeo y utiliza ese (en lugar de crear uno a los arañazos). El mapeo puede hacer a los valores parte de la inicialización. 406 Pensando en Java www. i < pa. System. se podrá ver que el objeto es recogido por el recolector de basura.out. Es este tipo de mapeos.get()). WeakReference w = new WeakReference( new V eryBig("Weak")).println("Just created: " + (VeryBig)pa[i]. como se demuestra aquí: //: c09:CanonicalMapping. Para hacer uso de esto. Esta clase esta diseñada para hacer fácil la creación de mapeos canonicalizados. PhantomReference[] pa = new P hantomReference[size]. No se tiene que hacer nada especial para que las claves y los valores que se quieran sean colocados en el WeakHashMap: estos son automáticamente envueltos en WeakReferences por el mapeo. pero es mejor que los valores sean creados en demanda.length. aun si se sigue teniendo acceso a ellos a través de un objeto Reference (para obtener la referencia a el objeto actual.java // Demonstrates WeakHashMap. i++) { pa[i] = new PhantomReference( new VeryBig("Phantom " + i).gc().

porque una referencia común a esta clave ha sido colocada en el arreglo de claves y por lo tanto esos objetos no pueden ser recolectados.gc(). public Key(String id) { ident = id. Key[] keys = new Key[size].toString(i)). } public boolean equals(Object r) { return (r instanceof Key) && ident. i++) { Key k = new Key(Integer. // O. WeakHashMap whm = new WeakHashMap().ident). Value v = new Value(Integer. } public String toString() { return ident.println("Finalizing Key "+ ident). i < size. } public void finalize() { System. import java. for(int i = 0.equals(((Key)r). } } public class CanonicalMapping { public static void main(String[] args) { int size = 1000.ref.parseInt(args[0]). } public int hashCode() { return ident. } } class Value { String ident. } } ///:~ La clase Key debe tener un hashCode() y un equals() dado que son utilizados como claves en una estructura de datos de hash.lang.hashCode().out.out. 407 . como se describió previamente en este capítulo. Cuando se ejecute el programa se podrá ver que el recolector de basura saltara cada tercer clave. se elige el tamaño mediante la línea de comandos: if(args. } public void finalize() { System.*. } System.put(k.toString(i)). if(i % 3 == 0) keys[i] = k.length > 0) size = Integer.*. // Guardar como referencias "reales" whm. public Value(String id) { ident = id.util. v).println("Finalizing Value "+ident).import java. } public String toString() { return ident. class Key { String ident.

BruceEckel. i++) v. PrintData no sabe o no le importa el tipo de contenedor donde se origina el Iterator. Puede verse que PrintData. la clase PrintData utiliza un Iterator para moverse a través de una secuencia y llamar el método toString() para cada objeto. PrintData.println("HashMap").println("ArrayList").put(new Integer(i). Dado que un Iterator oculta la estructura de las capas mas bajas del contenedor. así es que se puede ver ambos cuando son impresos.entrySet(). Entonces se realiza una conversión descendente de el Object que retorna Iterator.out.out.com .print(v. //: c09:Iterators2. System. import java. se debe asumir de que el Iterator esta atravesando un contenedor de algún tipo específico.hasNext()) System. respectivamente objetos Mouse y Hamster (estas clases son definidas mas atrás en este capítulo). lo que contiene la clave y el valor para cada entrada.next()). el método entrySet() produce un Set de objetos Map.util.*. En el siguiente ejemplo. Es mejor en el problemas. i < 5.Revisión de iteratores Podemos ahora demostrar el verdadero poder del Iterator: la habilidad de separar la operación de atravesar una secuencia desde la estructura de capas bajas de esa secuencia. } } class Iterators2 { public static void main(String[] args) { ArrayList v = new ArrayList().out.add(new Mouse(i)).java // Revisiting Iterators.println() es automática. for(int i = 0.enty. System. Dos tipos diferentes de contenedores son creados -un ArrayList y un HashMap. class PrintData { static void print(Iterator e) { while(e.iterator()). PrintData.out. 408 Pensando en Java www. Por ejemplo.next() para producir una Shape.y pueden ser llenados con. i++) m.println(e.print() toma ventaja del hecho de que los objetos en estos contenedores son del la clase Object así es que la llamada a toString() hecha por System. se debe asumir que todo en el contenedor es una Shape con un método draw(). i < 5.iterator()). for(int i = 0.print(m. } } ///:~ Para el HashMap. new Hamster(i)). HashMap m = new HashMap().

La distinción entre los otros contenedores a menudo provienen de por “quien está apoyados”. y Stack es que hay clases heredadas. Por otro lado. se debe elegir HashSet por defecto. fuerzas y debilidades.Eligiendo una implementación Por ahora se debe entender que hay solo tres componentes contendores: Map. ¿Como se decide la implementación particular a utilizar? Para entender la respuesta. Esto significa que. mientras que LinkedList es implementada en la forma usual para una lista doblemente enlazada. esto es. Sin embargo. con objetos individuales cada uno conteniendo los datos junto con las referencias a el anterior y al siguiente elemento de la lista. A causa de esto. ArrayList es secundado por un arreglo. si se quiere hacer muchas inserciones y eliminaciones den el medio de la lista. Otro ejemplo. Cuando se esta escribiendo un programa que necesita un Set. el rendimiento de agregar en un TreeSet serán lentos. El siguiente código establece una 409 . y solo dos de tres implementaciones de cada interfase. una ArrayList es típicamente mas rápida. se puede ver en el diagrama que la “característica” de Hashtable. Por ejemplo. Un TreeSet esta apoyado por un TreeMap y esta diseñado para producir un grupo constantemente ordenado. si se va a tener Set muy grandes. Elección entre listas La forma mas convincente de ver las diferencias entre las implementaciones de List es con una prueba de rendimiento. una LKinkedList es la elección apropiada (LinkedList también tiene funcionalidades adicionales que son establecidas en una AbstractSequentialList). se debe ser conciente que cada una de las diferentes implementaciones tienen sus propias características. es mejor si no se utiliza estos en nuevo código (Java 2). Vector. por ejemplo. Sin embargo. un Set puede ser implementado como un TreeSet o como un HashSet. y cambiar a TreeSet cuando sea mas importante tener un grupo constantemente ordenado. List y Set. las estructuras de datos que físicamente implementan la interface deseada. así es que el viejo código no se romperá. ArrayList y LinkedList implementan la interfase List así es que el programa producirá el mismo resultado independientemente del que se utilice. Si no fuera así. Si se necesita utilizar la funcionalidad que entrega una interface particular.

int size.clase base interna para utilizar como framework de prueba.listIterator(3). i++) { for(int j = 0. i < reps. 300) { void test(List a.bruceeckel.com . } }.name = name.size()/2. Cada una de estas clases internas es llamada por el método test().iterator(). entonces se crea un arreglo de clases internas anónimas. int reps). int reps) { for(int i = 0. for(int i = 0.hasNext()) it.*. j++) a. //: c09:ListPerformance. int size) { this. new Tester("iteration".next(). while(it. while(it. int reps) { for(int i = 0. i < reps. } } }. import java.get(j).next(). // Test quantity Tester(String name. Esta estrategia permite fácilmente agregar y quitar nuevos tipos de pruebas. new Tester("insert". i++) { Iterator it = a.hasNext()) { it. 5000) { void test(List a. int reps) { int half = a.BruceEckel. } } 410 Pensando en Java www. int reps) { ListIterator it = a. ListIterator it = a. 5000) { void test(List a.size = size. import com.util. i < size * 10. this. new Tester("remove".listIterator(half).java // Demonstrates performance differences in Lists.add(s). } abstract void test(List a. i++) it. una para cada grupo diferente. public class ListPerformance { private abstract static class Tester { String name. it. } } }.util. String s = "test".remove().*.size(). } private static Tester[] tests = { new Tester("get". j < a. 300) { void test(List a.

para proporcionar una clase base para la prueba específica. simplemente agregue o quite una 411 .reset()).t1)). reps).currentTimeMillis().print(tests[i]. choose the number of repetitions // via the command line: if(args. // Can only do first two tests on an array: for(int i = 0. test(new LinkedList(). System.out. long t1 = System. que son inicializados con las diferentes clases anónimas que heredan de Tester.fill(a. long t2 = System.t1)).print(tests[i].name).name). reps).parseInt(args[0]). long t2 = System.length. // Or. System.reset(). Arrays2.out. i++) { Collections2.out. }. Todos los diferentes tipos de pruebas son recolectadas en un lugar. System.test(a. int reps) { // A trick to print out the class name: System.println(": " + (t2 .}. Contiene un String que será impreso cuando las pruebas comiencen.fill(sa. tests[i].countries. public static void test(List a. tests[i].currentTimeMillis().size].test(a.length > 0) reps = Integer. } } public static void testArray(int reps) { System. Para agregar o quitar tareas. reps).currentTimeMillis(). long t1 = System.currentTimeMillis(). los arreglos test.out. for(int i = 0.println("Testing " + a. reps). i++) { String[] sa = new String[tests[i].println("Testing array as List").println(reps + " repetitions").println(": " + (t2 .asList(sa).out. } } ///:~ La clase interna Tester es a bstract.size). Collections2. testArray(reps). reps). List a = Arrays. test(new Vector().getClass(). un parámetro size que será utilizado para probar la calidad de los elementos o repeticiones de las pruebas. tests[i]. } } public static void main(String[] args) { int reps = 50000.out. System.countries.out. Collections2. i < tests. un constructor para inicializar los campos. System.getName()). y un método abstracto test() que hace el trabajo. i < 2. test(new ArrayList().

el siguiente programa de prueba da una indicación de este cambio: //: c09:SetPerformance. y cambiar a una LinkedList si se descubren problemas de rendimiento debido a muchas inserciones y eliminaciones del medio de la lista. y puede ser evitado.definición de clase interna del arreglo. las inserciones y las eliminaciones del medio de una lista son dramáticamente mas económicas para una LinkedList que para una ArrayList -especialmente las eliminaciones. La List que es manejada para test() es primero llenada con elementos. estos pretenden dar solo un orden de magnitud para realizar una comparación entre el rendimiento de los diferentes contenedores. Puede verse que solo las dos primeras pruebas pueden ser realizadas en este caso. Se puede ver que los acceso aleatorios (get()) son económicos para ArrayLists y costosos para LinkedLists (Curiosamente. utilice un arreglo. los arreglos son mas rápidos que cualquier contenedor para acceso aleatorios. dado que no se puede insertar o quitar elementos de un arreglo. Por el otro lada.BruceEckel.asList(). la iteración es mas rápida para una LinkedList que para una ArrayList. Eligiendo entre Sets Se puede elegir entre un TreeSet y un HashSet. y todo lo demás se sucede automáticamente. La mejor estrategia es probablemente elegir una ArrayList por defecto. He aquí un resumen de una corrida: Tipo arreglo ArrayList LinkedList Vector Extraer 1430 3070 16320 4890 Iteración 3850 12200 9110 16250 Insertar na 500 110 550 Eliminar na 46850 60 46850 Como se esperaba. una prueba especial es creada para arreglos envolviéndolo como una List utilizando Arrays. El resultado puede variar de máquina a máquina. luego a cada prueba en los arreglos se le mide el tiempo. se debe utilizar un TreeSet). Vector generalmente no es tan rápido como una ArrayList. búsquedas e iteración.com .java 412 Pensando en Java www. Claro. no solo en la librería para soporte de código heredado (la única razón por lo que funciona en este programa es porque fue adaptado para ser una List en Java 2). lo que va un poco en contra de la intuición). Para comparar el acceso de los arreglos a el acceso a los contenedores (inicialmente contra ArrayList). dependiendo de el tamaño del Set (si se necesita producir una secuencia ordenada de un Set. si se esta trabajando con un grupo de elementos de tamaño fijo.

i < reps. Tester(String name) { this. }. } private static Tester[] tests = { new Tester("add") { void test(Set s. int size.out. } } public static void main(String[] args) { int reps = 50000. int size.getName() + " size " + size). System. public static void test(Set s. i++) { Iterator it = s.import java. Collections2. Collections2. int size.toString(j)). int reps). int size. tests[i].test(s. i++) { System.print(tests[i]. size. } } }. // O elija el número de repeciones repetitions // mediante la linea de comandos: if(args.out.currentTimeMillis().*.clear(). i++) { s. new Tester("iteration") { void test(Set s.name).getClass(). Collections2.reset().util.contains(Integer.currentTimeMillis().util. Collections2.countries.length. public class SetPerformance { private abstract static class Tester { String name.next().countries. new Tester("contains") { void test(Set s.t1)/(double)size)). long t2 = System. j < size.size).println("Testing " + s. reps). int reps) { for(int i = 0.length > 0) 413 . long t1 = System.println(": " + ((double)(t2 . j++) s.name = name. i++) for(int j = 0. } abstract void test(Set s.*. size). while(it. for(int i = 0.hasNext()) it. i < reps * 10.fill(s. int size. int reps) { System. i < reps. int reps) { for(int i = 0. } } }. } }.out.reset(). import com.iterator(). i < tests.bruceeckel. int reps) { for(int i = 0.fill(s.

test(new HashSet().1 20 6. esto puede ser de acuerdo a la computadora y a la JVM que se esté utilizando.5 192 . La única razón por la que TreeSet existe es porque 414 Pensando en Java www. reps). reps). reps). 04 H as h Se t 10 100 1000 82. 10.0 106. // Grande: test(new TreeSet().0 Tr ee Se t 100 151. 10. // Mediano: test(new TreeSet(). 100. las operaciones mas importantes).parseInt(args[0]). todos debería correr la prueba): A g r e g a r 1 3 8.0 187 . reps).4 40. 39 El rendimiento de HashSet es generalmente superior a TreeSet para todas las operaciones (pero en particular agregando y buscando. // Pequeño: test(new TreeSet(). } } ///:~ La siguiente tabla muestra el resultado de una corrida (Claro.0 20 2. reps). 1000. 2 1 5 0 .com .6 5 5 4 5 3 6 Ti p o Tamaño de la Prueba Conten edores Iter aci ón 10 115.0 90. 1000.reps = Integer.BruceEckel.2 39. reps). test(new HashSet(). 0 1 8 9. 100.5 1000 177. test(new HashSet().

hasNext()) it. int size. int reps).name = name.util. Eligiendo entre Maps Cuando se elige entre implementaciones de Map.fill(m.reset().clear(). }. Collections2.util. i++) { 415 .fill(m. i < reps * 10. while(it.length. Collections2. import java.geography. Collections2.mantiene sus elementos en orden. j++) m. int reps) { for(int i = 0.entrySet().getName() + " size " + size). int size. i++) { Iterator it = m. int reps) { for(int i = 0. public static void test(Map m. for(int i = 0. int size. Tester(String name) { this.*. } abstract void test(Map m. el tamaño del Map es lo que mas fuertemente afecta el rendimiento. i < reps. int size. size).geography. así es que solo debe utilizarse cuando se necesite un grupo ordenado. Collections2.bruceeckel.*. new Tester("get") { void test(Map m. public class MapPerformance { private abstract static class Tester { String name. j < size. } private static Tester[] tests = { new Tester("put") { void test(Map m. i++) for(int j = 0.get(Integer.toString(j)). int size. i < reps. int reps) { for(int i = 0. new Tester("iteration") { void test(Map m. } } }.out.getClass().next(). } }.reset().println("Testing " + m.iterator(). size).java // Demuestra los diferentes rendimientos en Maps. i < tests. int reps) { System. import com. i++) { m. } } }. y el siguiente programa de prueba da una indicación de este intercambio: //: c09:MapPerformance.

long t2 = System. long t1 = System.3 110.t1)/(double)size)).0 201. reps). test(new Hashtable(). System.7 105. se verá que las pruebas de cronometraje dividen el tiempo por el tamaño para normalizar cada medida. test(new HashMap().3 416 Pensando en Java www.0 329. test(new HashMap(). reps). // Grande: test(new TreeMap().currentTimeMillis().0 80.BruceEckel.2 61. 100.com .0 47. 100. 10.test(m.4 205. elija el número de repeticiones // mediante la linea de comandos: if(args.println(": " + ((double)(t2 . // O.6 54.currentTimeMillis().print(tests[i]. 1000.0 143. reps).out. 10. size. 1000.parseInt(args[0]).2 83. 10.0 188. test(new Hashtable().1 222. 1000.1 40. Tipo TreeMap Tamaño de la prueba 10 100 1000 HashMap 10 100 1000 Hashtable 10 100 1000 Poner 143.name).1 Tomar 110. // Medio: test(new TreeMap().8 66.0 135. reps). } } ///:~ Dado que el tamaño del mapa es el tema. } } public static void main(String[] args) { int reps = 50000. Aquí hay un grupo de resultados (correr el programa probablemente entregue valores diferentes).4 302. // Pequeño: test(new TreeMap(). tests[i]. reps).7 93.0 280. reps).7 278. test(new Hashtable(). reps).0 90.95 Iteración 186. 100. test(new HashMap().out.5 41.7 197.System.7 48. reps). reps).length > 0) reps = Integer. reps).

println(list + "\n").java: //: c09:ListSortSearch.println("Location of " + key + " is " + index + ". cuando se esta utilizando un Map la primera elección suele ser un HashMap. HashMap pretende remplazar Hashtable). sino como una forma de crear una lista ordenada. Una vez que se llena un TreeMap. El comportamiento de un árbol es tal que siempre esta en orden y no tiene por que estar especialmente clasificado.get(12).println("After shuffling: "+list). Collections2.out. public class ListSortSearch { public static void main(String[] args) { List list = new ArrayList(). se puede fácilmente crear un HashMap de un TreeMap con la creación de un solo objeto al final. y luego si se necesita un Map continuamente ordenado se necesitará un TreeMap.out.out.binarySearch() (discutido mas adelante) para encontrar objetos rápidamente en el arreglo ordenado.*. System. modificado de ArraySearching. list. se puede llamar a ketSet() para obtener una vista del Set para estas claves. int index = Collections. System. System. System.util. el comportamiento de un HashMap es inaceptable. Ordenando y explorando Lists Los utilitarios para realizar ordenamientos y búsquedas en Lists tienen los mismos nombres y firmas que aquellos para realizar búsquedas de arreglos de objetos. por alguna razón. Collections2. pero son métodos estáticos de Collections en lugar de Arrays. También.capitals. el rendimiento de Hashtable es mas o menos el equivalente a HashMap (se puede ver también que HashMap generalmente es un poquito mas rápido.sort(list). Claro.get(" + 417 .java // Sorting and searching Lists with 'Collections.' import com. Collections.println(list + "\n"). 25). import java.binarySearch(list. He aquí un ejemplo.Como se podía esperar. Object key = list. luego toArray() para producir un arreglo con estas claves.fill(list. Se puede utilizar entonces el método estático Arrays. así es que por que se utiliza? Así es que no se debería utilizar como un Map. ¿TreeMap es mas lento generalmente que HashMap. dado que HashMap esta diseñado para encontrar cosas rápidamente. key).util. que se debería hacer esto si.out.shuffle(list). Collections.*.bruceeckel.

get(" + index + ") = " + list. max(Collection) min(Collection) max(Collection.out.com . Produce el máximo o el mínimo elemento en la Collection utilizando el Comparator. Comparator) min(Collection.get(12). } } ///:~ El uso de estos métodos es idéntico a los de Arrays. List origen) 418 Pensando en Java www. Este programa también demuestra el método shuffle() en Collections.println(list + "\n"). si se ordena utilizando un Comparator se debe realizar una binarySearch() utilizando el mismo Comparator. AlphabeticComparator comp = new AlphabeticComparator().sort(list. list. pero se está utilizando una List en lugar de un arreglo. que desordena el orden de una List. index = Collections. Comparator) reverse() copy(List destino.get(index)). Invierte de lugar todos los elementos. key = list.out. Produce el máximo o mínimo elemento en el argumento utilizando el método natural de comparación de los objetos en la Collection.index + ") = " + list.binarySearch(list. key. Utilitarios Hay algunas otras utilidades útiles en la clase Collections: enumeration(Collection) Produce una Enumeration del viejo estilo para el argumento. comp).get(index)).BruceEckel. comp). Collections. System.println("Location of " + key + " is " + index + ". Copia elementos de origen a destino. System. Exactamente como explorando y ordenando con arreglos.

Retorna una List inmutable de tamaño n cuyas referencias apuntan todas a o. System.println(lit. Object o) Remplaza todos los elementos de una lista por o.*.add("one"). Collections2.unmodifiableSet(s). System. y Map. // No se puede cambiar Set s = new HashSet(). List.fill(List lista. gen. Set. // Lectura OK lit.fill(a. System. 25). 25).out. 25). La clase Collection permite hacer esto pasando el contenedor original dentro de un método que retorne una versión de solo lectura.*. una para cada Collection (si no se quiere manejar una collection de un tipo mas específico).unmodifiable methods. no con Lists. // Insertar datos c = Collections. // Reading OK 419 .unmodifiableList(a).countries.println(c).add("one"). public class ReadOnly { static Collections2.bruceeckel.util.reset(). // Lectura OK c. gen. Este ejemplo muestra la forma adecuada de crear versiones solo lectura de cada una: //: c09:ReadOnly.out. se necesita realizar un short() (ordenar) una List o un arreglo antes de realizar una binarySearch()).java // Using the Collections. así es que no necesita preocuparse por cuando la Collection debe ser ordenada o no (Como se ha mencionado antes. nCopies(int n. ListIterator lit = a.listIterator().println(s).reset().unmodifiableCollection(c). s = Collections.StringGenerator gen = Collections2.fill(s. import java. gen. Hay cuatro variaciones de este método. a = Collections.fill(c. Collections2. Collections2. public static void main(String[] args) { Collection c = new ArrayList(). import com.util. Object o) Debe notarse que min() y max() trabajan con una objetos Collection. Haciendo una Collection o un Map inmodificable A menudo es conveniente crear una versión de solo lectura de una Collection o un Map.out. // No se puede cambiar List a = new ArrayList().next()).

Map m = Collections.put("Ralph". m = Collections. pero cualquier otro solo puede leerla. Por el otro lado. "Howdy!").synchronizedCollection( new ArrayList()).out.synchronizedMap( new HashMap()). pero una vez que la transformación ha sucedido.add("one"). Collections2. un tema mas complicado que no será introducido hasta el capítulo 14. la mejor estrategia es reemplazar la referencia existente con la referencia que fue producido por la llamada a el “inmodificable”. no se corre el riesgo de accidentalmente cambiar el contenido una vez que se ha hecho inmodificable. System.*.//! s. Collections2.geography. veremos solamente que la clase Collections contiene una forma para sincronizar automáticamente un contenedor entero. se debe llenar el contenedor con datos significativos antes de hacerlo de solo lectura. // Lectura OK //! m.synchronized methods. 25).BruceEckel. La sintaxis es similar a los métodos “inmodificable”. Set s = Collections. Llamando a el método “inmodificable” para un tipo en particular no causa una verificación en tiempo de compilación.fill(m. De esta forma. 420 Pensando en Java www. Una vez que es cargada. // No se puede cambiar Map m = new HashMap().util. public class Synchronization { public static void main(String[] args) { Collection c = Collections. } } ///:~ En cada caso. Sincronizando una Collectiono un Map La palabra clave synchronized es una parte importante del tema de hilos múltiples. Así es que se puede cambiar esta dentro de la clases. esta herramienta también permite mantener un contenedor inmodificable como privada dentro de una clase y retornar una referencia de solo lectura como contenedor de una llamada a método.unmodifiableMap(m).synchronizedList( new ArrayList()).synchronizedSet( new HashSet()).println(m).com . cualquier llamada a métodos que modifican el contenido de un contenedor en particular producirán una UnsupportedOperationException. List list = Collections. //: c09:Synchronization. Aquí.java // Using the Collections. import java.

next(). Debe notarse que no se puede beneficiar de este tipo de monitoreo cuando se esta accediendo a los elementos de una List utilizando get(). Falla rápida Los contenedores de Java tienen también un mecanismo para prevenir que mas de un proceso modifique el contenido de un contenedor. inmediatamente se pasa el nuevo contenedor a través del método “synchronized”.java // Demonstrates the "fail fast" behavior. import java.} } ///:~ En este caso. de esta forma no hay posibilidad de accidentalmente exponerlo a una versión no sincronizada. tal vez este adelante. La librería de contenedores de Java incorpora un mecanismo de falla-rápida que mira cualquier otro cambio en el contenedor del cual no sea personalmente responsable el proceso. La posibilidad que dos partes del programa puedan modificar el mismo contenedor produce un estado de incertidumbre.util. Detecta si alguien mas está modificando el contenedor. } } ///:~ La excepción se sucede porque alguien esta colocando algo en el contenedor luego que el iterator es obtenido por el contenedor. El problema sucede si se esta iterando a través de un contenedor y algún otro proceso interviene e inserta. 421 . así es que la excepción notifica que se debe cambiar su código -en este caso. // Causes an exception: String s = (String)it. e inmediatamente produce un ConcurrentModificationException.add("An object"). Es fácil de ver el mecanismo de falla rápida en operación -todo lo que se tiene que hacer es crear un iterator y luego agregar algo a la colección al que el iterator esta apuntando. public class FailFast { public static void main(String[] args) { Collection c = new ArrayList().*.iterator(). Tal vez ya se haya pasado ese objeto. Este es el aspecto “falla rápida” -no trata de detectar un problema mas tarde utilizando un algoritmo mas complejo. tal vez el tamaño del contenedor se contrae antes de que se llame a size() -hay muchos escenarios para el desastre. c. Iterator it = c. borra o cambia un objeto en el contenedor. de la siguiente forma: //: c09:FailFast. obtener el iterator después de que se han agregado todos los elementos al contenedor.

// Compila pero no corre: lit. System. // Unsupported a. i++) a.removeAll(a2).containsAll(a2) = " + a.isEmpty()). // Unsupported a.*.previous() + " "). "five".out.retainAll(a2).com . Se aprenderá todo acerca de excepciones en el siguiente capítulo.println( "a.listIterator(a. static List a = Arrays. "six".asList(): //: c09:Unsupported.indexOf(" + s[5] + ") = " + a.println( "a.contains(s[0])).BruceEckel.add("X").out. "four". "nine".out.Operaciones no soportadas Es posible convertir un arreglo en una List con el método Array. while(lit. "seven".out.subList(3. // Unsupported a. i < a. "eight".contains(" + s[0] + ") = " + a. "three".size()).util. "47"). public static void main(String[] args) { System.out.clear().addAll(a2).println( "a.containsAll(a2)). // Unsupported } } ///:~ Se descubrirá que una parte de la interfase de la Collection y la List son implementadas actualmente.println(). // Grupo de elementos de diferente valor: for(int i = 0.add("eleven").println(a2). El resto de los método causan la indeseada venida de algo llamado UnsupportedOperationException.println(a).remove(s[0]).out.size(). }.println(a).java // Algunos métodos definidos en la interfase // de Collection no funcionan! import java.print(lit. 6).asList(s). System.out.set(i. public class Unsupported { private static String[] s = { "one". static List a2 = a. System. System.out.indexOf(s[5])). pero la historia corta es que 422 Pensando en Java www.isEmpty() = " + a. // Unsupported a.println("a.hasPrevious()) System. // Unsupported a. "ten". System.out. System. "two". System. // Atravesar açhacia atrás: ListIterator lit = a. // Unsupported operation a.

El diseño proporciona una “puerta trasera” si se quiere crear una nueva Collection sin proporcionar definiciones significativas para todos los métodos en la interfase de la Collection. Para que esta estrategia funcione. “!El punto mas importante de las interfases o las clases base es que prometen esos métodos harán algo significativo! ¡Esto rompe esa promesa -ellos dicen que no solo llamando algunos métodos no se tendrá un comportamiento significante. incrédulamente. y encajar igual en la librería existente. esta indica un error de programación: se esta utilizando una implementación de forma 2. Set. que pueden o pueden no ser “soportados” en la clase concreta que implementa esa interfase. Con una Collection. Además. Cuando una operación no es soportada. LinkedList.soportan todas las operaciones. de la misma forma que otras implementaciones en concreto. debería ser razonable la posibilidad de que una unsupportedOperationException apareciera en tiempo de implementación. detendrán el programa! ¡La seguridad de tipo simplemente es tirada por la ventana!” No es tan malo. el contenedor es simple de aprender y utilizar. y HashMap. las operaciones no soportadas son un caso especial que pueden ser aprendidas mas adelante. así es que no es como Smalltalk (en donde se puede llamar cualquier método para cualquier objeto. Esto es verdad en las librerías de contenedores de Java. Esto no siempre es posible para tomar todos los casos especiales de interfases. en lugar de que sucediera luego de enviado el producto al cliente. o Map.tienen métodos “opcionales”. List. y tener respuesta solo cuando se ejecuta un programa donde su llamada hace algo). sin embargo: 1. muchos métodos que toman una Collection como argumentos solo leen de esa Collection -todos los métodos que “leen” de Collection no son opcionales. “¿¡¿Que?!?” se puede decir. y solo en casos especiales debe una operación no ser soportada. La estrategia de la “operación no soportada” alcanza una importante meta en la librería de contenedores de Java. dado que alguien puede siempre inventar una nueva interfase. Después de todo. 423 . La UnsupportedOperationException debe ser un evento raro. Esta estrategia previene una explosión de interfases en el diseño. en las clases que se utilizan el 99 por ciento de as veces -ArrayList. el compilador a pesar de ello restringe el llamar solo los métodos en esa interfase. HashSet.la interfase Collection -al igual que algunas de las otras interfases en la librería de contenedores de Java. Llamar a un método no soportado produce una UnsupportedOperationException para indicar un error de programación. Esto es. para la mayoría de las clases todas las operaciones deben funcionar. Otros diseños para las librerías de contenedores siempre parecen terminar con un confuso exceso de interfases para describir cada una de las variantes del tema principal y son de esta forma difíciles de aprender.

Vector y Enumeración La única secuencia que se explicaba sola de Java 1. así como puede confundir algunas personas y hacerlas pensar que Vector se ha mejorado. Este punto es menos cierto. una nueva interfase fue requerida para expresar este tipo diferente de comportamiento (llamada. Básicamente. o Map como argumente especificará que métodos opcionales deben ser implementados. Sus defectos eran muchos para describir aquí (vea la primera edición de este libro.com .set(). así es que en el siguiente ejemplo el método collections2.1 Desafortunadamente un montón de código fue escrito utilizando contendores de Java 1. Esto es un poco perverso. por el otro lado.BruceEckel.BruceEckel.0/1. y es donde la naturaleza experimental de este diseño comienza a jugar.com libremente). Arrays. Set. En la librería de contendores de Java 2. Sin embargo tiene sentido que solo soporte operaciones que no cambien el tamaño del arreglo. los viejos contenedores son muy limitados. tal vez. y de esta forma se utilizaba mucho. cuando es incluido actualmente para poder soportar código anterior a Java 2.1. List.fill() es utilizado exitosamente. “FixedSizeList”). Por ejemplo. dejará una puerta abierta a la complejidad y pronto no se sabrá donde comenzar cuando se intente utilizar la libraría. Contenedores de Java 1.incorrecta.asList() produce una List que es respaldada por un arreglo de tamaño fijo. En el ejemplo anterior. así es que no hay mucho para saber de ellos (Dado que ellos son pasado. me trataré de refrenar de darle demasiada importancia a las horrendas decisiones de diseño). ordenar requiere los métodos set() e Iterator.1 era el Vector. se puede pensar en esto como un Arra yList con lagos y complicados nombres de métodos. La documentación para un método que toma una Collection. Vector fue adaptado de tal forma que se puede encontrar como una Collection y una List. disponible en el CD-ROM y en www. 424 Pensando en Java www. Sin embargo. y a aún nuevo código es a veces escrito utilizando estas clases.0/1. Si. pero no add() y remove(). se debe estar concientes de estos. Solo en horas extras se encontrara que tan bien trabaja esto.0/1. Así es que a pesar nunca se utilice los viejos contenedores cuando se escriba nuevo código.

Para producir una Enumeration. “enumeración”.elements(). se puede seguir utilizando los nuevos contenedores. De esta manera. aún en los nombres de 425 . Hashtable Como se ha visto en las comparaciones de rendimiento en este capítulo. la Hashtable básica es muy similar a la HashMap.1 del iterator.println(e.0/1.hasMoreElements()) System.nextElement()).0/1. si se tiene código viejo que necesita una Enumeration.out. como se ha visto en este ejemplo: //: c09:Enumerations.util.Se decidió por elegirle un nuevo nombre a la versión de Java 1.countries. y aun nuevas librerías a veces utilizan la vieja Enumeration -que es desafortunadamente pero generalmente inofensiva. } } ///:~ El Vector de Java 1. class Enumerations { public static void main(String[] args) { Vector v = new Vector().1 tiene solo un método addElement().enumeration().1 Vector y Enumeration. import com. Collections2.0/1.util. import java. La última línea crea un ArrayList y utiliza enumeration() para adaptar una Enumeration de un ArrayList Iterator. no una implementación.bruceeckel. Además. luego se puede utilizar para realizar una iteración hacia adelante. Collections2.*. se debe llamar a elements(). y Object nextElement() retorna el siguiente elemento de esta enumeración si hay mas (de otra forma lanza una excepción).java // Java 1. // Produce una Enumeration de una Collection: e = Collections. se puede producir una Enumeration para cada Collection utilizando el método Collections. utiliza nombres de métodos muy grandes: boolean hasMoreElements() produce un true si la enumeración contiene mas elementos.*. con solo dos métodos.enumeration(new ArrayList()).fill( v. 100). pero fill() utiliza el método add() que incorporado en Vector cuando se convirtió en una List. La interfase Enumeration es mas pequeña que la de Iterator. Enumeration es solo una interfase. Enumeration e = v. while(e. en lugar de utilizar un termino que para todos ya era familiar. se debe estar preparado para librerías que quieren utilizar una Enumeraton. Aún cuando se debería utilizar siempre Iterator cuando se pueda en el código.

i < months. todas las operaciones que pueden ser realizadas en un Vector.println(stk. "November". "March". Así es Stack que tiene todas las características y comportamientos de un Vector mas algunos comportamientos extras de Stack. "June". import java. o si simplemente es un diseño sencillo.BruceEckel.addElement("The last line"). Esto es posible porque.métodos. y luego traído de la parte superior de la pila con un pop(). "February". Como mencionamos antes. Lo que es realmente extraño con el Stack de Java 1. 426 Pensando en Java www. System.println("stk = " + stk). while(!stk.length. Stack El concepto de pila fue introducido temprano. public static void main(String[] args) { Stack stk = new Stack(). Para aclarar el asunto. Esto es difícil de saber si los diseñadores explícitamente decidieron que era una forma útil de hacer las cosas. public class Stacks { static String[] months = { "January".out. "October". "September".println( "element 5 = " + stk.out.out. "May".1 es que en lugar de utilizar un Vector como un bloque para construirlo. for(int i = 0. como elementAt().elementAt(5)). System. con la LinkedList. "July". System.java // Demonstracion de la clase Stack. i++) stk. // Tratando un stack como un Vector: stk. } } ///:~ Cada línea en el arreglo months es insertada dentro de Stack con puch().empty()) System. de debe utilizar una LinkedList cuando se quiere el comportamiento de una pila.pop()).0/1. "August". Aquí hay una demostración simple de Stack que empuja las líneas de un arreglo de String: //: c09:Stacks.println("popping elements:"). "December" }.push(months[i] + " ").com . De esta manera. Stack es heredado de Vector. "April". por virtud de la herencia. No hay razón para utilizar Hashtable en lugar de HashMap en el nuevo código. las operaciones de Vector son también realizadas por en el objeto Stack.util.out.*. un Stack es un Vector. pueden también ser realizadas en un Stack.

short st = (short)rand. printBitSet(bb). BitSet bs = new BitSet(). else 427 . int it = rand. for(int j = 0. o simplemente un arreglo.out. es mejor crear su propia clase.*. para almacenar sus banderas si el tamaño es el tema. else bb. System. Esto implica que si se esta almacenando algo menor. System. i >=0.clear(i). } public static void main(String[] args) { Random rand = new Random(). y el BitSet hace esto de la misma forma. i >=0. un BitSet será antieconómico. for(int i = 15. Es eficiente solo desde el punto de vista del tamaño. printBitSet(bs). i--) if(((1 << i) & bt) != 0) bb. BitSet bi = new BitSet(). else bs.println("bits: " + b).set(i). // Toma el LSB del nextInt(): byte bt = (byte)rand.size() . i--) if(((1 << i) & it) != 0) bi. j++) bbits += (b. el tamaño mínimo para un BitSet es el de un long: 64 bits.BitSet Un Bitset es utilizado si se quiere almacenar eficientemente un montón de información de llaves. j < b. String bbits = new String().println("bit pattern: " + bbits). El siguiente ejemplo muestra como el BitSet trabaja: // Demonstración de BitSet.set(i). como 8 bits. for(int i = 31.out. Un contenedor normal se expande a medida que se van agregando elementos.out. i--) if(((1 << i) & st) != 0) bs.set(i). for(int i = 7.println("short value: " + st). si se esta buscando un acceso eficiente. import java.nextInt(). public class Bits { static void printBitSet(BitSet b) { System.nextInt().get(j) ? "1" : "0"). BitSet bb = new BitSet(). System.util.nextInt(). es ligeramente mas lento que utilizar un arreglo de algún tipo nativo.out. Además.clear(i).println("byte value: " + bt). i >=0.

println("int value: " + it). // Prueba bitsets >= 64 bits: BitSet b127 = new BitSet(). así es que ninguno de estos produce un incremento en tamaño. así es que no almacena primitivas y se debe siempre realizar una conversión del resultado cuando se extraiga una referencia a un Objeto de un contenedor. 2. } } ///:~ El generador de números aleatorios es utilizado para crear números byte. b1023. 3.set(1023).BruceEckel. el tamaño no puede ser cambiado una vez que se ha creado. Resumen Para repasar los contenedores proporcionados por la librería estándar de Java: 1. Este puede ser multidimensional. BitSet b255 = new BitSet(65).bi. b255.out.out.set(1024). Entonces un BitSet de 512 bits es creado. mientras que un Map almacena pares asociados. Sin embargo. y puede almacenar primitivas.com . y int aleatorios. Un arreglo asocia índices numéricos a objetos. y una LinkedList si se esta haciendo una gran cantidad de inserciones y eliminaciones en el medio de la lista. b1023.println("set bit 127: " + b127). 428 Pensando en Java www. System.println("set bit 1023: " + b1023). Este almacena objetos de un tipo conocido así es que no se tiene que realizar una conversión del resultado cuando se está buscando un objeto. Esto trabaja bien porque un BitSet son 64 bits.println("set bit 255: " + b255). printBitSet(bi). se pueden configurar 1024 o mas.out. System.out.set(255).clear(i). 4. Pero una List solo puede almacenar referencias a objetos. Sin embargo. short. System. System. b127. Una Collection almacena elementos simples. Como un arreglo. y cada uno es transformado en su correspondiente patrón en bits en el BitSet. una List también asocia índices numéricos a objetos -se puede pensar en arreglos y Lists como contenedores ordenados. Use un ArrayList si esta haciendo una gran cantidad de accesos aleatorios.set(127). La List automáticamente cambia el tamaño sola cuando se agregan mas elementos. El constructor asigna espacio para el doble de bits. BitSet b1023 = new BitSet(512).

busque el Roedor para cada clave e imprime la clave e indíquele al Roedor que salte(). No hay necesidad de utilizar las clases heredadas Vector. Hashtable y Stack en el nuevo código. 8. Obtenga un Iterator para el ketSet() y úselo para moverse a través del Map. Cree un ArrayList y agregue algunos objetos Roedro a la List. asocie el nombre del Roedor con un String (la clave) para cada Roedor (el valor) que se coloca en la tabla. Cree una nueva clase Roedor con un int numeroDeRoedor que es inicializado en el constructor (similar a el ejemplo Mouse en este capítulo). HashSets proporciona las búsquedas mas rápidas.5. ArrayList y LinkedList) y llénela utilizando collections2. Un Set solo acepta uno de cada tipo de objeto. 1. 7. mientras que TreeSets mantiene los elementos en orden. deques. y de esta manera no es tan rápido como un HashMap. disponible por una pequeña propina en www. Ahora use el método get() para moverse a través de la List y llame salte() para cada Gerbil. Ejercicios La solución de los ejercicios seleccionados pueden encontrarse en el documento electrónico The T h i n k i n g in Java Annotated Solution Guide. El diseño de un HashMap esta enfocado en el acceso rápido. Imprima el resultado. Un Map es la forma de asociar no números. Modifique el ejercicio 2 de tal manera de que se use un Iterator para moverse a través de la List mientras se llama salte(). y pilas es proporcionado por la LinkedList. 3. Los contenedores son herramientas que se pueden utilizar día a día para hacer sus programas mas simples. y lo que esta saltando. mas poderosos y mas efectivos. 4. Crea una List (intente con ambas. 2. objetos con otros objetos.BruceEckel.countries. luego aplique Collections. Tome la clase Roedor en el ejercicio 2 y colóquela dentro de un Map. Ordene la lista e imprímala.shuffle() a la lista repetidamente.com . 429 . Cree un arreglo de double y llénelo utilizando fill() utilizando RandDoubleGenerator. El comportamiento de las colas. 6. Dele un método llamado salte() que imprima que numero de roedor es. imprímala cada ves así puede ver como el método shuffle() desordena la lista de forma diferente cada vez. donde un TreeMap tiene sus claves en orden. 5.

15. 10. Repita el ejercicio 9 para un contendor de int.bruceeckel.java de tal forma que se herede de ArrayList en lugar de utilizar composición. 8. Cree una clase conteniendo dos objetos String.bruceeckel. 9. Ahora cree un Comparator que solo le importe el segundo String y demuestre que ordena palabras adecuadamente.com . luego imprima cada contenedor. 13. y regresar al principio cuando se salga de los nombres. Si el arreglo interno no es suficientemente grande para cada agregado. En su comparación de rendimiento. un ArrayList. 12. y compare el rendimiento con un ArrayList almacenando objetos Integer. también realice una búsqueda binaria utilizando su Comparator. un LinkedList y los dos tipos de Set. 14.BruceEckel. Demuestre que no puede agregar nada mas que un Mouse a la MouseList. Utilice las utilidades en com. 430 Pensando en Java www. Modifique el ejercicio 13 de tal forma que un ordenamiento alfabético sea utilizado. Demuestre que ordena palabra adecuadamente.RandStringGenerator para llenar un treeSet pero utilizando un ordenamiento alfabético.java creando un contenedor Cats (utilizando una ArrayList) que no solo acepte y recupere objetos C a t. su contendor debe automáticamente cambiar de tamaño. utilice el generador geography.util.6. compare el rendimiento de su contenedor con un ArrayList almacenando Strings. Modifique MouseList. y que solo agregue String y se obtengan Strings. y hágala Comparable de tal forma que a la comparación solo le importe el primer String. 7. Llene un arreglo y una ArrayList con objetos de su clase. incluya el proceso de incrementar cada objeto en el contenedor.util para llenar un arreglo. Utilice las utilidades en com. Use Array2. Repare CatsAndDogs. 11. En main(). de tal forma que no haya temas de conversiones mientras se utiliza. Imprima el TreeSet para verificar el orden. Demuestre el problema con esta estrategia. Cree un contendor que encapsule un arreglo de String. cree un arreglo con cada tipo primitivo y con String. e imprima cada arreglo utilizando el método print() apropiado. Cree un generador que produzca nombres de caracteres para su película favorita (se puede utilizar Snow White o Star Wars como un sistema de soporte). luego llene cada arreglo utilizando un generador apropiado.

Llene una List de su arreglo. Cambie el ejercicio 6 del capítulo 7 para utilizar un ArrayList para almacenar los Rodents y un Iterator para moverse a través de la secuencia de Rodents.java como inspiración. Imprima cada lista utilizando un Iterator común. Cree un subgrupo de su List utilizando subList(). 19. Comience con Statistics. Llene todos los diferentes tipos de Collections con objetos y aplique su método a cada contenedor. Utilice Collections2.java. 28. Ahora agregue el código que prueba las diferencias de rendimiento entre HashMap y TreeMap en ese programa. Cree una clase. cree una clase Deque y pruébela. Intente esto con ambos tipos de Set. 22.java utilizando un HashSet de objetos Counter (tendrá que modificar Counter para que trabaje con HashSet). Escriba nuevamente Statics. luego cree un arreglo de objetos inicializado de su clase. insertando en cada una de las otras ubicaciones. cree un programa que ejecute la prueba repetidamente y busque si algún número tiende a aparecer mas que los otros en los resultados. Produzca un Map y un Set conteniendo todos los países que comienzan con ‘A’. 20. Siga el ejemplo Queue. Repare el problema en InfiniteRecursion. Use SlowMap. 26. y llene cada una utilizando el generador Collection2. 24. y luego elimine este subset de su List utilizando removeAll(). ¿Que estrategia parece mejor? Modifique la clase en el ejercicio 13 de tal forma que trabaje con HashSets y tenga una clave en HashMaps. llene un Set varias veces con los mismos datos y verifique que al final el Set tiene solo una instancia de cada uno.capitals. 23. 21.java. Use un TreeMap en Statistics.java.countries. luego inserte una lista dentro de la otra utilizando un ListIterator. Ahora realice la inserción comenzando desde el final del la primera lista y moviéndose hacia atrás. cree un SlowSet. 17.java. 25. Escriba un método que utilice un Iterator para ir a través de una Collection e imprima el hashCode() de cada objeto en el contenedor. 18. Cree una ArrayList y una LinkedList. 431 . Recuerde que una ArrayList almacena solo Objects así se debe realizar una conversión cuando se acceda a Rodents individuales. 27.16.

Siga el ejemplo en SimpleHashMap. doble el número de cubos. 36. Agregue un método privado rehash() a SimpleHashMap que sea invocado cuando el factor de carga exceda los 0. 432 Pensando en Java www.BruceEckel.java para comparar el rendimiento de las dos implementaciones.java para incluir pruebas de SlowMap. Modifique SimpleHashMap de tal forma que reporte colisiones. llénelo con elementos..sun. Modifique MapPerformance. Cree un HashMap. Utilice MapPerformance.java para SlowMap para verificar que trabaja. 33. almacene una solo ArrayList u objetos MPair. es cuantas llamadas a next() deben ser hechas en los Iterators que se mueven en los LinkedLists buscando coincidencias? Implemente los métodos clear() y remove() para SimpleHashMap.com . y modifique get() para utilizar Collections. pruebe la velocidad de su nuevo Map. busque la clase HashMap. 32. Pruebe la velocidad de búsqueda con este mapa. luego busque el primer número primo mayor que ese para determinar el nuevo número de cubos. Modifique MapPerformance. 37. ¿Esto. 34. Implemente el resto de la interfase Map para simpleHashMap. Modifique SimpleHashMap para utilizar ArrayList en lugar de LinkedLists.75. Aplique las pruebas en Map1. Utilice la documentación HTML para la JDK (que puede bajar de java. Compare el rendimiento de la nueva versión con la vieja. Agregue un campo char a CountedString que también sea inicializado en el constructor. Implemente el resto de la interfase Map para SlowMap. Ahora cambie el método put() de tal forma que realice una sort() luego que cada par es entrado.java. Modifique SimpleHashMap de tal forma que reporte el número de “pruebas” necesarias cuando una colisión se sucede.binarySearch() para buscar la clave. y pruébelo agregando el mismo grupo de datos dos veces para ver las colisiones. y determine el factor de carga. 41. 31. y modifique los métodos hashCode() y el equals() para incluir los valores de este char. 40. cree y pruebe un SimpleHashSet. Corrija cualquier cosa en SlowMap que no trabaje correctamente. 38. luego intente incrementar la velocidad haciendo un nuevo HashMap con una capacidad inicial mas grande y 30. Verifique que la versión modificada trabaja correctamente.com).29. 39. Modifique SlowMap de tal forma que en lugar de dos ArrayLists. Durante el rehashing. 35.java.

encuentre el ejemplo GreenhouseControls. Todo lo que invoque claves no debe utilizar tipos genéricos. En el capítulo 8. no Objetcts.java. la clase EventSet es solo un contenedor. ellos proporcionaran una forma de hacer este trabajo para usted automáticamente (de la misma forma que muchos otros beneficios).copiando el viejo mapa en el nuevo. (Desafío). 433 . Modifique MapPerformance. Encuentre el código fuente para un List en las librerías de código fuente de Java que vienen con las distribuciones. No herede de Map. 43. Copie este código y haga una versión especial llamada intList que almacene solo enteros. Escriba su propia clase map hashed. para evitar el costo de la conversión ascendente y descendente. 44. pero en lugar de eso deben trabajar con cadenas.java. Esto va a requerir mas que simplemente reemplazar EventSet con LinkedList. hecha a la medida para una clave de tipo particular: String para este ejemplo. Su meta es hacer una implementación hecha a la medida lo mas rápida posible. En Controller. ejecute la prueba de velocidad nuevamente en el nuevo mapa. que consiste en tres ficheros. necesitará también utilizar un Iterator para ir a través del grupo de eventos. 42. (Desafío). Considere que tomará para hacer una versión especial de List para todos los tipos primitivos.java para probar su implementación contra un HashMap. duplique los métodos de tal forma que los métodos put() y get() especialmente tomen objetos String. Si los tipos parametrizados son siempre implementados en Java. En lugar de eso. como claves. Cambie el código para utilizar una LinkedList en lugar de un EventSet. Ahora considere que sucede si quiere hacer una clase de lista enlazada que trabaje con todos los tipos primitivos.

com . se retorna un valor especial o un grupo de banderas. 434 Pensando en Java www. su código se vuelve una pesadilla ilegible. los errores le pueden suceder a otros. y ellas son generalmente establecidas por convención y no como parte del lenguaje de programación. robustos y fáciles de mantener.10: Manejo de errores con excepciones La filosofía básica en Java es “el código mal formado no correrá”. antes de que incluso trate de ejecutar el programa. Esto es actualmente una larga historia. Así es que. no sorprendentemente. el resto de los problemas debe ser manejado en tiempo de ejecución. y el receptor supuestamente observaba el valor de las banderas y determinaba que estaba mal. Sin embargo. En C y en otros lenguajes precoses. ellos no verificaran las condiciones de error (y a veces las condiciones de error son tan tontas de verificar1 ). pero no en mi código”. a través de alguna formalidad que permita que lo que origine el error pase la información adecuada a un receptor que sepa como manejar la dificultad adecuadamente. Pero el 1 El programador C puede buscar el valor de retorno de printf() para un ejemplo de esto. Las solución es tomar la naturaleza casual fuera del manejo de error y forzar la formalidad. Dado que los programadores pueden persuadir los sistemas con esos lenguajes son resistentes a admitir la verdad: Esta estrategia de manejar errores es la mayor limitación para crear programas grandes. Sin embargo. El momento ideal para atrapar un error es en tiempo de compilación. no todos los errores pueden ser detectados en tiempo de compilación. “Si. e incluso a el “on error goto” de BASIC. los años han pasado. Si se es suficientemente minucioso para verificar errores cada vez que se llama a un método. pueden haber muchas de esas formalidades. puesto que las implementaciones de manejo de excepciones regresan a los sistemas operativos de los 60s. y se ha descubierto que los programadores que utilizan una librería tienden a pensar que son invencibles -para adentro. Típicamente.BruceEckel.

Todo lo que se puede hacer es saltar fuera del contexto y relegar el problema a un contexto superior. Esto es importante de distinguir de una condición excepcional de un problema normal. Con una condición excepcional. y la forma en que se puede generar excepciones propias si un método esta en problemas. se debe parar y alguien. Y. La palabra “excepción” esta utilizada en el sentido de “tomo excepción de esto”. y depuración del código se hace mucho mas limpia con excepciones que utilizando las viejas formas de manejo de errores. En el punto en que el problema se sucede se puede no saber que hacer con el. hay solo unos tantos ejemplos que puedan ser escritos en este libro sin aprender acerca de manejo de excepciones. Esto es lo que sucede cuando lanza una excepción. Excepciones básicas Una condición excepcional es un problema que evita la continuación del método o el campo de acción donde se encuentra. Pero no se tiene suficiente información en el contexto actual para solucionar el problema. escritura. y separa el código que describe que es lo que quiere hacer del código que es ejecutado cuando las cosas van mal. Este capítulo introduce al código que se necesita escribir para manejar adecuadamente excepciones. no se puede continuar procesando porque no se tiene la información necesaria para tratar con el problema en el contexto actual. en algún lugar. En general. en el cual se tiene la información suficiente en el contexto actual para que alguien haga frente a la dificultad. la lectura. Esto saca adelante el código. Así es que se reparte el problema a un contexto mayor donde alguien está calificado para tomar la decisión apropiada (muy parecido a una cadena de comandos). el llamado manejador de excepciones. se necesita manejar el problema en solo un lugar. vale la pena verificar para asegurarse que no se va a realizar esa división. pero se sabe que no se puede continuar alegremente. Un ejemplo simple es una división. no se necesita verificar en el punto de la llamada al método (dado que la excepción garantiza que alguien la tiene que atrapar). debe entender que hacer. El otro beneficio significante de las excepciones que limpian el código de manejo de excepciones. Dado que el manejo de excepciones esta implementado por el compilador Java. y Java es basado primariamente en C++ (a pesar de que se ve mas como Pascal orientado a objetos).manejo de errores de C++ fue basado en Ada. Si se esta dividiendo y es posible hacerlo por cero. En lugar de verificar un error en particular y tratar con el en muchos lugares en su programa. ¿Pero que significa que el denominador sea cero? Tal ves se sepa. 435 .

se crean siempre excepciones en el heap utilizando new . y el segundo toma una cadena como argumento así es que se puede colocar información pertinente en la excepción: if(t == null) throw new NullPointerException("t = null"). como tratar con un denominador cero. if (t== null) throw new NullPointerException(). El objeto es. En este punto el mecanismo de excepción se encarga y comienza a buscar un lugar apropiado para continuar ejecutando el programa. en efecto. Esto es llamado lanzar una excepción Aquí vemos como se vería: .en el contexto del problema que se esta tratando de solucionar en este método en particular. Argumentos de excepciones Como un objeto en Java. considere una referencia a un objeto llamada t. Esto es simplemente mágicamente manejado por alguien mas. no se puede tratar con el y se debe lanzar una excepción en lugar de continuar adelante por ese camino. Primero. así es que se puede querer verificar antes de tratar de llamar un método utilizando esa referencia a objetos. Se puede enviar información acerca del error dentro de un gran contexto creando un objeto que represente su información y “lanzarla” fuera de su contexto. Cuando se lanza una excepción. Precisamente donde será mostrado en breve. con new . el objeto excepción es creado de la misma forma en que cualquier objeto Java es creado: en el heap. “retornado” del método. Típicamente. se utilizará primero new para crear un objeto que represente la condición de error. como se verá en breve. Una forma 436 Pensando en Java www. Es posible que pueda pasar una referencia que no fue inicializada. Hay dos constructores en todas las excepciones estándar: el primero es el constructor por defecto. Este lugar apropiado es el manejador de excepciones. La palabra clave throw causa que sucedan una gran cantidad de cosas mágicas. lo que permite -en el contexto actual. muchas cosas suceden.com . aún si el tipo de objeto no es normalmente el que el método esta diseñado a retornar. Esto lanza la excepción.BruceEckel.abdicar responsabilidades para pensar mas acerca de este tema. Se da la referencia resultante a throw . Esta cadena puede ser mas tarde extraída utilizando varios métodos. Pero si es un valor inesperado. quien asigna espacio y llama al constructor. cuyo trabajo es recuperarse del problema así el programa puede cambiar de dirección o simplemente continuar. Luego el actual camino de ejecución (el que no se puede continuar) es parado y la referencia al objeto excepción es expulsado del contexto actual. Como un ejemplo simple de lanzado de excepción.

y la cual esta seguida del código que maneja esas excepciones. se debe asumir que esa excepción es “capturada” y tratada.simplista de pensar en el manejo de excepciones es como un mecanismo alternativo de retorno. dado que donde retorna es un lugar completamente diferente de donde se retorna en una llamada a método normal (Se termina en un manejador de excepciones apropiado a millas -muchos niveles abajo de la llamada. así es que alguien en el gran contexto puede darse cuenta de que hacer con su excepción (A menudo. y el método o alcance sale. precedido por la palabra clave try . Una de las ventajas del manejo de excepciones es que permite concentrarse en los problemas que se esta tratando de resolver en un lugar. y nada significante es almacenado con el objeto de la excepción). La información acerca del error es representadas dentro del objeto de la excepción e implícitamente en el tipo de objeto de excepción elegido. Típicamente. y el trato con los errores de ese código en otro lugar. se puede lanzar una excepción de cualquier tipo de objeto Throwable que se quiera. Se puede también salir del ámbitos usuales lanzando una excepción. Además. Pero un valor es retornado. se lanza una clase diferente de excepción para cada diferente tipo de error. producir excepciones. Cualquier similitud con un retorno común de un método termina aquí.de donde la excepción fue lanzado). Capturando una excepción Si un método lanza una excepción. Si no se quiere que se salga del método lanzando una excepción se puede configurar un bloque especial dentro del método para capturar la excepción. try { // Código que puede generar excepciones } 437 . PAra ver como una excepción es capturada. esto es llamado el bloque try porque se “intenta” las distintas llamadas a métodos aquí. se debe entender primero el concepto de una región protegida que es una sección de código que puede . El bloque try es un ámbito común. ese método saldrá en el proceso de lanzado de excepción. El bloque try Si esta dentro de un método y se lanza una excepción (u otro método que se llama dentro de este método lanza una excepción). la única información es el tipo de objeto de excepción. a pesar de que se esta en problemas si lleva la analogía muy lejos.

y así sucesivamente) puede ser utilizado dentro del manejador. pero se necesita solo uno manejador. dentro del bloque try. Este “lugar” es el manejador de excepciones.. la excepción lanzada debe terminar en algún lugar. pero el identificador debe seguir estando allí. y hay uno para cada tipo de excepción que se quiera capturar.com . Con el manejo de excepciones. Luego entra en la cláusula catch. y la excepción es considerada manejada. Si una excepción es lanzada. el mecanismo de manejo de excepciones busca el primer manejador con un argumento que corresponda con el tipo de excepción. Los manejadores deben aparecer directamente luego del bloque try. Solo la cláusula catch que corresponde se ejecuta. no es como una instrucción switch en donde se necesita un break luego de cada case para evitar que el resto se ejecute. se coloca todo en un bloque try y se captura todas las excepciones en un solo lugar. un gran número de llamadas a métodos puede generar la misma excepción. La búsqueda por manejadores se detiene una vez que la cláusula es finalizada.BruceEckel. Los manejadores de excepción siguen inmediatamente al bloque try y son indicados por la palabra clave catch: try { // Code that might generate exceptions } catch(Type1 id1) { // Handle exceptions of Type1 } catch(Type2 id2) { // Handle exceptions of Type2 } catch(Type3 id3) { // Handle exceptions of Type3 } // etc. exactamente igual a un argumento de un método.. Debe notarse que. El identificador (id1 . Manejadores de error Claro. se tiene que rodear cada llamada a un método con configuraciones y código de prueba de errores.Si se va a verificar por errores cuidadosamente en un lenguaje de programación que no soporta manejo de excepciones. aún si se llama al mismo método muchas veces. Cada cláusula catch (manejador de excepción) es como un pequeño método que toma uno y solo un argumento de un tipo particular. 438 Pensando en Java www. Esto significa que su código es mucho mas fácil de escribir y mas fácil de leer porque la meta del código no es confundirse con las verificaciones de errores. id2. A veces nunca se utiliza este identificador porque el tipo de excepción le da la suficiente información para tratar con la excepción.

se llama a un método que arreglo el problema). la excepción tiene el comportamiento mas como una llamada a un método -que es lo que se tiene que configurar en situaciones en Java en las cuales se quiere reanudar (esto es. se puede colocar el bloque try dentro de un bucle while que mantiene el bloque try reingresando hasta que el resultado es satisfactorio. Para crear su propia clase de excepción. La principal razón es probablemente el acoplamiento que resulta: su manejador a menudo tiene cuidado de donde la excepción es lanzada y contiene código no genérico específico a la ubicación de lanzamiento. Si se quiere reanudar. Esto significa que el manejador de excepciones esta esperando hacer algo para rectificar la situación. se esta forzado a heredar de un tipo de excepción. Esto es importante porque a menudo se necesita crear excepciones propias para mostrar un error especial que su librería es capas de crear. Históricamente. La alternativa es llamada reanudación.Finalización contra reanudación Hay dos modelos básicos en la teoría de manejo de excepciones. Así es que a pesar de que la reanudación suena atractiva al principio. significa que se tiene la esperanza de que se pueda continuar la ejecución luego de que la excepción es manejada. y entonces el método culpable vuelve a intentarlo. suponiendo el éxito la segunda vez. los programadores utilizan sistemas operativos que soportan reanudación en el manejo de excepciones parándolo utilizando código como el de terminación y saltándose la reanudación. especialmente para sistemas grandes donde la excepción puede ser generada de muchos puntos. En este caso. Quienquiera que ha lanzado la excepción decide que no hay forma de salvar la situación. Alternativamente. no es tan útil en la práctica. sin embargo) . pero que no se previeron cuando la jerarquía de excepciones de Java fue creada. Creando excepciones propias No se esta atascado utilizando las excepciones existentes de Java. En la finalización (que es lo que Java y C++ soportan). y no quiere regresar. La forma mas trivial de crear un nuevo tipo de excepción es simplemente indicarle al 439 . se asume que el error es tan crítico que no hay forma de retornar a donde la excepción ha ocurrido. no se lanza una excepción. preferiblemente una que este cercana al significado de la nueva excepción (esto a menudo no es posible. Esto hace el código difícil de escribir y de mantener.

automáticamente (y de forma transparente) llama el constructor de la clase base. try { sed. } public static void main(String[] args) { SimpleExceptionDemo sed = new SimpleExceptionDemo(). Si se envía la salida a System. } } public class FullConstructors { public static void f() throws MyException { System. así es que la mayoría del tiempo una excepción como la mostrada arriba es satisfactoria. class SimpleException extends Exception {} public class SimpleExceptionDemo { public void f() throws SimpleException { System. throw new SimpleException ().com . Esto es usualmente mejor lugar para enviar información acerca de errores que System.err no puede ser redirigido junto con System. Aquí.println( "Lanzando MyException de f()").out. en este caso no se tiene un constructor para SimpleException(String).out.java // Heredando sus propias excepción.BruceEckel. así es que se requiere al menos nada de código después de todo: //: c10:SimpleExceptionDemo. el cual puede ser redirigido. Creando una clase de excepción que solo tenga un constructor que tome un String es también bastante simple: //: c10:FullConstructors. throw new MyException(). } public static void g() throws MyException { 440 Pensando en Java www.out.out así es que es mas probable que el usuario tenga aviso de el.err. pero en la práctica no es muy utilizado. la cosa mas importante acerca de las excepciones es el nombre de clases. } catch(SimpleException e) { System.compilador que cree un constructor por defecto para usted. } } } ///:~ Cuando el compilador crea un constructor por defecto.println( "LAnzando una excepción simle de f()").f().java // Heredando sus propias excepciones. Como se vera. el resultado es impreso a la consola de error estándarmediante System.println("Capturandola!"). Claro.err. class MyException extends Exception { public MyException() {} public MyException(String msg) { super(msg).

} public static void main(String[] args) { try { f().java:24) Throwing MyException from g() MyException: Originated in g() at FullConstructors.printStackTrace(System.g(FullConstructors.println( "Lanzando MyException de g()"). El proceso de creación de excepciones propias puede ir mas allá aún. La salida del programa es: Throwing MyException from f() MyException at FullConstructors. } catch(MyException e) { e.err así es que es mas probable que sea notificado en el evento que en System.System.main(FullConstructors. } public int val() { return i.main(FullConstructors. i = x.err). } 441 . } try { g(). La información de trazado de pila es enviada a System.out. class MyException2 extends Exception { public MyException2() {} public MyException2(String msg) { super(msg).f(FullConstructors.java:29) Se puede ver la ausencia del mensaje de detalle en MyException lanzado desde f().out redirigido.java:20) at FullConstructors. } } } ///:~ El código agregado es pequeño -el agregado de dos constructores que definen la forma en que MyException es creada.java // Further embellishment of exception classes. el constructor de la clase base con un argumento String es explícitamente invocado utilizando la palabra clave super. int x) { super(msg). Se puede agregar constructores extra y miembros: //: c10:ExtraFeatures. } private int i. } catch(MyException e) { e.printStackTrace(System. } public MyException2(String msg. throw new MyException("Originada en g()").java:16) at FullConstructors.err). En el segundo constructor.

err).java:39) Throwing MyException2 from h() MyException2: Originated in h() at ExtraFeatures. } public static void h() throws MyException2 { System.main(ExtraFeatures.err. } catch(MyException2 e) { e. } catch(MyException2 e) { e. } catch(MyException2 e) { e.out.println("e.println( "Throwing MyException2 from g()").val() = " + e. Tenga 442 Pensando en Java www.println( "Throwing MyException2 from h()").printStackTrace(System.printStackTrace(System.val()). } public static void g() throws MyException2 { System.main(ExtraFeatures.val() = 47 Dado que una excepción es solo otro tipo de objeto. La salida es: Throwing MyException2 from f() MyException2 at ExtraFeatures. System.out. throw new MyException2("Originated in g()").BruceEckel.printStackTrace(System. } } } ///:~ Todos los datos miembros i han sido agregados.com .java:34) Throwing MyException2 from g() MyException2: Originated in g() at ExtraFeatures. } try { g().java:44) e. throw new MyException2( "Originated in h()". 47).err).public class ExtraFeatures { public static void f() throws MyException2 { System.java:30) at ExtraFeatures.g(ExtraFeatures.println( "Throwing MyException2 from f()"). throw new MyException2().f(ExtraFeatures.h(ExtraFeatures. junto con un método que lee el valor y un constructor adicional que lo configura.err).main(ExtraFeatures. } try { h().out.java:22) at ExtraFeatures. } public static void main(String[] args) { try { f(). se puede continuar este proceso de embellecimiento del poder de sus clases de excepciones.java:26) at ExtraFeatures.

. si el código fuente esta disponible. que toda estas vestiduras pueden ser perdida en el programa cliente que utiliza los paquetes. void f() throws TooBig. y es parte de la declaración de métodos. TooSmall. el cliente programador puede buscar y encontrar instrucciones throw . Haciendo cumplir la especificación de excepciones del principio al fin. 2 Esto es una mejora significante sobre el manejo de excepciones de C++. Esto es civilizado. se requiere que se informe al cliente programador. DivZero { //. sin embargo. Claro.en mente. que pueden razonablemente ser lanzadas en cualquier lado -esto será descrito mas tarde.. el compilador las detectara y e indicara que se debe manejar o indicar con una especificación de excepciones que pueden ser lanzadas por el método.. pero a menudo las librerías no vienen con el código fuente. Esto es la especificación de excepciones. así el cliente programador puede manejarlas.. La especificación de excepciones utiliza una palabra clave adicional. dado que la persona que llama puede saber exactamente que código escribir para capturar todas las potenciales excepciones. que llama a el método. significa que no hay excepciones que serán lanzadas desde el método (Excepto por las excepciones del tipo RuntimeException.) No se puede mentir acerca de la especificación de excepciones -si el método causa excepciones y no se manejan. Así es que su definición de método puede verse como esto. dado que simplemente puede considerar la excepción a ser lanzada y nada mas (Esto es la forma en que la mayoría de las excepciones de Java son utilizadas). Si se dice void f() { // . Java proporciona la sintaxis (y fuerza a utilizar esa sintaxis) para permitir que políticamente indique al cliente programador que excepciones lanza este método. seguida de una lista de todos los tipos potenciales de excepción. 443 . Java garantiza que la exactitud de excepciones puede ser asegurada en tiempo de compilación2. de las excepciones que pueden ser lanzadas desde el método. que no captura las violaciones de especificaciones de excepciones hasta el tiempo de ejecución. Para prevenir esto de ser un problema. throws. que se hace presente luego de la lista de argumentos. Especificación de excepciones En Java. donde no es muy útil.

println("Captura una excepción"). String toString() Retorna una corta descripción del Throwable. o un mensaje ajustado para este escenario particular. se entenderá por que hay dos tipos de flujo). A causa de que la clase Exception es la base para todas las clases de excepción que son importantes para el programador. El compilador toma la información. Esto es también importante para la creación de clases base abstractas e interfases cuyas clases derivadas o implementaciones pueden necesitar lanzar excepciones.com . se podrá poner al final de su lista de manejadores para evitar la atribución de cualquier manejador de excepciones que puedan de otra forma seguirlo. } Esto capturará cualquier excepción. pero se puede llamar los métodos que vienen de su tipo base Throwable: String getMessage() String getLocalizedMessage() Obtiene el detalle del mensaje. Capturando cualquier excepción Es posible crear un manejador que capture cualquier tipo de excepción. incluyendo el mensaje de detalle si hay uno. La llamadas de pila muestra la secuencia de las llamadas a métodos que nos traen a el punto en que la excepción fue lanzada. pero Exception es la base que es aplicable a virtualmente todas las actividades de programación): catch(Exception e) { System. 444 Pensando en Java www. la segunda y tercera imprime a un flujo de su elección (en el Capítulo 11. no se tiene mucha información específica acerca de la excepción.err. Esto tiene el beneficio de que se tiene un lugar para esa excepción. Se realiza esto capturando el tipo base la excepción Exception (hay otros tipos de excepciones base. y fuerza a l usuario de su método a tratarla como si realmente se lanzara la excepción. void printStackTrace() void printStackTrace(PrintStream) void printStackTrace(PrintWriter) Imprime el Throwable y la traza de las llamadas de pila Throwable. así es que si se usa.Hay un lugar donde se puede mentir: se puede reclamar el lanzar una excepción que realmente no se tiene. así es que se puede comenzar a lanzar la excepción mas tarde sin requerir cambios en el código existente.BruceEckel. La primer versión imprime a la salida de error estándar.

getLocalizedMessage(): Here's my Exception e.lang. Útil cuando una aplicación vuelve a lanzar un error o excepción (mas acerca de esta historia).Exception: Here's my Exception at ExceptionMethods.java:7) java. 445 .main(ExceptionMethods.getMessage()).println( "e.toString(): java.getLocalizedMessage(): " + e.println( "e.Exception: Here's my Exception e.java:7) Se puede ver que el método proporciona sucesivamente mas información cada ves es efectivamente un super grupo del anterior. System.java // Demonstrating the Exception Methods. System. System.toString(): " + e).println("e. public class ExceptionMethods { public static void main(String[] args) { try { throw new Exception("Here's my Exception").Exception: Here's my Exception at ExceptionMethods.err.lang.getMessage(): Here's my Exception e. El único que se hace cómodo para las excepciones es getClass().println("Caught Exception"). Se puede a su vez indagar a este objeto Class por su nombre con getName() o toString().Throwable fillInStackTrace() Registra información dentro de este objeto Throwable acerca de el estado actual de la trama de la pila.printStackTrace(System.err.getLocalizedMessage()).lang.printStackTrace():").err). Los objetos Class serán estudiados mas adelante en este libro. se tienen algunos otros métodos del objeto base del tipo Throwable (el tipo base de todo el mundo).main(ExceptionMethods.err. } catch(Exception e) { System. } } } ///:~ La salida para este programa es: Caught Exception e. System. También se pueden hacer cosas mas sofisticadas con objetos Class que no son necesarias para el manejo de excepciones. que retorna un objeto representando la clase de este objeto.getMessage(): " + e. e. He aquí un ejemplo que muestra el uso de estos métodos básicos de Exception: //: c10:ExceptionMethods.printStackTrace(): java.err.err.println("e. Además.

así es que el manejador en el contexto mas alto que captura la excepción especificada puede extraer toda la información de ese objeto. todo acerca del objeto de la excepción es preservado.Lanzando nuevamente una excepción A veces se quiere volver a lanzar una excepción que simplemente se capturó. se puede hacer llamando a fillInStackTrace(). el que retornara un objeto de excepción que es creado rellenando la información actual de la pila dentro del objeto de excepción anterior. Si se quiere instalar nueva información de trazado de pila. Si simplemente se vuelve a lanzar la excepción actual.printStackTrace()"). } catch(Exception e) { System. } Volver a lanzar una excepción causa que la excepción vaya a el manejador de excepciones en el siguiente nivel superior del contexto. throw e. } public static void g() throws Throwable { try { f().printStackTrace(System. e. 446 Pensando en Java www. e.err). simplemente se puede volver a lanzar esa referencia: catch(Exception e) { System.fillInStackTrace(). Dado que ya se tiene la referencia a la excepción actual. particularmente cuando se utiliza Excepción para capturar cualquier excepción.java // Demonstrating fillInStackTrace() public class Rethrowing { public static void f() throws Exception { System. Cualquier catch superior para el mismo bloque try es ignorada.com .err. Además.println( "originating the exception in f()").err.println( "Inside g(). // 17 // throw e. throw new Exception("thrown from f()"). no a el lugar donde se volvió a lanzar. Aquí vemos como se vería: //: c10:Rethrowing. throw e.out. // 18 } } public static void main(String[] args) throws Throwable { try { g().println("Una excepción fué lanzada ").BruceEckel. la información que se imprime acerca de esa excepción en printStackTrace() pertenecerá a la excepción original.

e.f(Rethrowing.printStackTrace()"). e. } } } ///:~ Los números de línea importantes son marcados como comentarios.java:24) Así es que el trazado de pila de la excepción siempre recuerda su verdadero punto de origen.printStackTrace(System. Para asegurarse todo en este orden.lang.err.g(Rethrowing. La clase Throwable debe aparecer en la especificación de la excepción para g() y main() porque fillInStackTrace() produce una referencia a un objeto Throwable.Exception: thrown from f() at Rethrowing. así es que el manejador para Exception en main() puede perderse. la línea 18 se convierte en el punto de origen de la excepción. la excepción en el siguiente programa no es capturada en main(): //: c10:ThrowOut.java:8) at Rethrowing.lang.Exception: thrown from f() at Rethrowing. Con la línea 17 comentada y la línea 18 sin comentar.printStackTrace() java. e.java public class ThrowOut { public static void main(String[] args) throws Throwable { 447 .java:12) at Rethrowing.g(Rethrowing. es posible obtener un objeto que es un Throwable pero no una Exception. Por ejemplo.f(Rethrowing.g(Rethrowing.} catch(Exception e) { System.java:24) Caught in main.java:8) at Rethrowing.printStackTrace() java.printStackTrace() java.f(Rethrowing. no importa cuantas veces es vuelta a lanzar.main(Rethrowing.lang.println( "Caught in main. e. y el resultado es: originating the exception in f() Inside g().err).java:12) at Rethrowing.g(Rethrowing.java:18) at Rethrowing.java:24) Puesto que por fillInStackTrace(). la salida es: originating the exception in f() Inside g(). Con la línea 17 sin comentar (como se muestra). Dado que Throwable es una clase base de Exception.Exception: thrown from f() at Rethrowing. el compilador fuerza a una especificación de excepción para Throwable.java:24) Caught in main. fillInStackTrace() es utilizado en su lugar.main(Rethrowing.Exception: thrown from f() at Rethrowing. e.main(Rethrowing.main(Rethrowing.java:12) at Rethrowing. e.lang.printStackTrace() java.java:8) at Rethrowing.

} } class TwoException extends Exception { public TwoException(String s) { super(s).main(RethrowNew.java:17) at RethrowNew. } public static void main(String[] args) throws TwoException { try { f().println("Caught in main()").java:22) Exception in thread "main" TwoException: from main() at RethrowNew. } } public class RethrowNew { public static void f() throws OneException { System.try { throw new Throwable(). e. } catch(OneException e) { System.err). Si se hace esto.err. así es que el recolector de basura automáticamente los limpia a todos ellos. y lo que queda es la información pertinente a el nuevo throw : //: c10:RethrowNew. } } } ///:~ Es posible también volver a lanzar una excepción diferente de la que se capturó.java:27) La excepción final solo conoce lo que viene del main(). throw new OneException("thrown from f()").main(RethrowNew.com . } catch(Exception e) { System. 448 Pensando en Java www.printStackTrace()"). y no del f().java // Rethrow a different object // from the one that was caught. throw new TwoException("from main()").BruceEckel.out. se tiene un efecto similar que cuando se utiliza fillInStackTrace() -la información acerca del sitio original de la excepción se pierde.f(RethrowNew. Están todos los objetos basados en el heap creados con new . } } } ///:~ La salida es: originating the exception in f() Caught in main.printStackTrace() OneException: thrown from f() at RethrowNew.err.println( "originating the exception in f()"). class OneException extends Exception { public OneException(String s) { super(s).printStackTrace(System. e. e.println( "Caught in main. No hay que preocuparse nunca acerca de limpiar la excepción previa. o cualquier excepción si vamos a eso.

Así es que el tipo base de interés para el programador Java es Exception.com. Hay dos tipos generales de objetos Throwable (“tipos de” = “heredados de”).io. básicamente no tiene sentido imprimirlas en un libro. La mejor forma de tener una visión general de las excepciones es buscar la documentación Java que se puede bajar desde java. Error representa errores en tiempo de compilación y errores de sistema de los cuales no hay que preocuparse por capturar (excepto en casos especiales).sun. Las excepciones no están definidas todas en java. Lo importante de entender es el concepto y que se puede hacer con excepciones. no se necesita -esto es parte de las pruebas en tiempo de ejecución que Java realiza por usted. Puede ser un poco horripilante pensar que se debe verificar por null en cada referencia que es pasada a un método (dado que no se puede conocer si el que llama el método ha pasado una referencia válida). Afortunadamente. todas las excepciones E/S son heredadas de java. net.IOException. el número de excepciones en Java se sigue expandiendo. y si cualquier llamada es hecha a una referencia null.Excepciones estándar de Java La clase de Java Throwable describe todo lo que puede ser lanzado como una excepción. desde los métodos propios y en accidentes en tiempo de ejecución. Cada nueva librería que se obtenga de un tercero probablemente tenga sus propias excepciones también. y el nombre de excepción pretende ser relativamente auto explicativa. Por ejemplo. La idea básica es que el nombre de la excepción represente el problema que ha ocurrido.lang. y io. Tiene mérito hacer esto una vez solo para obtener un sentido de las diferentes excepciones. Java 449 . También. pero rápidamente verá que no hay nada especial entre una excepción y la siguiente excepto el nombre. lo que se puede ver de sus nombres de clases completos o de donde son heredadas. Exception es el tipo básico que puede ser lanzado desde cualquiera de los métodos de la librería de clases estándar de Java. algunas son creadas para apoyar a otras librerías como util. El caso especial de RuntimeException El primer ejemplo en este capítulo fue if(t == null) throw new NullPointerException().

virtualmente nunca se captura una RuntimeException -se trata automáticamente.java:12) at NeverCaught. intente con el siguiente ejemplo: //: c10:NeverCaught.BruceEckel.f(NeverCaught. } static void g() { f(). printStackTrace() es llamado para esa excepción así como se sale del programa. } } ///:~ Ya se puede ver que una excepción RuntimeException (o cualquiera heredada de esta) es un caso especial. Así es que el pedacito de código arriba es siempre superfluo. Si se esta forzado a verificar excepciones RuntimeException su código puede volverse desordenado.g(NeverCaught.main(NeverCaught. en paquetes propios se debe elegir lanzar algunas de las RuntimeException.java // Ignoring RuntimeExceptions. } public static void main(String[] args) { g(). Dado que indican errores de programación. public class NeverCaught { static void f() { throw new RuntimeException("From f()").java:15) Así es que la respuesta es: Si una excepción RuntimeException se obtiene de todas formas fuera del main() sin ser capturada. Hay un conjunto completo de tipos de excepciones que están en esta categoría. 450 Pensando en Java www.RuntimeException: From f() at NeverCaught. Aún cuando no se capturen típicamente excepciones RuntimeException. nunca se necesita escribir una especificación de excepción a no ser que un método pueda lanzar una RuntimeException. están agrupadas juntas en una sola clase base llamada RuntimeException. La salida es: Exception in thread "main" java. ¿Que sucede cuando no se capturan estas excepciones? Dado que el compilador no fuerza la especificación de estas.java:9) at NeverCaught.automáticamente lanzará una NullPointerException. dado que el compilador no requiere una especificación de excepción para estos tipos. que es un ejemplo perfecto de herencia: esta establece una familia de tipos que tienen algunas características y comportamientos en común.lang. De forma muy adecuada. Para ver que sucede en este caso. También. Estas siempre se lanzan automáticamente por Java y no se necesita incluirlas en la especificación de excepción. puesto que simplemente es asumido. es bastante plausible que una excepción RuntimeException pueda filtrarse de todas formas fuera de su método main() sin ser capturada.com .

Realizando una limpieza con finally A menudo alguna pieza de código que se quiere ejecutar se lance o no una excepción dentro de un bloque try . Un error no se puede capturar (recibir una referencia nula manejada por un método por el cliente programador. Es interesante notar que no se puede clasificar el manejo de excepciones en Java como una herramienta con un solo propósito. Para alcanzar este efecto.Se tiene que tener en mente que solo se puede ignorar excepciones RuntimeException en su código. debe ser verificado en el código (como un ArrayOutOfBoundsException donde se debe prestar atención al tamaño del arreglo). dado que ayuda en el proceso de depuración. se utiliza una cláusula 3 finally en el final de un manejador de excepciones. Se puede ver que tremendo beneficio es tener excepciones en este caso. Un error que. Esto es usualmente se aplica a algunas operaciones aparte de las de recuperación de memoria (dado que se esta siendo cuidadoso con el recolector de basura). esta diseñada para manejar estos molestos errores en tiempo de ejecución que producirán una salida del control del código. La imagen completa de una sección de manejo de excepciones es de esta forma: try { // La región protegida: Actividades peligrosas // que pueden lanzar A. por ejemplo). como programador. El razonamiento es que una excepción RuntimeException representa un error de programación: 1. 451 . o C } catch(A a1) { // Manejador para la situación A } catch(B b1) { // Manejador para la situación B } catch(C c1) { // Manejador para la situación C } finally { // Actividades que deben suceder todas las veces } 3 El manejo de excepciones de C++ no tiene la cláusula finally porque confia en los destructores para lograr este tipo de limpieza. B. dado que todos los demás manejos son forzados cuidadosamente por el compilador. Si. 2. pero esto es algo esencial para ciertos tipos de errores de programación que el compilador no puede detectar.

// out of "while" } } } } ///:~ Este programa también sugiere como se puede tratar con el hecho de que las excepciones en Java (como las excepciones en C++) no permiten reanudar donde la excepción fue lanzada. se puede intentar con este programa: //: c10:FinallyWorks.err.println("In finally clause").out. class ThreeException extends Exception {} public class FinallyWorks { static int count = 0.java // The finally clause is always executed.BruceEckel.println("No exception"). finally es importante porque permite que el programador garantice la liberación de memora sin importar que sucede en el bloque try . ¿Para que es finally? En un lenguaje sin recolección de basura y sin llamadas automáticas a destructores4. se puede establecer una condición que pueda ser encontrada antes de continuar con el programa. public static void main(String[] args) { while(true) { try { // Post-increment is zero first time: if(count++ == 0) throw new ThreeException(). Se debe saber exactamente donde y cuando el destructor es llamado.err. como se planteó antes. } finally { System. la cláusula finally es siempre ejecutada. 452 Pensando en Java www. C++ tiene llamadas automáticas a destructores.com . pero las versiones Delphi de Pascal orientado a objetos 1 y 2 no (lo que cambia el significado y uso del concepto de destructor para ese lenguaje).println("ThreeException"). Se puede también agregar un contador estático o algún otro dispositivo para permitir que el bucle intente con diferentes estrategias antes de abandonar. De esta forma se puede crear un gran nivel de robustez dentro de los programas. } catch(ThreeException e) { System. La salida es: ThreeException In finally clause No exception In finally clause Sea o no lanzada una excepción.Para demostrar que la cláusula finally siempre se ejecuta. Si se coloca el bloque try en un bucle. System. 4 Un destructor es una función que siempre es llamada cuando un objeto no se utiliza mas. if(count == 2) break.

sw. Sin embargo. a veces se dibuja en la pantalla. OnOffException2 {} public static void main(String[] args) { try { sw..err. ¿Así es que cuando se necesita utilizar finally en Java? finally es necesaria cuando se necesita ajustar alguna otra cosa que volver la memoria a su estado original. como se modela en el siguiente ejemplo: //: c10:OnOffSwitch.println("OnOffException2"). o incluso se intercambia en el mundo exterior.. } void on() { state = true.Pero Java tiene un recolector de basura. sw. f().on(). } catch(OnOffException1 e) { System. boolean read() { return state. así es que la liberación de memoria nunca es realmente un problema. public class WithFinally { static Switch sw = new Switch(). Esto es algún tipo de limpieza como un fichero abierto o una conexión a redes.err. 453 . no tiene destructores a llamar. También.off().off() se coloca en el final del bloque try y al final de cada manejador de excepción.java // Finally Guarantees cleanup. } } } ///:~ El objetivo aquí es asegurarse que la llave esté apagada cuando haya terminado main(). } } class OnOffException1 extends Exception {} class OnOffException2 extends Exception {} public class OnOffSwitch { static Switch sw = new Switch().off().off() sea echada de menos.println("OnOffException1"). // Code that can throw exceptions. Pero es posible que una excepción pueda ser lanzada que no sea capturada aquí. public static void main(String[] args) { try { sw.java // ¿Por que utilizar finally? class Switch { boolean state = false. } catch(OnOffException2 e) { System. así es que sw. //: c10:WithFinally.off(). sw. static void f() throws OnOffException1. con finally se puede colocar código de limpieza en un bloque try en solo un lugar. así es que sw. } void off() { state = false.on().

println( "Entering first try block"). } finally { System.println("OnOffException2").f().off() ha sido movido a solamente un lugar. Note que.off(). } finally { sw.println( "finally in 1st try block"). junto con el 454 Pensando en Java www. } catch(OnOffException1 e) { System. try { throw new FourException().err.out.err.err.println( "Entering second try block").. finally será ejecutado antes que el mecanismo de excepciones continúe su búsqueda por un manejador en el siguiente nivel mas alto: //: c10:AlwaysFinally.err. OnOffSwitch.println( "Caught FourException in 1st try block").com .java // Finally is always executed. Incluso en casos en donde la excepción no es capturada en el grupo actual de cláusulas catch.out. } } catch(FourException e) { System. } } } ///:~ Aquí el sw. } finally { System. try { System.out. } catch(OnOffException2 e) { System. donde está garantido que se ejecute sin importar que suceda..println("OnOffException1").BruceEckel. } } } ///:~ La salida para este programa muestra que sucede: Entering first try block Entering second try block finally in 2nd try block Caught FourException in 1st try block finally in 1st try block La instrucción finally también será ejecutada en situaciones en donde las instrucciones break y continue están involucradas.// Code that can throw exceptions.println( "finally in 2nd try block"). class FourException extends Exception {} public class AlwaysFinally { public static void main(String[] args) { System.

java:29) Se puede ver que no hay evidencia de VeryImportantException. la implementación de excepciones de Java es bastante excepcional.break etiquetado y continue etiquetado.dispose(). Esto es una falla seria. try { lm. } finally { lm.main(LostMessage. Esto sucede con una configuración particular utilizando una cláusula finally : //: c10:LostMessage. } public static void main(String[] args) throws Exception { LostMessage lm = new LostMessage(). } } public class LostMessage { void f() throws VeryImportantException { throw new VeryImportantException(). class VeryImportantException extends Exception { public String toString() { return "A very important exception!". dado que significa que una excepción puede ser completamente perdida. } } class HoHumException extends Exception { public String toString() { return "A trivial exception".f().java // How an exception can be lost. la que es simplemente reemplazada por HoHumException en la cláusula finally. } void dispose() throws HoHumException { throw new HoHumException(). C++ trata la situación en donde una segunda 455 .dispose(LostMessage. } } } ///:~ La salida es: Exception in thread "main" A trivial exception at LostMessage. A pesar de que las excepciones son una indicación de una crisis en un programa y nunca deben ser ignoradas. es posible para una excepción simplemente ser perdida. finally elimina la necesidad de una instrucción goto en Java. En contraste. Dificultad: la excepción perdida En general. pero desafortunadamente hay un desperfecto. y de una forma mas sutil y difícil de detectar que el ejemplo anterior.java:21) at LostMessage.

Esto es una restricción útil. incluyendo excepciones. class BaseballException extends Exception {} class Foul extends BaseballException {} class Strike extends BaseballException {} abstract class Inning { Inning() throws BaseballException {} void event () throws BaseballException { // No tiene actualmente que lanzar nada } abstract void atBat() throws Strike.excepción es lanzada antes que la primera es manejado como un calamitoso error de programación. void rainHard() throws RainedOut. como un dispose(). pero se debe tratar // con las excepciones de los constructores // de la clase base: StormyInning() throws RainedOut.com . normalmente se puede envolver cualquier método que lance una excepción. se puede lanzar solo las excepciones que han sido especificadas en la versión del método en la clase base. Foul. BaseballException {} 456 Pensando en Java www. Restricciones en la excepción Cuando se sobrescribe un método. dentro de una cláusula try -catch). void walk() {} // No lanza nada } class StormException extends Exception {} class RainedOut extends StormException {} class PopFoul extends Foul {} interface Storm { void event() throws RainedOut. } public class StormyInning extends Inning implements Storm { // Se pueden agregar nuevas excepciones para // constructores. Tal vez una futura versión de Java reparará este problema (por el otro lado.BruceEckel. dado que significa que el código que trabaja con la clase base automáticamente trabajará con cualquier objeto derivado de la clase base (un concepto fundamental de la POO. claro). Este ejemplo demuestra el tipo de restricciones impuesta (en tiempo de compilación) para las excepciones: //: c10:StormyInning. o excepciones derivadas de // la clase base de excepciones.java // Los métodos sobrecargados pueden lanzar // solo exceptiones especificadas en sus versiones // de la clase base.

err. } catch(PopFoul e) { System.atBat().atBat(). // Se deben capturar excepciones de la version de la // clase base del método: } catch(Strike e) { System. Ambos métodos 457 .println( "Generic baseball exception"). la excepción esta bien: public void rainHard() throws RainedOut {} // Se puede elegir no lanzar ninguna excepción.err.err. try { // Que sucede si se realiza una conversión ascendente? Inning i = new StormyInning(). como se ve en atBat(). } } } ///:~ En inning. y un método que no lo esta. pero nunca lo hacen. BaseballException {} // Metodos regulares deben asemejarse a la clase base: //! void walk() throws PopFoul {} //Error de compilación // La interfsce NO PUEDE agregar excepciones a los métodos // existentes de la clase base: //! public void event() throws RainedOut {} // Si el método no existe ya en la clase // base.println("Generic error").println("Strike"). } catch(BaseballException e) { System. } catch(RainedOut e) { System. i.println("Rained out"). La misma idea se mantiene para métodos abstractos. } catch(RainedOut e) { System. se puede ver que el constructor y el método event() expresan que lanzarán una excepción.println("Pop foul"). si.err.println("Foul"). } // Strike no lanza en la versión derivada.err. Esto es legal porque permite que se fuerce al usuario a atrapar cualquier excepción que pueda ser agregada en versiones sobrescritas de event(). } catch(BaseballException e) { System. // aún si la clase base lo hace: public void event() {} // Los métodos sobrescritos pueden lanzar // excepciones heredadas: void atBat() throws PopFoul {} public static void main(String[] args) { try { StormyInning si = new StormyInning().err.println("Rained out").StormyInning(String s) throws Foul.err. } catch(Foul e) { System. La interfase de Storn es interesante porque contiene un método (event()) que esta definido en Inning.

como lo es rainHard(). donde Inning. una excepción que es derivada de Foul lanzada por la versión de la clase base de atBat(). Cuando StromyInning extends Inning e implements Strom . Se puede ver en StormyInning que un constructor puede lanzar cualquier cosa que se quiera. si un método descrito en una interfase no está en la clase base. pero si se realiza una conversión ascendente a el tipo de la clase base entonces el compilador (correctamente) fuerza a capturar las excepciones para el tipo 458 Pensando en Java www. el compilador fuerza a atrapar solo las excepciones que son especificas de esa clase. que lanza PopFoul. El ejemplo sobrescrito event() muestra que la versión de la clase derivada de un método puede elegir no lanzar ninguna excepción. pero cuando se substituye un objeto de la clase derivada de Inning. Esta forma. Nuevamente. Claro. esto esta bien dado que no quiebra ningún código que sea escrito -asumiendo que la versión de la clase base lanza excepciones. Debe notarse que un constructor de una clase derivada no puede capturar excepciones lanzadas por el constructor de su clase base. Aquí se puede ver que si se está tratando con objetos StormyInning y no otros. RaindedOut.walk() no compilara es que lanza una excepción. entonces se puede escribir código que llama a Inning. Dado que PopFoul es derivada de Foul. Una lógica similar se aplica a atBat().lanzan un nuevo tipo de excepción. estas deben capturar la excepción Foul.walk() y que no tenga que manejar ninguna excepción. esto tiene sentido porque de otra forma nunca se sabe si se esta capturando lo correcto cuando se esta trabajando con la clase base. el manejador de excepciones capturará también PopFoul. se podrá ver que el método event() en Strom no puede cambiar la interfase de event() en Inning. Si esto fuera permitido. si alguien escribe código que trabaje con Inning y llamadas a atBat(). las excepciones pueden ser lanzadas así es que su código se puede quebrar. Forzando los métodos de la clase derivada para amoldarse a las especificaciones de los métodos de la clase base. dado que un constructor de la clase base debe siempre ser llamado de una forma u otra (aquí.BruceEckel.com . la capacidad para sustituir los objetos es mantenida. sin importar de que lanza el constructor de la clase base. La razón por la cual StormyInning. el constructor de la clase derivada debe declarar cualquier excepción en el constructor de la clase base en su especificación de excepciones. Sin embargo. el constructor por defecto es llamado automáticamente). La restricción de las excepciones no se aplica a los constructores. entonces no hay problema si lanza una excepción. Nuevamente. El último punto de interés se encuentra en el main().walk() no lo hace. aún si la versión de la clase base lo hace.

Dado que solamente se ha aprendido acerca de finally . 459 . Por lo tanto. Es útil darse cuenta que a pesar de que las especificaciones de excepciones son implementadas por el compilador durante la herencia. es particularmente importante que se siempre se pregunte.base. o derivados de. Esto es un caso en donde C++ actualmente es capaz de verificar especificaciones de excepciones en tiempo de compilación. dado que finally realiza la limpieza del código todo el tiempo. Si se lanza una excepción dentro de un constructor. Poniéndolo de otra forma. Pero no es tan simple. Esto significa que se debe ser especialmente diligente cuando se escriba su constructor. donde un método en la clase base debe también existir en la clase derivada. pero esto puede ampliarse -esto es precisamente el opuesto a la regla de la interfase de clases durante la herencia. Esto es muy diferente de las reglas de herencia. si se realiza la limpieza en finally se debe configurar algún tipo de bandera cuando el constructor finalice correctamente y de esta forma no hacer nada en el bloque finally si la bandera esta configurada. no se puede sobrescribir métodos basados en especificaciones de excepciones. este comportamiento de limpieza puede no suceder correctamente. será propiamente limpiada?”. lo que abarca solo en nombre del método y el tipo de argumento. La mayoría del tiempo se esta bastante seguro. Dado que no es particularmente elegante (se esta asociando el código de un lugar a otro). El constructor coloca el objeto dentro de un estado seguro de arranque. las especificaciones de excepciones no son parte del tipo en un método. “¿Si una excepción ocurre. Además solo porque una especificación de excepciones existe en una versión de la clase base de un método no significa que debe existir en la versión del método para la clase derivada. pero puede realizar algunas operaciones -como abrir un fichero.que no son limpiadas hasta que el usuario ha terminando con el objeto y llama un método de limpieza especial. Constructores Cuando se escribe un código con excepciones. se puede pensar que esta es la solución correcta. aún en situaciones en donde no se quiere ejecutar código de limpieza hasta que el método de limpieza se ejecute. la “interfase de especificación de excepciones” par un método en particular puede estrecharse durante la herencia y cuando se sobrescribe. 5 ISO C++ agrega limitaciones similares que requieren excepciones en métodos derivados para hacer lo mismo. Todas estas limitaciones producen código de manejo de excepciones mas robusto5. De esta forma. las excepciones lanzadas por el método de la clase base. pero en los constructores hay un problema.

una clase llamada InputFile es creada que abre un fichero y permite leer una línea (convertida a String) a la vez. // Rethrow } finally { // No cierre aquí!!! } } String getLine() { String s. class InputFile { private BufferedReader in. s = "failed". Se utilizan las clases FileReader y BufferedReader de la librería estándar de Java que serán tratadas en el Capítulo 11.*. } catch(IOException e2) { 460 Pensando en Java www. import java.println( "in. } catch(IOException e) { System. // No fué abierto. try { s = in. } catch(Exception e) { // Todas las otras excepciones deben cerrarse try { in.es mejor que se intente evitar este tipo de limpieza en finally a no ser que se este forzado. así es que no se cierra throw e. } return s.io.close().close(). En el siguiente ejemplo.BruceEckel.close() unsuccessful"). } void cleanup() { try { in.com . } catch(IOException e2) { System. InputFile(String fname) throws Exception { try { in = new BufferedReader( new FileReader(fname)).err.println( "Could not open " + fname).err.err. // Otro código que puede lanzar excepciones } catch(FileNotFoundException e) { System. } throw e.println( "readLine() unsuccessful").java // Prestar attention a las excepciones // en constructores. pero que son suficientemente simples para que probablemente no se tenga problema entendiendo su uso básico: //: c10:Cleanup.readLine().

println( "in. de debe querer dividir las cosas en muchos bloques try ).close() unsuccessful").System.err. esto es difícil si mas de un método puede lanzar un FileNotFoundException. El método close() puede lanzar una excepción o sea que es probado y atrapado aún cuando esta dentro del bloque de otra cláusula catch -esto es solo otro par de llaves para el compilador de Java. Dado que queremos que el fichero sea abierto durante el tiempo de vida del objeto InputFile esto no sería apropiado. Dentro del bloque try .err. e. se crea un FileReader utilizando el nombre de fichero. que es el nombre del fichero que se quiere abrir.println(""+ i++ + ": " + s). En este ejemplo.err).printStackTrace(System. Un FileReader no es particularmente útil hasta que se convierta y se use para crear un BufferedReader con el cual se puede establecer una comunicación -debe notarse que uno de los beneficios de InputFile es que combina estas dos acciones. y no se quiere que la llamada al método asuma que el objeto fue creado adecuadamente y era válido. lanza una FileNotFoundException. } } } ///:~ El constructor para InputFile toma un argumento String.out.println( "Caught in main. Si el constructor de FileReader no es exitoso.getLine()) != null) System. lo que es apropiado porque este constructor ha fallado. Luego de realizar las operaciones locales la excepción es lanzada nuevamente.printStackTrace()"). while((s = in. Este llama a readLine(). e. String s. que debe ser capturada separadamente porque es el único caso en donde no se quiere cerrar el fichero porque fue abierto en el momento en que se entraba a las cláusulas catch (Claro. El método getLine() retorna un String conteniendo la siguiente línea del fichero. pero esa 461 . int i = 1. la cláusula finally no es definitivamente el lugar para llamar a close() y cerrar el fichero. } } } public class Cleanup { public static void main(String[] args) { try { InputFile in = new InputFile("Cleanup. que no usa la técnica antes mencionada de las banderas. } catch(Exception e) { System.java"). En ese caso.cleanup(). in. que puede lanzar una excepción. dado que lo cerrará cada vez que el constructor sea completado.

Uno de los beneficios de este ejemplo es mostrar como las excepciones son introducidas en este punto en el libro -no se puede crear entradas y salidas básicas sin utilizar excepciones. no se sabe cuando). así es que se debe informar al cliente programador que son responsables. El método getLine() se convierte en: String getLine() throws IOException { return in.excepción es capturada así es que getLine() no lanza ninguna excepción. o cuando pasarla simplemente. el sistema de manejo de excepciones mira los manejadores mas “cercanos” en el orden en que son escritos. En Cleanup. el método que ha llamado ahora es responsable por el manejo de cualquier IOException que pueda originarse.java e InputFile es creada para abrir el mismo fichero fuente que crea el programa. Todas las excepciones son capturadas genéricamente en main(). Pasándola. para manejarla parcialmente y pasar la misma excepción (o una diferente). Las excepciones son integrales para programar en Java. en el punto que se lo quiera dejar ir.com . } Pero claro.readLine().no se suceden automáticamente. y posiblemente garantizar que la limpieza sucede utilizando finalize(). No se quiere hacer esto hasta que se ha finalizado con el objeto InputFile. cuando es apropiado. un destructor puede manejar esto. De puede pensar que colocar alguna funcionalidad dentro de un método finalize() será llamado (aún si se puede asegurar que será llamado. Excepciones que coinciden Cuando una excepción es lanzada. El método cleanup() debe ser llamado por el usuario cuando se finalice utilizando el objeto InputFile. y son agregados números de línea. lo que se puede lograr sin mucho conocimiento de como trabajar con ellas.BruceEckel. Esto liberará los recursos del sistema (como el manejador de fichero) que es utilizado por BufferedReader y/o por los objetos FileReader 6. la excepción es considerada manejada. Esto es uno de las desventajas de Java: toda la limpieza -otras además de la memoria. Uno de los temas de diseño con excepciones es cuando manejar una excepción completamente en este nivel. y ninguna otra búsqueda se realiza. especialmente porque el compilador las fuerza. 6 En C++. Cuando encuentra una coincidencia. el fichero es leído una línea a la vez. puede ciertamente simplificar el código. 462 Pensando en Java www. a pesar de que se puede elegir por una gran granularidad.

println("Caught Annoyance").err. como esto: try { throw new Sneeze(). Sin embargo.java // Catching exception hierarchies. como se muestra en este ejemplo: //: c10:Human. } El compilador dará un mensaje de error. } catch(Annoyance a) { System. dejando solo: try { throw new Sneeze().println("Caught Annoyance"). } catch(Sneeze s) { System.Que coincida una excepción no requiere una perfecta coincidencia entre la excepción y su manejador. claro esta. dado que ve que la cláusula de captura de Sneeze nunca será alcanzada. Si se trata de “disfrazar” las excepciones de la clase derivada poniendo la clase base el la clase base primero. catch(Annoyance e) capturará una Annoyance o cualquier clase derivada deesta. } } } ///:~ La excepción Sneeze será capturada por la primer cláusula catch que coincida -la que sea la primera. } El código seguirá trabajando porque se esta capturando la clase base de Sneeze.println("Caught Sneeze"). si se quita la primer cláusula de captura.println("Caught Annoyance").err. } catch(Annoyance a) { System. } catch(Sneeze s) { System. class Annoyance extends Exception {} class Sneeze extends Annoyance {} public class Human { public static void main(String[] args) { try { throw new Sneeze(). } catch(Annoyance a) { System. 463 .err. entonces el código del cliente programador no necesitará cambios mientras el cliente capture las excepciones de la clase base.println("Caught Sneeze"). Una clase derivada coincidirá un manejador para la clase base.err. Poniéndolo de otra forma.err. Esto es útil porque si se decide agregar a un método mas excepciones derivadas.

y son uno se esas características que proporcionan beneficios inmediatos y significantes para su proyecto. 464 Pensando en Java www. 2.Líneas guía para la excepción Use excepciones para: 1. 8. Hacer lo que se pueda en el contexto actual y lanzar la misma excepción a un contexto mas alto. y con mas confianza de que su aplicación no tiene un error sin manejar. 6. Hacer lo que se pueda en el contexto actual y lanzar una excepción diferente en un contexto mas alto. Java implementan todos los aspectos de las excepciones así es que es garantido que ellas sean utilizadas consistentemente por los diseñadores de librerías y los clientes programadores. donde una de las metas principales es crear componentes de programa para que otros utilicen. Arreglar las cosas y continuar sin intentar de nuevo con el método. pero es especialmente importante en Java. 3.BruceEckel. Las excepciones no son horriblemente difíciles de aprender. entonces es doloroso y es molesto de utilizar). Las metas para el manejo de excepciones en Java son simplificar la creación de programas largos y confiables utilizando la menor cantidad de código que sea posible. y una inversión a largo plazo (para robustez de aplicación). Resumen Mejorar la recuperación del error es una de las formas mas poderosas que se tiene para incrementar la robustez del código. Solucionar el problema y llamar el método que produce la excepción nuevamente. 7. 4. Simplificar (Si el esquema de la excepción hace cosas mas complicadas. Afortunadamente.com . La recuperación del error es una preocupación fundamental para cualquier programa que se escriba. Terminar el programa. Calcular algún resultado alternativo en lugar de lo que el método se supone tiene que producir. 5. cada componente debe ser robusto . Para crear un s istema robusto. Hacer su librería y programa mas seguro (Es una inversión a corto plazo para depurar.

Escriba un constructor para esta clase que tome un argumento String y lo almacene dentro del objeto con una referencia String. 2. Escriba un método que imprima el String almacenado. cree una C y realícele una conversión ascendente hacia A. lance una excepción de un nuevo tipo que defina. Herede B de A y sobrescriba el método así este lanza una excepción en el nivel dos de su jerarquía. En el main(). lance una excepción diferente (de un segundo tipo que defina). En el main(). Defina una referencia a un objeto e inicialícela a null. luego llame el método. Cree una cláusula try catch para ejercitarse con su nueva excepción.BruceEckel. 1. En g(). Cree una clase con dos métodos. Trate de compilarla sin una especificación de excepción para ver que dice el compilador. En f(). Ahora cree una clase base A con un método que lance una excepción en la base de su jerarquía. 7. f() y g(). disponible por una pequeña propina en www. Cree una clase con un main() que lance un objeto de la clase Exception dentro de un bloque try . Dele el constructor para la Exception un argumento String. Escriba una clase con un método que lance las tres. Agregue la especificación de excepción apropiada. 6. Repita heredando la clase C de la B.com . 465 . 3. Prueba su código en el main(). Escriba un código para generar y capturar un ArrayIndexOutOfBoundsException.Ejercicios La solución de los ejercicios seleccionados pueden encontrarse en el documento electrónico The T h i n k i ng in Java Annotated Solution Guide. Cree tres nuevos tipos de excepciones. Ponga a prueba su clase y su excepción dentro de una cláusula try -catch. Cree su propio comportamiento análogo a una reanudación utilizando un bucle while que repita hasta que una excepción no sea lanzada. Capture la excepción dentro de una cláusula catch e imprima el argumento String. Trate de llamar a un método con esta referencia. 4. Ahora envuelva el código en una cláusula try -catch para capturar la excepción. en la cláusula catch. capture la excepción y. 8. Agregue una cláusula finally e imprima el mensaje para probar que está ahí. Cree una jerarquía de excepciones de tres niveles. 9. llame el método pero solo use una sola cláusula catch que capturará los tres tipos de excepciones. llame a g(). 5. Cree su propia clase excepción utilizando la palabra clave extends. Escriba una clase con un método que lance una excepción del tipo creado en el Ejercicio 2.

BruceEckel. Demuestre que un constructor de una clase derivada no puede capturar excepciones lanzadas por el constructor de su clase base. Cree un ejemplo donde se use una bandera de control si un código de limpieza es llamado. 18. Quite la primer cláusula catch en Human. En el Capítulo 5. encuentre los dos programas llamados Assert. aún si un NullPointerException es lanzada. Muestre que WithFinally.java puede fallar lanzando una RuntimeException dentro del bloque try . Agregue un grupo apropiado de excepciones a co8:GreenhouseControls.java puede fallar lanzando una RuntimeException dentro del bloque try . 17. 16.java agregando una excepción UmpireArgument. 14.java y verifique que el código sigue compilando y corre correctamente. 19. Pruebe la jerarquía modificada.java.err. 12. 466 Pensando en Java www. Muestre que OnOffSwitch. 13. Agregue un segundo nivel de excepción perdida a LostMessage.java y modifique estos para lanzar sus propios tipos de excepción en lugar de imprimir a System. como se describe en el segundo párrafo luego del encabezado “Constructores”. y los métodos que lanzan esta excepción. Verifique que su cláusula finally es ejecutada. Esta excepción debe ser una clase interna que extienda RuntimeException. Modifique el ejercicio 6 agregando una cláusula finally .10. 15. Modifique StormyInning.java de tal forma que HoHumException es remplazada por una tercer excepción.com . 11.

la consola. conexiones de redes). acceso aleatorio. caracteres. Esto queda en evidencia por la cantidad de diferentes estrategias. cuando la librería originalmente orientada a byte fue complementada con la orientada a char. etc. se puede preguntar por el grupo con 467 . los actuales diseños de la E/S de Java previene una explosión de clases). veremos una utilidad proporcionada con la librería para asistir en el manejo de temas de directorios de ficheros. De hecho. es muy importante entender la historia evolutiva de la librería de E/S. Como resultado hay una justa cantidad de clases para aprender antes de que se entienda lo suficiente la imagen de la E/S de Java para que se pueda utilizar adecuadamente.). La clase File tiene un nombre engañoso -se puede pensar que se refiere a un fichero.11: El sistema de E/S de Java Crear un buen sistema de entrada/salida(E/S) es una de las tareas mas difíciles para el diseñador de lenguajes. El problema es que sin la perspectiva histórica se comienzan a confundir con algunas de las clases y cuando utilizarlas o no. Si es un grupo de ficheros en un directorio. Este capítulo dará una introducción a la variedad de clases de E/S en la librería estándar de Java y como utilizarlas. El reto parece ser cubrir todas las eventualidades. palabras. La clase File Antes de introducirnos a las clases que actualmente leen y escriben datos en flujos. clases basadas en Unicode. Además. pero no lo es. binario. No solo tienen diferentes fuentes y sinks de E/S con los que se quiere comunicar (ficheros. Puede representar tanto el nombre de un fichero particular como los nombres de un grupo de ficheros en un directorio. con buffers. por líneas. aun si su primera reacción es “¡No me molesten con historia. hay tantas clases para el sistema de E/S que puede ser intimidante al principio (irónicamente. También hubo un cambio significante en la librería de E/S luego de Java 1. Los diseñadores de la librería de Java atacaron este problema creando muchas clases. solo muéstrenme como se utiliza!”. sino que se necesita hablar con ellos en una amplia variedad de formas (secuencial.0.

que es una clase que indica como seleccionar los objetos de File para desplegar.com .length.getName(). } } class DirFilter implements FilenameFilter { String afn. String name) { // información desnuda de la ruta: String f = new File(name). i < list. y si se quiere un listado de directorio diferente solo se crea un objeto File diferente.BruceEckel.list(new DirFilter(args[0])). se obtiene la lista completa que el objeto File contiene.bruceeckel. return f.java.Array. } public boolean accept(File dir. si se quiere una lista clasificada -por ejemplo. import java.*.java // Displays directory listing. Sin embargo. import com. De hecho. He aquí el código para el ejemplo.*. i++) System. Le da sentido a retornar un arreglo en lugar de uno de las flexibles clases contenedoras porque el número de elementos es fijo. DirFilter(String afn) { this. El objeto File puede ser listado de dos formas. else list = path. Si se llama a list() sin argumentos. “FilePath” podría haber sido un mejor nombre para la clase. Arrays.out. } } ///:~ 468 Pensando en Java www.el método list(). if(args. incluyendo la interfase asociada FilenameFilter. si se quiere todos aquellos ficheros con una extensión de .*. public class DirList { public static void main(String[] args) { File path = new File(".afn = afn. new AlphabeticComparator()).util. import java. Esta sección muestra un ejemplo del uso de esta clase.io. Note que el resultado ha sido fácilmente ordenado (alfabéticamente) utilizando el método java. y este retorna un arreglo de String.sort(list.println(list[i]). for(int i = 0. String[] list.sort() y el AlphabeticComparator definido en el capítulo 9: //: c11:DirList.").util.indexOf(afn) != -1.entonces se puede utilizar un “filtro de directorio”.length == 0) list = path.utils.list(). Una clase que crea lista de directorios Supóngase que se quiere ver un listado de un directorio.

La única razón detrás de la creación de esta clase es proporcionar el método accept() para el método list() de tal forma que list() pueda devolverle la llamada a accept() para determinar que nombres de fichero deben ser incluidos en la lista.La clase DirFilter “implementa” la interfase FilenameFilter. el constructor de DirFilter es también creado. esto significa que se puede pasar un objeto de cualquier clase que implemente FilenameFilter para elegir (aún en tiempo de ejecución) como el método list() debe comportarse. pero probablemente al menos se utilizará el nombre de fichero. pero si no se encuentra el valor de retorno es -1. Esta es útil para ver que tan simple la interfase FilenameFilter es: public interface FilenameFilter { boolean accept(File dir. De esta forma. Se puede indagar este arreglo por su largo y luego moverse a través de el seleccionando los elementos. El método accept() debe aceptar un objeto Fichero representando el directorio donde se encuentra un fichero en particular. y un String conteniendo el nombre de este fichero. En este caso. Si afn se encuentra dentro de la cadena. Para asegurarse que el elemento con el que se esta trabajando solo es el nombre del fichero y no contiene información de la ruta. Se puede elegir utilizar o ignorar estos argumentos.b?r”. todo lo que se tiene que hacer es tomar el objeto String y crear un objeto File fuera de el. el valor retornado es el índice inicial de afn. DirFilter muestra que solo porque una interfase contiene solo un grupo de método. El método list() retorna un arreglo. DirFilter es un funtor porque su único trabajo es almacenar un método) o el Command Pattern.que es mucho mas difícil de implementar. Se debe tener presente que esto es una simple búsqueda de cadenas y no tiene ni una gota de expresiones de correspondencia con comodines -como lo es “fo?. Se debe recordar que el método list() llama a accept() por cada uno de los nombres de ficheros encontradas en el objeto directorio para ver cual debe ser incluido -esto es indicado por un resultado boolean retornado por accept(). String name). Luego accept() utiliza el método indexOf() de la clase String para ver si la cadena de búsqueda afn aparece en algún lado en el nombre del fichero. Porque list() toma un objeto FilenameFilter como argumento. sin embargo). esta técnica es a menudo referida como una callback y a veces como functor (esto es. que quita toda la información de ruta (de una forma independiente de la plataforma). } Esta dice todo lo que el tipo de objeto hace es proporcionar una llamada al método accept(). Esta 469 . El propósito de la devolución de la llamada es proporcionar flexibilidad en el comportamiento del código. luego llamar a getName(). se debe estar restringido a escribir solo estos métodos (se debería por lo menos proporcionar definiciones para todos los método es una interfase.

*.out. if(args.util. Este diseño es una mejor porque la clase FilenameFilter esta ahora estrechamente unida a DirList2. 470 Pensando en Java www.*.java // Creando la clase anónima interna "en el lugar.util. se crea un método filter() que retorna una referencia a FilenameFilter: //: c11:DirList2.util.list(). else list = path. i < list.bruceeckel. public class DirList2 { public static FilenameFilter filter(final String afn) { // Creation of anonymous inner class: return new FilenameFilter() { String fn = afn.println(list[i]).io.*. en tal caso es aún mas pequeña: //: c11:DirList3. Clases internas anónimas Este ejemplo es ideal para rescribir utilizando una clase anónima interna (descrita en el Capítulo 8).sort(list.io.getName(). import com.java // Uso de las clases anónimas internas. import java.length. } } ///:~ Debe notarse que el argumento para filter() debe ser final.list(filter(args[0])). for(int i = 0. } }. String n) { // Desnuda la información de ruta: String f = new File(n).length == 0) list = path.*. import java. Esto es requerido por la clase interna anónima así es que puede utilizar un objeto fuera de su alcance. i++) System. new AlphabeticComparator()). String[] list.habilidad de pasar un arreglo dentro y fuera de un método es una tremenda mejora sobre el comportamiento de C y C++.com . import java. public boolean accept(File dir. // Fin de la clase anónima interna } public static void main(String[] args) { File path = new File(". se puede ir mas allá con esta estrategia y definir la clase anónima interna como un argumento para list()." import java. Sin embargo. return f.*.indexOf(fn) != -1. Arrays.").BruceEckel. Como una primera aproximación.

*. y borrar el fichero. } }).sun. Arrays.. Dado que todo en Java gira alrededor de las clases. i < list...getName(). la fecha de la última modificación. String[] list.. ver cuando un objeto File representa un fichero o un directorio. así es que debe utilizarse juiciosamente.length. } } ///:~ El argumento para main() es ahora final. dado que la clase anónima interna utiliza args[0] directamente.out. String n) { String f = new File(n). Probando y creando directorios La clase File es mas que simplemente una representación para un fichero o directorio existente.util. esto puede ser una técnica de codificación útil.com): //: c11:MakeDirectories.bruceeckel. Se puede también ver las características de los ficheros (tamaño. Uno de los beneficios es que mantiene el código que soluciona un problema en particular aislado junto en un sitio. Para mostrar como una clase anónima interna permite la creación de clases rápidas y sucias para solucionar problemas. else list = path.println(list[i]).io.\n" + "Creates each path\n" + "Usage:MakeDirectories -d path1 . public class MakeDirectories { private final static String usage = "Usage:MakeDirectories path1 .import com.length == 0) list = path.sort(list."). Por otro lado.*. import java. for(int i = 0.list(new FilenameFilter() { public boolean accept(File dir.java // Demonstrates the use of the File class to // create directories and manipulate files.indexOf(args[0]) != -1. También se puede utilizar un objeto File para crear un nuevo directorio o una ruta completa si no existe. if(args. lectura o escritura). no es siempre tan fácil de leer.list(). public class DirList3 { public static void main(final String[] args) { File path = new File(". return f. i++) System.\n" + 471 . new AlphabeticComparator()). Este programa muestra alguno de los otros métodos disponibles con la clase file (si se quiere ver el grupo completo vea en la documentación HTML de java.

com .lastModified()). else if(f. File old = new File(args[1]).println( "Absolute path: " + f.out.isDirectory()) System. } } 472 Pensando en Java www." + f)..getName() + "\n getParent: " + f. if(f.println("it's a directory"). if(args[0].out. System.isFile()) System. } public static void main(String[] args) { if(args.delete()."Deletes each path\n" + "Usage:MakeDirectories -r path1 path2\n" + "Renames from path1 to path2\n".println(usage).canWrite() + "\n getName: " + f. old. count < args.out.BruceEckel.exists()) { System.renameTo(rname).out. // Exit main } int count = 0.exit(1). } for( .getParent() + "\n getPath: " + f. if(args[0]. } private static void fileData(File f) { System.println("it's a file").out.getPath() + "\n length: " + f. if(f.equals("-r")) { if(args.err.println("deleting.canRead() + "\n Can write: " + f.println("created " + f). f.length < 1) usage().length() + "\n lastModified: " + f.out.length. rname = new File(args[2]). System. } } else { // Doesn't exist if(!del) { f.mkdirs(). del = true. if(del) { System. private static void usage() { System.length != 3) usage(). fileData(rname).. count++) { File f = new File(args[count]).getAbsolutePath() + "\n Can read: " + f.equals("-d")) { count++.println(f + " exists"). return. boolean del = false. fileData(old).

Por herencia. Esto también trabaja con directorios de cualquier largo. en lugar de eso acomodaremos múltiples objetos juntos para proporcionar la funcionalidad deseada. encontrará que se puede crear una ruta a directorio de cualquier complejidad porque mkdirs() hará todo el trabajo. El primer método que es ejercitado por main() es renameTo(). Si se experimenta con el programa mas arriba. De la misma forma. Sin embargo. En Java 1.fileData(f). Es útil categorizar las clases por su funcionalidad. que es otro objeto File. generalmente no se pueden utilizar estos método. raramente se crea un objeto flujo utilizando una sola clase. todo lo derivado de las clases InputStream o Reader tienen métodos básicos llamados read() para leer un solo byte o arreglos de bytes. que representa cualquier fuente de datos o drenaje como un objeto capaz de producir o recibir pedazos de datos. que permite cambiar el nombre (o mover) un fichero a una ruta totalmente nueva representada por el argumento. como se puede ver en la jerarquía de clases de Java en línea con un navegador Web. De esta forma.0. Entrada y salida Las librarías de E/S a menudo usan la abstracción de un flujo. estos existes para que otras clases puedan utilizarlos -estas otras clases proporcionan una interfase mas útil. los diseñadores de la librería comenzaron por decidir que todas las clases que tengan algo que hacer con la entrada serían heredados de InputStream y todas las clases que son asociadas con la salida serían heredadas de OutputStream. La librería de clases de Java para la E/S esta dividida en entrada y salida. } } } ///:~ En fileData() se puede ver varios métodos de investigación de ficheros utilizados para desplegar información acerca de la ruta de un fichero o directorio. todas las cosas derivadas de las clases OutputStream o Wrtiter tienen métodos básicos llamados write() para escribir un solo byte o un arreglo de bytes. El hecho de que se cree mas de un objeto para crear un solo flujo resultante es la razón primaria para que la librería de flujo de Java sea confusa. El flujo oculta los detalles de que sucede con los datos dentro del dispositivo actual de E/S. 473 .

Conectada a un objeto FilterInputStream para proporcionar una interfase útil. Cada uno de estos tiene una subclase asociada de InputStream. así se pueden juntar en un solo flujo. como una conexión a la Internet (Esto será discutido en capítulo mas adelante). 3. 2. Un objeto String. que trabaja como un tubo físico: se colocan cosas en un lado y aparecen por el otro. el FilterInputStream también es un tipo de InputStream. Una secuencia de otros flujos. Tipos de InputStream Class ByteArrayInputStream Function Argumentos del constructor Como utilizarlos Permite que un buffer El buffer de donde se extraen los en memoria sea datos utilizado como un Como una fuente de datos. Como una fuente de datos. Esto se discute mas adelante. Un arreglo de bytes. FileInputStream Para leer información de un fichero Un String representando el nombre del fichero. Además. Un fichero. o un objeto File o un objeto FileDescriptor. La implementación un InputStream de capas mas bajas actualmente utiliza un StringBuffer. para proporcionar una clase base para clases “decoradoras” que enganchan atributos o interfases útiles a los flujos de entrada.com .BruceEckel. Tabla 11-1. Otras fuentes. Una “tubo” o “pipe”. 5. 6. Estas fuentes pueden ser: 1. InputStream Conectada a un objeto FilterInputStream para proporcionar una interfase útil Convierte un String en Un String. 4.Tipos de InputStream El trabajo de InputStream es representar clases que produzcan entrada de diferentes fuentes. StringBufferInputStream 474 Pensando en Java www.

Conectada a un objeto FilterUnputStream para proporcionar una interfase útil. Dos objetos InputStream o una Enumeration de contenedores de objetos InputStream. el FilterOutputStream proporciona una clase base para clases “decoradoras” que enganchan atributos o interfases útiles a los flujos de salida. Conectada a un objeto FilterInputStream para proporcionar una interfase útil. Tipos de OutputStream Clase ByteArrayOutputStream Función Argumentos del constructor Como utilizarla Crea un buffer en Tamaño inicial del buffer memoria. Además. datos que se envían a el 475 . o una “tubería”. Vea la tabal 11-3. Como una fuente de datos. PipedOutputStream Como una fuente de datos en multihilado. SequenceInputStream FilterInputStream Clase abstracta que es una interfase para decoradores que proporcionan funcionalidades útiles a otras clases InputStream. Conectado a un objeto FilterInputStream para proporcionar una interfase útil. Implementa el concepto de canalización. sin embargo. eso será discutido mas tarde. un fichero. Tabla 11-2. PipedInputStream Produce que los datas sean escritos en la PipedOutputStream asociada. Vea la tabal 11-3. Todos los opcional. Tipos de OutputStream Esta categoría incluye las clases que deciden donde irá la salida: un arreglo de bytes (no un String.Como una fuente de datos. Vea la tabal 11-3. Convierte dos o mas objetos I n putStream en un solo InputStream. presumiblemente se puede crear uno utilizando el arreglo de bytes).

una interfase para Vea la tabla 11-4. FilterOutputStream Clase abstracta que es Vea la tabla 11-4. un objeto FilterOutputStream que provea una interfase útil. PipedOutputStream Cualquier información que se escriba aquí automáticamente termina como entrada para la PipedInputStream asociada. Para designar el destino de los datos. Debe conectarse a un objeto FilterOutputStream para proporcionar una interfase útil.BruceEckel. Agregando atributos y interfases útiles El uso de objetos en capas para agregar responsabilidades de forma dinámica y transparente a objetos individuales es referido como patrones 476 Pensando en Java www.com . File-OutputStream Para enviar información Una cadena representando a un fichero en nombre del fichero.datos que se envían a el Para designar el destino de flujo es colocado en este los datos. decoradores que proporcionan funcionalidad útil para las otras clases OutputStream. Debe conectarse a buffer. Implementando el concepto de “canalización” PipedInputStream Para designar el destino de los datos para multihilado. Vea la tabla 11-4. o un objeto File o un objeto FileDescriptor. Debe conectarse a un objeto filterOutputStream para proporcionar una interfase útil.

477 . pero agregan complejidad a el código. Esta es la razón para la existencia de clases “filtro” en la librería de E/S de Java: las clases abstractas “filtros” son la clase base para todos los decoradores (Un decorador debe tener la misma interfase que el objeto que esta decorando. que se puede bajar en www. Erich Gamma et al. como readByte(). Esto.).BruceEckel.. Los decoradores dan mucho mas flexibilidad cuando se esta escribiendo un programa (dado que se puede fácilmente mezclar y hacer corresponder atributos). que son el requerimiento clave del decorador (así es que proporcionan la interfase común a todos los objetos que son decorados). Las clases que proporcionan la interfase decoradora para controlar un InputStream o un O utputStream particular son las FilterInputStream y FilterOutputStream.Decoradores (Patrones1 son el tema de Pensando en patrones con Java el . Estos “lugares” son determinados por las clases en la Tabla 11-1. La librería de E/S de Java requiere muchas combinaciones diferentes de características.que no tienen nombres muy intuitivos. permite mover datos primitivos de un lugar a otra mediante un flujo. Esto es un inconveniente para los patrones decoradores. FilterInputStream y FilterOutputStream son clases abstractas que son derivadas de las clases base de la librería de E/S. que es por que el patrón decorador es utilizado. sin embargo. La razón para la que la librería de E/S de Java es complicada de utilizar es que se debe crear muchas clases -el tipo “corazón” de E/S mas todos los decoradores.para obtener el único objeto de E/S que se quiere. readFloat(). Addison-Wesley 1995. DataInputStream permite leer diferentes tipos de datos primitivos de la misma forma que objetos String (Todos los métodos comienzan con “read”. Leyendo de un InputStream con FilterUInputStream Las clases FilterInputStream logran dos cosas significativamente diferentes. Los decoradores son a menudo utilizado donde realizar simples subclases resulta en una gran cantidad de subclases para satisfacer cada combinación posible que se necesite -de esta manera muchas subclases se vuelven impracticas. esto hace el uso básico de los decoradores transparente se envía el mismo mensaje a un objeto esté decorado o no. pero el decorador puede también extender la interfase.com). junto con su compañera DataOutputStream. 1 Design Patterns. lo que ocurre en muchas de las clases “filtros”). InputStream y OutputStream. Los patrones decoradores especifican que todos los objetos que envuelven los objetos iniciales tienen la misma interfase. etc.

InputStream Contiene una interfase entera que permite leer tipos primitivos Buffered-InputStream Se debe utilizar para prevenir una lectura física cada ves que se quiera mas datos. Tabla 11-3. InputStream. etc. con un tamaño de buffer opcional. solo una exigencia de que un buffer sea utilizado. y cuando se puede rechazar un solo carácter.com . independientemente del dispositivo de E/S al que se esté conectando. LineNumberInputStream Mantiene la pista de los números de línea en la entrada de flujo. Tipos de FilterInputStream Clase Función Argumentos del constructor Como utilizarla Data -InputStream Utilizada coordinada con DataOutputStream.Las clases restantes modifican la forma en que InputStream se comporta internamente: si utiliza un buffer o no. así es que probablemente no las use en programación general. así es que probablemente se enganche un objeto interfase.) de un flujo en una forma portátil. Enganchando un objeto interfase. long. Se está diciendo “Utilice un buffer”. de tal forma que se puedan leer primitivas (int. Las dos últimas clases se ven mucho como soporte para construir un compilador (esto es.BruceEckel. son agregadas para apoyar la construcción del compilador de Java). Esta no proporciona una interfase de por si. InputStream Esto solo agrega numeración de líneas. Probablemente se necesite un buffer en su entrada al menos todo el tiempo. se puede llamar a getLineNumber()y setLineNumber(int) . char. si lleva un control de las líneas que lee (permitiendo que se pregunte por el número de líneas o configurar el número de línea). 478 Pensando en Java www. así es que tiene mas sentido para la librería de E/S hacer una excepción (o simplemente una llamada a un método) para una entrada que no utilice un buffer en lugar de una que si lo utilice.

Puchback-InputStream Tiene un buffer de rechazo de un solo carácter de tal forma que se puede rechazar el último carácter leído.

InputStream Generalmente utilizado en la búsqueda por un compilador y probablemente incluido porque el compilador de Java lo necesita. Probablemente nunca se utilice.

Escribiendo un OutputStream con FilterOutputStream
El complemento de DataInputStream es DataOutputStream, que organiza los datos de cada uno de los tipos primitivos y objetos String en un flujo de tal forma que cualquier DataInputStream, en cualquier máquina, pueda leerlo. Todos los métodos comienzan con “write”, como writeByte(), writeFloat(), etc. El intento original de PrintStream fue imprimir todos los tipos de datos primitivos y objetos String en un formato que se pudiera ver. Esto es diferente de DataOutputStream, cuya meta es colocar elementos de datos en un flujo de una forma en que DataInputStream puede reconstruirlo sin de forma portátil. Los dos métodos importantes en PrintStream son print() y println(), que son sobrecargados para imprimir todos los distintos tipos. La diferencia entre print() y println() es que el último agrega una nueva línea cuando termina. PrintStream puede ser problemático porque atrapa todas las IOExceptions (Se debe explícitamente verificar el estado de error con checkError(), que retorna true si un error ha sucedido). También, PrintStream no esta correctamente internacionalizado y no maneja los saltos de línea de una forma independiente de la plataforma (estos problemas son solucionados con PrintWriter). BufferedOutputStream es un modificador e indica el flujo a utilizar con buffer así es que no se tiene una escritura física cada vez que se escribe en el flujo. Probablemente siempre se quiera utilizar esto con ficheros, y posiblemente con la E/S de consola. Tabla 11-4. Tipos de FilterOutputStream

479

Clase

Función

Argumentos del constructor Como utilizarlo OutputStream Contiene una interfase completa para permitir escribir tipos primitivos. OutputStream, con un boolean opcional indicando que el buffer es limpiado con cada nueva línea. Suele ser la envoltura “final” para su objeto OutputStream. Normalmente se utiliza mucho. OutputStream, con un tamaño de buffer opcional. Esto no proporciona una interfase de por si, solo un requerimiento de que un buffer es utilizado. Enganche un objeto interfase.

Data OutputStream

Utilizado en coordinación con DataInputStream así es que se puede escribir primitivas (int, char, long, etc.) a un flujo en una forma portátil. Para prod